background

​ Recently in the work and amateur open source contributions , Contact with unit tests is frequent . However, the unit tests written under these two scenarios seem different , Even the same code scenario , The unit tests written today are not the same as those written yesterday , I feel that for unit testing , I don't have a unified specification and methodology for unit testing practice . After writing some unit tests, I began to want to know some best practices and techniques for writing unit tests .( In fact, when I reflected later, I thought , I should first learn the best practices related to unit testing , There is a general concept , It would be better to practice again .) It is summarized here as an article to share with you , I hope readers can gain something .

1. Why write unit tests

​ Unit testing is an essential part of a good project , It is particularly critical in a project that changes frequently and cooperates with many people . From the perspective of the programmer , In fact, many times you are not 100% sure that your code is free from any problems , There are many uncertain factors in the computer world , For example, we may not be sure about some dependencies in the code , In the actual code execution process, it will meet our expectations , We are not sure whether the logic we write can cover all scenarios , For example, there may be written if There are no more else The situation of . So we need to write a self-test to prove that our code is OK , Of course, writing a self-test does not guarantee that the code is completely free of problems , It can only be said that problems can be avoided as much as possible . Secondly, for a multi person project , Open source projects , It's good to work with multiple people , If you want to understand what a piece of logic is , Or to understand how the code works , The best entry point is often to look at the unit tests of the project or participate in writing the unit tests of the project . I personally want to learn about an open source project from unit testing , Unit tests can tell me a piece of logic what this code does , His expectation is what the input is , What is the output , What scenario will report an error .

2. How to write unit tests well

​ This chapter will explain why some code is difficult to test , And how to write a good test . Here I will give some examples of the code of some open source projects I have seen .

2.1 What code is harder to test

​ In fact, not all code can be tested , Or some code is not easy to test , Sometimes for the convenience of testing , You need to reconstruct the code to look easy to test . But many times before writing unit tests , You don't even know that the code you write is actually not measurable . Here I'm going to raise go-mysql Some code examples to illustrate the factors that are not measurable or easy to measure .

go-mysql yes pingcap The chief architect, Mr. Tang Liu, implemented a mysql Tool library , It provides some practical tools , such as canal Modules can consume mysql-binlog Data implementation mysql Replication of data ,client The module is a simple mysql drive , Realization and mysql And so on , Other functions can go to github Look up readme Detailed introduction . Recently, I have read a lot of the source code of this library for my work , So here are some examples of code .

2.1.1 The code depends on the external environment

​ When we actually do some code , In fact, part of the code will depend on the external environment , For example, some of our logic may need to be connected to mysql, Or you may need one tcp The connection of . For example, the following code :

/*
Conn is the base class to handle MySQL protocol.
*/
type Conn struct {
net.Conn
bufPool *BufPool
br *bufio.Reader
reader io.Reader
copyNBuf []byte
header [4]byte
Sequence uint8
}

​ This is go-msyql A structure that handles network connections , What we can see is that inside this structure is a net.Conn Interface , Not a specific implementation , This provides a very flexible test method , It only needs mock One net.Conn The implementation class can test its related methods , If the package here is net.Conn For example TCPConn, This makes it difficult to test , When writing unit tests, you may need to provide them with a TCP Environment , In fact, it is troublesome .

​ The second example comes from go-mysql canal This module , The main function of this module is through consumption mysql binlog In the form of mysql The data of , So how to test the overall logic here , This module is disguised as mysql To replicate data from the node , Where is the master node , Here we need to be honest mysql The environment . We can see how the author tests , The code here is too long, so I won't post it , hold GitHub The code link for is posted here , Interested readers can go to see Click here to see github Code . The author in CI I got one in the environment mysql Environment , Then, before the test, by performing some sql Statement to build the test environment , In the process of testing, it is also through execution sql To produce the corresponding binlog To verify your logic .

2.1.2 The code is too redundant

​ Sometimes writing code may be just for fun , A handful of Soha put all the logic in one function , This will cause too much logic to pile up , There may be too many branches when testing , Therefore, in order to make unit tests look concise, we may need to split such logic , Put together the logic of doing one thing , Do the corresponding test . Then it is good to test the whole logic .

2.2 How to write a unit test

​ For the convenience of describing this content , Here I simply provide a function like this . The logic of this function is relatively simple , Is to enter a name , Then return a message to say hello to you .

func Greeter(name string) string {
return "hi " + name
}

​ So how to write the test of this function . I understand that there are two key points , One is the naming of unit tests , The second is the content architecture of unit testing .

2.2.1 Naming unit tests

​ In fact, the naming is also exquisite , I understand that unit tests are also for others , So when I look at your unit tests , It is better to have : Test object , Input , Expected output . In this way, you can know the general content of the unit test by name .

2.2.2 Test content architecture

​ The content architecture of the test is mainly about these things :

  1. Test preparation . You may need to prepare some data before testing ,mock Some input parameters .
  2. perform . Execute the code that needs to be tested .
  3. verification . Verify that our logic is correct , What is mainly done here is a comparison between the expected return and the actual return after executing the code .

So the above two points , A good practice is this .

//  More detailed writing , What is being tested (Greeter),  What's the reference (elliot),  What is the expected result (hi elliot)
func Test_Greeter_when_param_is_elliot_get_hi_Elliot(t *testing.T) {
// Get ready
name := "elliot"
// perform
greet := Greeter(name)
// verification
assert.Equal(t, "hi elliot", greet)
} // Compare the ellipsis , What is being tested (Greeter), Participation is name, The expected result is a greeting msg,GreetMsg
func Test_Greeter_name_greetMsg(t *testing.T) {
// Get ready
name := "elliot"
// perform
greet := Greeter(name)
// verification
assert.Equal(t, "hi elliot", greet)
}

There's a catch , Try to avoid writing code for execution and verification together , For example, it is written like this :

assert.Equal(t, "hi elliot", Greeter("elliot"))

In fact, it has the same function , But it will affect the readability of the code . Not particularly recommended .

3. What is a good test

​ After talking about how to write a unit test , Let's talk about what tests are good tests . Personally, I think a good test should have the following three points :

  1. A reliable . First, the function of the unit test we write is to test the correctness of a certain logic , If our unit tests are not trustworthy , So how to ensure that the test object is trustworthy ? Sometimes some unit tests can be good or bad , For example, a unit test relies on a random number to do some logic , Then the random number itself is uncontrollable , Maybe the execution is good , The next execution will not pass .
  2. Maintainable . Business logic will continue to iterate , Then the unit tests will be iterated continuously , If it takes a lot of time to change unit tests every time , The maintainability of this unit test is poor . In fact, all logic is crammed into one function , Personally, I think the maintainability of unit tests corresponding to such code is relatively poor , All of them are piled together, which means that the unit test changes caused by each change need to take into account the overall impact . If you try to separate them , Unit tests can be changed as needed .
  3. Readability . Last but not least Is the readability of the unit test code , An incomprehensible unit test is no different from not writing it , Unable to understand the basic also means that it is untrustworthy and unmaintainable . I don't know how to trust you ? So it is very important to ensure the readability of the code .

​ In fact, after talking about some concepts, we still have no impression of how to write a test , Then we can learn from some bad case Go get started , After we know that those practices are bad , You will have a general understanding of good practice .

  1. Low readability test , That's right greeter Function unit test , In fact, this code is not well written , Low readability . Because for those who read this code , I don't even know this “hi elliot” What is it? , Why is he here . If you name it a little bit as a variable, it will be more readable .
//  Low readability , Because the reader doesn't know this “hi elliot” What is it? 
assert.Equal(t, "hi elliot", greet) // It will be better
expectedGreetMsg := "hi elliot"
assert.Equal(t, expectedGreetMsg, greet)
  1. Tests with logic . As a unit test , Try to avoid logic in it , If there is too much logic in it , Then it will evolve into code that needs to be tested , Because too much logic leads to more untrustworthy .
  2. Tests with error handling . Do not include error handling logic in unit tests , Because the unit test itself is used to find some errors in the program , If we just panic It's captured , Then I don't know where the code is wrong . In addition, for unit tests, errors should also be an expected result .
  3. Unrepeatable tests . This 《 The art of unit testing 》 This book provides an interesting example . Random numbers are used in the code to test , The random numbers generated each time are different , It means that the data of each test is different , This means that the test code is less reliable .
  4. Try to isolate unit tests . Try to make sure that the data between each unit test is prepared by yourself , Try not to share a set of things , Because doing so means that the success of one unit test is related to the success of another unit test , Uncontrollable things increase . for instance ,nutsdb Unit tests for have several global variables , Most of these unit tests are db Instances are shared , If the last unit test put db Shut down the , Or you can change some configurations and restart db, For the next unit test, he doesn't know what others have done , There may be unexpected mistakes when he executes .
  5. Each unit test should be as independent as possible . Try to run each unit test independently . And don't put it in order , Do not call another test in one test .

After talking about some good unit testing practices , We can improve a little . We might as well assume that there is such a scenario , It's actually testing a piece of logic , But there will be several test cases to test , So we need to write several test functions ? In fact, it's not necessary , That's all , Parametric testing , What does that mean ? Let's just give an example . Look at the following code .

func isLargerThanTen(num int) bool {
return num > 10
} func TestIsLargerThanTen_All(t *testing.T) {
var tests = []struct {
name string
num int
expected bool
}{
{
name: "test_larger_than_ten",
num: 11,
expected: true,
},
{
name: "test_less_than_ten",
num: 9,
expected: false,
},
{
name: "test_equal_than_ten",
num: 10,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := isLargerThanTen(test.num)
assert.Equal(t, test.expected, res)
})
}
}

​ The test here is to determine whether the input parameter is greater than 10 Function of , Then we naturally think of three test cases , Parameter is greater than the 10 Of , be equal to 10 Of , Less than 10 Of . But in fact, these three test cases are all testing a piece of logic , In fact, it is not necessary to write three functions . Therefore, the three test cases and the corresponding expected results are encapsulated , stay for The three test cases run in the loop . Personally, I think this is a better test method .

3. go Test tool recommendation

​ After talking about some of the above test methods , Here are some recommendations for go Inside the test tool . The most famous testify I have to recommend it . Many open source projects are using this library to build test cases . Speaking of this, it suddenly occurred to me that someone had given goleveldb Submit pr This library was introduced when the code was writing its own unit tests , I also “ To criticize ” He was , Changing the code is not the same thing as introducing a new library , Please do it separately hhhh, I'm sorry to think about it now . Return to the right topic , Let's briefly introduce some testify This library .

3.1 testify

​ testify This library mainly has three core contents ,assert, mock, suite.assert It's an assertion , You can encapsulate some judgments about equality , Whether there will be exceptions and so on . Limited article space , It's not right here assert Of api One by one , Interested friends can read the related articles of derivative reading . Here I will mainly introduce mock and suite modular .

3.1.1 mock

 We often need to prepare some data when we want to prepare the test ,mock Modules forge data by implementing interfaces . So you can use this when testing mock The object of is passed as a parameter . No more nonsense. Let's see how to simply practice .

First we define an interface :

//go:generate mockery --name=Man
type Man interface {
GetName() string
IsHandSomeBoy() bool
}

This interface defines a boy , One way is to get his name , The second way is to see if he is handsome . Here I also recommend go:generate By mockery( perform go get -u -v github.com/vektra/mockery/.../ install ) Command to generate the corresponding mock object ( The generated code will be placed in the current directory mocks Under the table of contents , Of course, you can also add parameters to the command to specify the generation path ), In this way, we don't need to realize mock Object . Let's take a look at the generated code .

// Code generated by mockery v2.10.0. DO NOT EDIT.

package mocks

import mock "github.com/stretchr/testify/mock"

// Man is an autogenerated mock type for the Man type
type Man struct {
mock.Mock
} // GetName provides a mock function with given fields:
func (_m *Man) GetName() string {
ret := _m.Called() var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
} return r0
} // IsHandSomeBoy provides a mock function with given fields:
func (_m *Man) IsHandSomeBoy() bool {
ret := _m.Called() var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
} return r0
}

So how do we use it ? Look at the following code :

func TestMan_All(t *testing.T) {
man := mocks.Man{}
// You can add the return corresponding to a method through this paragraph
man.On("GetName").Return("Elliot").On("IsHandSomeBoy").Return(true)
assert.Equal(t, "Elliot", man.GetName())
assert.Equal(t, true, man.IsHandSomeBoy())
}

3.1.2 suite

​ Sometimes we may not need to measure a single function , Are many methods of an object , For example, if you want to be right about leveldb Some of the main methods of testing , For example, simple reading and writing , Range queries , So if the unit test of each function is written as a function , Then it is possible that some things will be initialized repeatedly here , such as db. In fact, some states can be shared here , For example, after the data is written, the test can read the data , Or range query . In fact, it would be better to connect them in a closer way . that suite The suite came into being . I'm not going to go into details here , Interested readers can step by step derive from reading 《go One storehouse a day testify》. I understand that this article is quite clear . But here I can offer nutsdb A related test case for your reference :https://github.com/nutsdb/nutsdb/blob/master/bucket_meta_test.go If you are interested, you can also refer to this code .

4. summary

​ This article is mainly to summarize my recent thinking and precipitation on unit testing , And right go A rough explanation of the testing tools for . The source code of some open source projects used in this article , Mainly to share some of my own thoughts , I hope that's helpful .

Extended reading

  1. Best Practices for Testing in Go:https://fossa.com/blog/golang-best-practices-testing-go/#Catch
  2. 《 The art of unit testing 》
  3. go One storehouse a day testify:https://segmentfault.com/a/1190000040501767
  4. Use testify and mockery Libraries simplify unit testing :https://segmentfault.com/a/1190000016897506

How to write test cases and go Unit test tools testify A brief introduction to more related articles

  1. Coverage testing tool gcov Front end tools _LCOV_ Brief introduction

    1.Gcov It is a tool for coverage statistics of code running . It follows gcc Published together with , Its use is also very easy, You need to add... When compiling and linking -fprofile-arcs -ftest-coverage Generate binaries ...

  2. Automated build tools gulp Brief introduction and use

    One . Introduction and installation : gulp It's a tool to build code in the process of front-end development , It's a powerful tool for building automation projects : She can not only optimize the website resources , And in the development process, many repetitive tasks can be automatically completed with the right tools : Use her , Not only can we have a good time ...

  3. Java Learning notes 43( Print stream 、IO A brief introduction to the stream tool class )

    Print stream : There are two classes :PrintStream,PrintWriter class , The methods of the two classes are the same , The difference is the constructor PrintStream: Construction method : receive File type , Receive string file name , Receive byte output stream (Outpu ...

  4. First time to know Selenium as well as Selenium A brief introduction to common tools

    One . Why learn automated testing ? In the Internet industry, agile development takes small steps , Fast iteration , The regression test task in the test link is very complicated , Manual testing is easy to miss , Automated testing can improve testing efficiency and ensure product quality . Two . The hierarchical model of learning 1. Unit automation testing ...

  5. Jmeter( One ) A brief introduction to the tools (z)

    One .JMeter Introduce Apache JMeter yes 100% pure JAVA Desktop applications , Designed to test clients / Server side structure software ( for example web Applications ). It can be used to test the performance of static and dynamic resources , for example : Static files ,J ...

  6. Rust Project construction management tools in Cargo Brief introduction

    cargo yes Rust Built in project management tools . be used for Rust Project creation . compile . perform , Managing project dependencies at the same time , The third-party dependency library that I actively infer to use , Download and upgrade the version number . One . see cargo Version number install R ...

  7. Website stress testing tools Webbench Brief introduction

    Webbech Can test on the same hardware , The performance of different services and the health of the same service on different hardware .Webbench The standard test can show us two things about the server : The number of requests per second and the amount of data transmitted per second .Webbench Not only ...

  8. Java Concurrent Semaphore and Exchanger Brief introduction of tool class

    One .Semaphore Introduce Semaphore It means semaphore , It is used to control the number of threads accessing specific resources at the same time . It is also a shared lock in essence .Semaphore It can be used for flow control , Especially in the application scenarios with limited public resources . example ...

  9. Guava A brief introduction to open source tools

    Guava It's a Google Based on the java1.6 The extension project of the class library collection of , Include collections, caching, primitives support, concurrency libra ...

  10. iOS Performance testing tools instrunments Brief introduction

    1. Prerequisite stay appstore Download and install from xcode 2. Open mode 3. Introduction to page elements 3. Connect the computer to the mobile phone and select the one to be tested app 4. Select which to test , Double click in , Click to start the monitoring test 5. Mainly introduce three items First of all ...

Random recommendation

  1. sharepoint parts webparth Close the method of Retrieval

  2. Linux Simple operation of file system

    df: List the overall hard disk usage of the file system Display the capacity in a readable manner : [[email protected] ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/s ...

  3. Correct understanding SQL Server The license for ( turn )

    Today, I saw a discussion on how to use SQL Server As SEPM The backstage database of , How many do you need CAL The problem of :   If I do have to use SQL Server what type of li ...

  4. Linux Of IO Dispatch

    Linux Of IO Dispatch IO Scheduling takes place in Linux Kernel IO Scheduling layer . This level is aimed at Linux The whole of IO In terms of hierarchy . from read() perhaps write() From the system call point of view ,Linux whole IO The system can be divided into seven layers ...

  5. php The difference between single quotation mark and double quotation mark

    One . The difference between single quotation mark and double quotation mark 1." " The fields in double quotation marks are interpreted by the compiler , And then as HTML Code output . 2.' ' The words in single quotation marks are not explained , Direct output . It can be seen from the literal meaning that , Single quotation marks are better than double quotation marks ...

  6. Detailed explanation SSH The principles and advantages of the framework

    Struts Principles and advantages of .        Struts working principle   MVC namely Model-View-Controller Abbreviation , Is a common design pattern .MVC It weakens the coupling between business logic interface and data interface , And let ...

  7. 【 Answer key 】Luogu P4396 [AHOI2013] Homework

    Original title transmission gate The fastest solution seems to be cdq, But it's only the Mo team + Line segment tree / Tree array approach The title asks us to ask 1. In the interval [l,r] The middle range is [a,b] How many numbers are there in 2. In the interval [l,r] The middle range is [a,b] How many different numbers are there in At a glance ...

  8. 024_nginx And backlog pit

    One . I met one on the line nginx A setup for tuning , namely listen Set it later listen 80 backlog=1024; However, when multiple domain names are set with this value, the following prompt will appear and repeated errors will be reported . About backlog Parametric ...

  9. Tablet installation Ubuntu course

    Tablet installation Ubuntu course - With V975w For example ,Z3735 series CPU Universal Recently tried in Onda V975w Tablets and intel stick Install in ubuntu, Through the analysis of , There's a very big hole . But because of this pit , this ...

  10. handsontable problem

    There's a problem , Go to the official website to find community:http://docs.handsontable.com/0.16.1/tutorial-quick-start.html 1. describe : hold handson ta ...