This is a creation in Article, where the information may have evolved or changed.
Preface
In software development, the correctness of the product code is ensured by testing the code, and the correctness of the test code is guaranteed. The answer is undisputed and certainly the programmer himself. This requires that the test code must be simple and expressive enough to hide the error everywhere. We need to have a good nose to sniff out the bad taste of the test and to do the test refactoring in time to make the test code easy to maintain. I learned from a lot of coding practice: Although the programmer can write good product code is very cow, but can write good test code of the programmer more bull, especially for TDD practice.
To write good test code, you must be proficient in the relevant testing framework. For Golang programmers, there are at least three test frameworks that need to be mastered:
The author will explain these three test frameworks through a number of articles, at the same time, for the gostub framework will also carry out two development practices, in order to effectively solve more complex scenarios of piling problems.
This article will mainly introduce the basic use of Goconvey framework, so as to guide the reader to better test practice, and finally write a simple and elegant test code.
Note: The practice in this article is done under Mac OS X, and the process may be slightly different from the system in which the reader is located.
Goconvey Introduction
Goconvey is a test framework that is similar to the C + + language Gtest, a test framework for Golang that manages and runs test cases, provides rich assertion functions, and supports many Web interface features.
Although Golang has its own unit-testing capabilities and many third-party testing frameworks have emerged before the Goconvey framework was born, none of the test frameworks, like Goconvey, allow programmers to write test code in such a concise and elegant way.
Installation
At the command line, enter the command:
sudo go get github.com/smartystreets/goconvey
You will find:
- The github.com subdirectory is added under the $GOPATH/SRC directory, and the subdirectory contains the library code for the Goconvey framework.
- Subdirectory Go is generated under the/usr/local directory, and there are three subdirectories in the Go directory, namely bin,pkg and SRC.
We will copy the/usr/local/go/bin to $gopath:
cp -r bin $GOPATH
Basic Use Method
We introduce a case study of the basic use of the Goconvey framework and summarize the main points.
Product Code
We implement a function stringsliceequal that determines whether two string slices are equal, and the main logic includes:
- Returns False when two string slices are not of equal length
- Two string slices one is nil, the other is nil, and returns false
- Traverse two slices, compare the two slice element values of the corresponding index, and return false if not equal
- Otherwise, returns true
According to the above logic, the code implementation is as follows:
func StringSliceEqual(a, b []string) bool { if len(a) != len(b) { return false } if (a == nil) != (b == nil) { return false } for i, v := range a { if v != b[i] { return false } } return true}
For the logic "two string slices one is nil, the other is not nil, return false" the implementation of the code is a bit difficult to understand:
if (a == nil) != (b == nil) { return false}
We instantiate A and B, that is, []string{} and []string (nil), at which point the length of the two string slices is 0, but certainly not equal.
Test code
Write a normal test case first, as follows:
import ( "testing" . "github.com/smartystreets/goconvey/convey")func TestStringSliceEqual(t *testing.T) { Convey("TestStringSliceEqual should return true when a != nil && b != nil", t, func() { a := []string{"hello", "goconvey"} b := []string{"hello", "goconvey"} So(StringSliceEqual(a, b), ShouldBeTrue) })}
Because the Goconvey framework is compatible with Golang native unit tests, you can use go test-v to run tests.
Open the command line, go to the $gopath/src/infra/alg directory, run go test-v, then the test case execution Results Kusakabe:
=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil 1 total assertion--- PASS: TestStringSliceEqual (0.00s)PASSok infra/alg 0.006s
The above test case code has the following points:
- When you import the Goconvey package, add the number "." To reduce the redundant code. Generally in the test code to see convey and so two methods, must be convey package, do not define the same function name in the product code
- The name of the test function must begin with test, and the parameter type must be *testing. T
- Each test case must be wrapped with the convey function, whose first argument is a test description of type string, and the second parameter is the parameter of the test function (type *testing. T), the third parameter is a function that does not receive any parameters and returns no value (used to use closures)
- The implementation of the third parameter closure of the convey function through the so function to complete the assertion judgment, its first parameter is the actual value, the second argument is the assertion function variable, The third parameter is either not (when the second argument is a function variable in the form of a class shouldbetrue) or has (when the second function is a function variable of the class Shouldequal form)
We deliberately changed the test case to:
import ( "testing" . "github.com/smartystreets/goconvey/convey")func TestStringSliceEqual(t *testing.T) { Convey("TestStringSliceEqual should return true when a != nil && b != nil", t, func() { a := []string{"hello", "goconvey"} b := []string{"hello", "goconvey"} So(StringSliceEqual(a, b), ShouldBeFalse) })}
The result of the test case execution Kusakabe:
=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil ✘Failures: * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go Line 45: Expected: false Actual: true1 total assertion--- FAIL: TestStringSliceEqual (0.00s)FAILexit status 1FAIL infra/alg 0.006s
Let's add 3 more test cases:
import ("testing". "Github.com/smartystreets/goconvey/convey") func teststringsliceequal (t *testing. T) {convey ("teststringsliceequal should return True when a! = Nil && b! = nil", T, func () {A: = []str ing{"Hello", "Goconvey"} B: = []string{"Hello", "Goconvey"} So (Stringsliceequal (A, B), shouldbetrue)}) Convey ("Teststringsliceequal should return true when a = = Nil && b = = nil", T, func () {SO (Stringsliceeq UAL (nil, nil), shouldbetrue)}) convey ("Teststringsliceequal should return false when a = = Nil && b! = nil" , T, func () {A: = []string (nil) b: = []string{} SO (Stringsliceequal (A, B), Shouldbefalse)}) C Onvey ("Teststringsliceequal should return False when a! = Nil && B = nil", T, func () {A: = []string{"hel Lo "," World "} B: = []string{" Hello "," Goconvey "} So (Stringsliceequal (A, B), Shouldbefalse)})}
As can be seen from the test code above, each convey statement corresponds to a test case, then multiple test cases of a function can be rendered by multiple convey statements of a test function.
The result of the test case execution is as follows:
=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil 1 total assertion TestStringSliceEqual should return true when a == nil && b == nil 2 total assertions TestStringSliceEqual should return false when a == nil && b != nil 3 total assertions TestStringSliceEqual should return false when a != nil && b != nil 4 total assertions--- PASS: TestStringSliceEqual (0.00s)PASSok infra/alg 0.006s
Nesting of convey statements
Convey statements can be nested indefinitely to reflect the relationship between test cases. It is important to note that only the outermost convey need to pass in the *testing. Variable t of type T.
We write the previous test case in a nested way to another version:
Import ("Testing". "Github.com/smartystreets/goconvey/convey") func teststringsliceequal (t *testing. T) {convey ("teststringsliceequal", T, func () {convey ("should return True when a! = Nil && b! = nil", Func () {A: = []string{"Hello", "Goconvey"} B: = []string{"Hello", "Goconvey"} SO (StringS Liceequal (A, B), shouldbetrue)}) convey ("should return true when a = = Nil && B = nil", func () { So (stringsliceequal (nil, nil), shouldbetrue)}) convey ("should return false when a = = Nil &&A mp b! = nil ", func () {A: = []string (nil) b: = []string{} SO (Stringsliceequal (A, b), shouldb Efalse)}) convey ("should return FALSE when a! = Nil && b! = nil", func () {A: = []strin g{"Hello", "World"} B: = []string{"Hello", "Goconvey"} So (Stringsliceequal (A, B), Shouldbefalse) }) })}
The result of the test case execution is as follows:
=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil should return true when a == nil && b == nil should return false when a == nil && b != nil should return false when a != nil && b != nil 4 total assertions--- PASS: TestStringSliceEqual (0.00s)PASSok infra/alg 0.006s
It can be seen that the test logs nested in the convey statement differ from the display of the test logs that are not nested in the convey statement, and I prefer this form that is displayed in multiple test cases in the test function unit.
Web interface
Goconvey not only supports automated compilation testing at the command line, but also enables automated compilation testing in the Web interface. To use Goconvey Web interface features, you need to execute Goconvey in the directory where the test files are located:
$GOPATH/bin/goconvey
A page pops up, as shown in:
Goconvey-web.png
In the Web interface:
- You can set the interface theme
- View the complete test results
- Use browser reminders and other useful features
- Automatically detects code changes and compiles tests
- Semi-automated writing test cases
- View Test Coverage
- Temporarily masking a package's build test
Skip
For some assertion actions that you want to ignore but do not want to delete or comment out, Goconvey provides the convey/so skip method:
- The Skipconvey function indicates that the corresponding closure function will not be executed
- The SKIPSO function indicates that the corresponding assertion will not be executed
When Skipconvey or SKIPSO are present, the "skipped" notation is explicitly marked in the test log:
- When Skipconvey is present in the test code, the corresponding closure function, regardless of whether it is SKIPSO, is ignored, and the corresponding symbol in the test log is only one ""
- When SKIPSO is present in the test code convey statement, each so in the test log corresponds to a "" or "✘", and each SKIPSO corresponds to "", arranged in the actual order
- There is a string "{n} total assertions (one or more sections skipped)" In the test log, regardless of whether there is Skipconvey or SKIPSO, where {n} represents the number of assert statements actually running in the test
Customizing assertion functions
Let's take a look at the function prototypes of so:
func So(actual interface{}, assert assertion, expected ...interface{})
The second parameter is assertion, and its prototype is:
type assertion func(actual interface{}, expected ...interface{}) string
When the return value of assertion is "", the assertion succeeds, otherwise it fails, and the relevant code in the Goconvey framework is:
const ( success = "" needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0).")
We simply implement a assertion function:
func ShouldSummerBeComming(actual interface{}, expected ...interface{}) string { if actual == "summer" && expected[0] == "comming" { return "" } else { return "summer is not comming!" }}
We still write a simple test in the Slice_test file:
func TestSummer(t *testing.T) { Convey("TestSummer", t, func() { So("summer", ShouldSummerBeComming, "comming") So("winter", ShouldSummerBeComming, "comming") })}
Based on the implementation of Shouldsummerbecomming, the first so in the convey statement will assert success, and the second so will assert failure.
We run tests to see the results of the execution and meet expectations:
=== RUN TestSummer TestSummer ✘Failures: * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go Line 52: summer is not comming!2 total assertions--- FAIL: TestSummer (0.00s)FAILexit status 1FAIL infra/alg 0.006s
Summary
Although Golang has its own unit testing capabilities, I recommend that you use a proven third-party testing framework. This article mainly introduces the Goconvey framework, through the text combined with code examples to explain the basic use method, the main points are summarized as follows:
- Import Goconvey package, preceded by a dot "." To reduce redundant code
- The name of the test function must begin with test, and the parameter type must be *testing. T
- Each test case must be wrapped using the convey function, it is recommended to use the nesting of the convey statement, that is, a function has a test function, a test function nested two level convey statement, the first level convey statement corresponding to the test function, the second level convey statement corresponding to the test case
- The third parameter of the convey statement is used as a closure, and the assertion is completed through the so statement in the closure
- Use the Web interface features of the Goconvey framework as a complement to the command line
- Use the Skipconvey function or the SKIPSO function in the appropriate scenario
- You can customize the assertion function when you need it in a test
At this point, I hope the reader has mastered the basic use of the Goconvey framework to write simple and elegant test code.
However, things are not so simple! Imagine how we would write the test code if the underlying synchronous operation, such as the EXEC function of the OS package, was called multiple times in the function.
In fact, the answer is not complicated, we will announce it in the next article.