当前位置:网站首页>Go language unit test basics from getting started to giving up
Go language unit test basics from getting started to giving up
2022-06-21 21:48:00 【1024 Q】
Go Language tests
go test Tools
Unit test functions
Format
Unit test examples
go test -v
go test -run
regression testing
Skip some test cases
Subtest
Table driven testing
Introduce
Example
Parallel test
Using tools to generate test code
Test coverage
testify/assert
install
Examples of use
summary
Go Language testsThis is a Go Single test from getting started to giving up the tutorial series 0 piece , The main explanation is Go How to do unit test and table driven test are introduced 、 regression testing , It also introduces the common assertion tools .
go test ToolsGo Test dependencies in languages go test command . Write test code and write normal Go The code process is similar , There's no need to learn new grammar 、 Rules or tools .
go test A command is a driver of test code organized according to a certain convention . In the package directory , All with _test.go Source code files with suffix names are go test Part of the test , It won't be go build Compile into the final executable .
stay *_test.go There are three types of functions in the file , Unit test functions 、 Benchmark functions and sample functions .
| Test functions | The function name is prefixed with Test | Test whether some logical behaviors of the program are correct |
| Benchmark function | The function name is prefixed with Benchmark | Test function performance |
| Example functions | The function name is prefixed with Example | Provide sample documentation for documentation |
go test The command will traverse all *_test.go Functions in the file that conform to the above naming rules , And then generate a temporary main The package is used to call the corresponding test function , And then build and run 、 Report test results , Finally, clean up the temporary files generated in the test .
Each test function must import testing package , The basic format of the test function ( Signature ) as follows :
func TestName(t *testing.T){ // ...} The name of the test function must be Test start , The optional suffix must start with a capital letter , Take a few examples :
func TestAdd(t *testing.T){ ... }func TestSum(t *testing.T){ ... }func TestLog(t *testing.T){ ... } The parameter t Used to report test failures and additional log information .testing.T The way to have it is as follows :
func (c *T) Cleanup(func())func (c *T) Error(args ...interface{})func (c *T) Errorf(format string, args ...interface{})func (c *T) Fail()func (c *T) FailNow()func (c *T) Failed() boolfunc (c *T) Fatal(args ...interface{})func (c *T) Fatalf(format string, args ...interface{})func (c *T) Helper()func (c *T) Log(args ...interface{})func (c *T) Logf(format string, args ...interface{})func (c *T) Name() stringfunc (c *T) Skip(args ...interface{})func (c *T) SkipNow()func (c *T) Skipf(format string, args ...interface{})func (c *T) Skipped() boolfunc (c *T) TempDir() string Unit test examples It's like cells are the basic units that make up our bodies , A software program is also composed of many unit components . Unit components can be functions 、 Structure 、 Methods and anything the end user might depend on . In short, we need to make sure that these components work properly . Unit tests are programs that use various methods to test unit components , It compares the results with the expected output .
Next , We are base_demo The package defines a Split function , The specific implementation is as follows :
// base_demo/split.gopackage base_demoimport "strings"// Split Put the string s According to the given separator sep Split and return string slice func Split(s, sep string) (result []string) { i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+1:] i = strings.Index(s, sep) } result = append(result, s) return} In the current directory , We create a split_test.go Test files for , And define a test function as follows :
// split/split_test.gopackage splitimport ( "reflect" "testing")func TestSplit(t *testing.T) { // The test function name must be Test start , Must receive a *testing.T Type parameter got := Split("a:b:c", ":") // The output of the program want := []string{"a", "b", "c"} // Expected results if !reflect.DeepEqual(want, got) { // because slice It can't be more direct , With the help of the method in the reflection package t.Errorf("expected:%v, got:%v", want, got) // If the test fails, an error message will be output }} here split The files in this package are as follows :
* ls -ltotal 16-rw-r--r-- 1 liwenzhou staff 408 4 29 15:50 split.go-rw-r--r-- 1 liwenzhou staff 466 4 29 16:04 split_test.go Execute... In the current path go test command , You can see the output as follows :
go test -v* go test
PASS
ok golang-unit-test-demo/base_demo 0.005s
A test case is a little thin , Let's write another example to test the use of multiple characters to cut strings , stay split_test.go Add the following test function :
func TestSplitWithComplexSep(t *testing.T) { got := Split("abcd", "bc") want := []string{"a", "d"} if !reflect.DeepEqual(want, got) { t.Errorf("expected:%v, got:%v", want, got) }} Now we have multiple test cases , In order to better see the execution of each test case in the output results , We can go test Command addition -v Parameters , Let it output the complete test results .
* go test -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestSplitWithComplexSep
split_test.go:20: expected:[a d], got:[a cd]
--- FAIL: TestSplitWithComplexSep (0.00s)
FAIL
exit status 1
FAIL golang-unit-test-demo/base_demo 0.009s
From the output above, we can clearly see that TestSplitWithComplexSep This test case fails the test .
The results of unit tests show that split The implementation of the function is not reliable , No consideration is given to incoming sep When the parameter is multiple characters , Let's fix this Bug:
package base_demoimport "strings"// Split Put the string s According to the given separator sep Split and return string slice func Split(s, sep string) (result []string) { i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+len(sep):] // Use here len(sep) obtain sep The length of i = strings.Index(s, sep) } result = append(result, s) return} In execution go test Command can be added -run Parameters , It corresponds to a regular expression , Only the test function on the function name match will be go test Command execution .
For example, by giving go test add to -run=Sep Parameter to tell it that this test only runs TestSplitWithComplexSep This test case :
* go test -run=Sep -v=== RUN TestSplitWithComplexSep--- PASS: TestSplitWithComplexSep (0.00s)PASSok golang-unit-test-demo/base_demo 0.010sThe final test results show that we have successfully repaired the previous Bug.
regression testingIt is wrong and dangerous for us to only execute those failed test cases or newly introduced test cases after modifying the code , The correct approach should be to run all test cases completely , Make sure you don't introduce new problems by modifying the code .
* go test -v=== RUN TestSplit--- PASS: TestSplit (0.00s)=== RUN TestSplitWithComplexSep--- PASS: TestSplitWithComplexSep (0.00s)PASSok golang-unit-test-demo/base_demo 0.011sThe test results show that all our unit tests have passed .
From this example we can see , With unit tests, you can quickly perform regression tests after code changes , Greatly improve development efficiency and ensure code quality .
Skip some test casesTo save time, it is supported to skip some time-consuming test cases during unit testing .
func TestTimeConsuming(t *testing.T) { if testing.Short() { t.Skip("short The test case will be skipped in mode ") } ...} When executed go test -short Will not execute the above TestTimeConsuming The test case .
In the above example, we wrote a test function for each test data , Usually, multiple groups of test data are required in unit testing to ensure the test effect .Go1.7+ A new subtest is added in , Support the use of... In test functions t.Run Execute a set of test cases , This eliminates the need to define multiple test functions for different test data .
func TestXXX(t *testing.T){ t.Run("case1", func(t *testing.T){...}) t.Run("case2", func(t *testing.T){...}) t.Run("case3", func(t *testing.T){...})} Table driven testing Introduce Writing good tests is not easy , But in many cases , Table driven testing can cover many aspects : Each entry in the table is a complete test case , Contains inputs and expected results , Sometimes it also contains additional information such as test name , To make the test output easy to read .
Using table driven testing can easily maintain multiple test cases , Avoid copying and pasting frequently when writing unit tests .
The step of table driven testing is usually to define a test case table , Then traverse the table , And use t.Run Perform the necessary tests on each item .
Table driven testing is not a tool 、 Bags or anything else , It's just a way and perspective to write clearer tests .
ExampleThere are many examples of table driven tests in the official standard library , for example fmt The test code in the package :
var flagtests = []struct { in string out string}{ {"%a", "[%a]"}, {"%-a", "[%-a]"}, {"%+a", "[%+a]"}, {"%#a", "[%#a]"}, {"% a", "[% a]"}, {"%0a", "[%0a]"}, {"%1.2a", "[%1.2a]"}, {"%-1.2a", "[%-1.2a]"}, {"%+1.2a", "[%+1.2a]"}, {"%-+1.2a", "[%+-1.2a]"}, {"%-+1.2abc", "[%+-1.2a]bc"}, {"%-1.2abc", "[%-1.2a]bc"},}func TestFlagParser(t *testing.T) { var flagprinter flagPrinter for _, tt := range flagtests { t.Run(tt.in, func(t *testing.T) { s := Sprintf(tt.in, &flagprinter) if s != tt.out { t.Errorf("got %q, want %q", s, tt.out) } }) }}Usually tables are anonymous struct array slices , You can define a structure or declare a structure array using an existing structure .name Attributes are used to describe specific test cases .
Next, let's try to write our own table driven tests :
func TestSplitAll(t *testing.T) { // Define test forms // Here, several test cases are defined using anonymous structures // And set a name for each test case tests := []struct { name string input string sep string want []string }{ {"base case", "a:b:c", ":", []string{"a", "b", "c"}}, {"wrong sep", "a:b:c", ",", []string{"a:b:c"}}, {"more sep", "abcd", "bc", []string{"a", "d"}}, {"leading sep", " Sand river has sand and river ", " sand ", []string{"", " There are rivers ", " Another river "}}, } // Traverse the test case for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Use t.Run() Perform subtests got := Split(tt.input, tt.sep) if !reflect.DeepEqual(got, tt.want) { t.Errorf("expected:%#v, got:%#v", tt.want, got) } }) }} Execute at the terminal go test -v, The following test output results will be obtained :
Parallel test* go test -v
=== RUN TestSplit
--- PASS: TestSplit (0.00s)
=== RUN TestSplitWithComplexSep
--- PASS: TestSplitWithComplexSep (0.00s)
=== RUN TestSplitAll
=== RUN TestSplitAll/base_case
=== RUN TestSplitAll/wrong_sep
=== RUN TestSplitAll/more_sep
=== RUN TestSplitAll/leading_sep
--- PASS: TestSplitAll (0.00s)
--- PASS: TestSplitAll/base_case (0.00s)
--- PASS: TestSplitAll/wrong_sep (0.00s)
--- PASS: TestSplitAll/more_sep (0.00s)
--- PASS: TestSplitAll/leading_sep (0.00s)
PASS
ok golang-unit-test-demo/base_demo 0.010s
Many tests are usually defined in table driven tests case, stay Go It is easy to use its concurrency advantage to parallelize table driven tests in the language , You can see the following code example .
func TestSplitAll(t *testing.T) { t.Parallel() // take TLog Marked to be able to run in parallel with other tests // Define test forms // Here, several test cases are defined using anonymous structures // And set a name for each test case tests := []struct { name string input string sep string want []string }{ {"base case", "a:b:c", ":", []string{"a", "b", "c"}}, {"wrong sep", "a:b:c", ",", []string{"a:b:c"}}, {"more sep", "abcd", "bc", []string{"a", "d"}}, {"leading sep", " Sand river has sand and river ", " sand ", []string{"", " There are rivers ", " Another river "}}, } // Traverse the test case for _, tt := range tests { tt := tt // Notice the restatement here tt Variable ( Avoid multiple goroutine The same variable is used in ) t.Run(tt.name, func(t *testing.T) { // Use t.Run() Perform subtests t.Parallel() // Mark each test case as capable of running in parallel with each other got := Split(tt.input, tt.sep) if !reflect.DeepEqual(got, tt.want) { t.Errorf("expected:%#v, got:%#v", tt.want, got) } }) }} Using tools to generate test code There are many tools in the community to automatically generate table driven test functions , such as gotests etc. , Many editors such as Goland It also supports the rapid generation of test files . Here's a simple demonstration of gotests Use .
install
go get -u github.com/cweill/gotests/...perform
gotests -all -w split.go The command above means , by split.go All functions of the file generate test code to split_test.go file ( If this file exists in the directory in advance, it will not be generated ).
The generated test code is roughly as follows :
package base_demoimport ( "reflect" "testing")func TestSplit(t *testing.T) { type args struct { s string sep string } tests := []struct { name string args args wantResult []string }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if gotResult := Split(tt.args.s, tt.args.sep); !reflect.DeepEqual(gotResult, tt.wantResult) { t.Errorf("Split() = %v, want %v", gotResult, tt.wantResult) } }) }}The code format is similar to that above , Only need TODO Add our test logic to the location .
Test coverageTest coverage is the percentage of code covered by the test suite . Usually we use the coverage of statements , That is, the proportion of the total code that is run at least once in the test . Within the company, the test coverage is generally required to reach 80% about .
Go Provide built-in features to check your code coverage . We can use go test -cover To see the test coverage . for example :
* go test -coverPASScoverage: 100.0% of statementsok golang-unit-test-demo/base_demo 0.009sFrom the above results, we can see that our test cases cover 100% Code for .
Go There's an extra -coverprofile Parameters , It is used to output the coverage related record information to a file . for example :
* go test -cover -coverprofile=c.outPASScoverage: 100.0% of statementsok golang-unit-test-demo/base_demo 0.009s The above command will output coverage related information to... Under the current folder c.out In file .
* tree ..├── c.out├── split.go└── split_test.go Then we carry out go tool cover -html=c.out, Use cover Tools to process the generated record information , This command will open a local browser window and generate a HTML The report .

In the figure above, each statement block marked in green indicates that it is covered , The red color indicates that it is not covered .
testify/asserttestify It is very popular in a community Go Unit test kit , One of the most used functions is the assertion tool it provides ——testify/assert or testify/require.
go get github.com/stretchr/testify Examples of use When we write unit tests , It is often necessary to use assertions to validate test results , But because of Go The language does not provide assertions , So we will write a lot if...else... sentence . and testify/assert It provides us with many common assertion functions , And can output friendly 、 Easy to read error description information .
For example, we were in TestSplit In the test function reflect.DeepEqual To judge whether the expected results are consistent with the actual results .
t.Run(tt.name, func(t *testing.T) { // Use t.Run() Perform subtests got := Split(tt.input, tt.sep) if !reflect.DeepEqual(got, tt.want) { t.Errorf("expected:%#v, got:%#v", tt.want, got) }}) Use testify/assert Then the above judgment process can be simplified as follows :
t.Run(tt.name, func(t *testing.T) { // Use t.Run() Perform subtests got := Split(tt.input, tt.sep) assert.Equal(t, got, tt.want) // Use assert The provided assertion function }) When we have multiple assertion statements , You can also use assert := assert.New(t) Create a assert object , It has all the previous assertion methods , It just doesn't need to be passed in again Testing.T Parameters .
func TestSomething(t *testing.T) { assert := assert.New(t) // assert equality assert.Equal(123, 123, "they should be equal") // assert inequality assert.NotEqual(123, 456, "they should not be equal") // assert for nil (good for errors) assert.Nil(object) // assert for not nil (good when you expect something) if assert.NotNil(object) { // now we know that object isn't nil, we are safe to make // further assertions without causing any errors assert.Equal("Something", object.Value) }}testify/assert There are many assertion functions available , There is no way to enumerate them one by one , You can check the official documents to understand .
testify/require Have testify/assert All assertion functions , The only difference between them is ——testify/require The test will be terminated immediately in case of failure .
Besides ,testify The package also provides mock、http And other testing tools , Due to space limitations, I won't go into details here , Interested students can learn about it by themselves .
This paper introduces Go Basic usage of language unit testing , By providing Split Function to write a real case of unit test , It simulates the scenarios in the daily development process , Step by step, the table driven test is introduced in detail 、 Regression testing and common assertion tools testify/assert Use . In the next article , We will go further , Describe in detail how to use httptest and gock Tools for network testing , More about Go For the basic information of language unit testing, please pay attention to other relevant articles on the software development network !
边栏推荐
- Revenue and profit "ebb and flow", water drops turn in pain
- Data types in JS (basic)
- 在AD中安装元件和封装库
- tkinter绘制组件(29)——单选组控件
- Uibutton implements left text and right picture
- InteliJ-IDEA-高效技巧(二)
- 从随便到无聊到有病
- Tutorial on the implementation of smart contracts by solidity (4) -erc1155 contracts
- 杰理之配对成对耳后,想保持两个耳机都输出立体声【篇】
- Go语言单元测试基础从入门到放弃
猜你喜欢

测评 | 在生活中,你是一位什么样的人呢?

js中的for.....in函数

Yb5212a charging IC chip sop8

Tx9116 Synchronous Boost IC
![Jerry's problem of playing songs after opening four channel EQ [chapter]](/img/ef/16d630b44df9b1c700bf8cf6acf8b2.png)
Jerry's problem of playing songs after opening four channel EQ [chapter]

PowerPoint tutorial, how to organize slides into groups in PowerPoint?

Revenue and profit "ebb and flow", water drops turn in pain

基于接口划分VLAN:静态VLAN【未完善】
Go语言单元测试基础从入门到放弃

Product innovation - an innovative social app that returns to real life
随机推荐
15 iterator
潮流媒体Hypebeast拟曲线上市:作价5.3亿美元 拟第三季完成
solidity实现智能合约教程(4)-ERC1155合约
你真的了解二叉树吗?(上篇)
Revenue and profit "ebb and flow", water drops turn in pain
Mendeley installation, configuration and use
ARP协议及ARP攻击
Definition of unused processing methods when compiling C51 with keil 5
[yolov5] opencv450 loads onnx for reasoning GPU acceleration
Product innovation - an innovative social app that returns to real life
When Jerry made Bluetooth transmission, when he modified stereo to mono differential output, there was a jam sound at the receiving end [chapter]
英文论文要怎么查重?
Caricature scientifique | Vous pouvez apprendre l'EEG en regardant les images. Voulez - vous essayer?
Which small directions of cv/nlp are easy to publish papers?
What is gcamp6f? Calcium ion imaging technology.
Fs9935 high efficiency constant current limiting WLED drive IC
Operation of 2022 welder (Advanced) examination question bank simulation examination platform
ACM. Hj51 outputs the penultimate node ●
Go语言单元测试模拟服务请求和接口返回
How to write a proposal for an English paper?