This is a creation in Article, where the information may have evolved or changed.
Preface
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:
Through the previous article "Goconvey Framework Use Guide" learning, we are familiar with the basic use of the Goconvey framework, although it is possible to write simple and elegant test code, but if the measured function called the underlying operation function, such as the call to the OS package stat function, You need to pile the underlying operation function in the test function first. So, how to efficiently pile up a function?
This article introduces a lightweight gostub framework, interface-friendly, you can pile on global variables, functions or processes, let's experience it together.
Installation
At the command line, enter the command:
go get github.com/prashantv/gostub
You will find that in the $gopath/src/github.com directory, a new prashantv/gostub subdirectory is added, this is the protagonist of this article.
Usage Scenarios
The Gostub framework is used in a number of scenarios, in turn:
- Basic scenario: Piling for a global variable
- Basic scenario: Piling for a function
- Basic scenario: piling for a process
- Composite scenario: A combination of any of the same or different basic scenarios
Piling for a global variable
Assuming that num is a global integer variable used in the measured function, the current test case assumes that the value of NUM is greater than 100, for example, 150, the code for the piling is as follows:
stubs := Stub(&num, 150)defer stubs.Reset()
Stubs is the object returned by the function interface stub of the Gostub framework, which has a reset operation that restores the value of the global variable to its original value.
Piling for a function
Suppose we have the following function definitions in the existing code of our products:
func Exec(cmd string, args ...string) (string, error) { ...}
The EXEC function cannot be piling through the gostub frame.
To pile the EXEC function through the gostub framework, simply refactor the function declaration, define the EXEC function as an anonymous function, assign it to the exec variable, and refactor the code as follows:
var Exec = func(cmd string, args ...string) (string, error) { ...}
Description: For the new function, define the method as above
When the EXEC function is heavily composed of the exec variable, it does not affect the call to the EXEC function in the existing code. Since the exec variable is a function variable, we generally call this type of variable also a function.
Now we can pile the EXEC function, and the code looks like this:
stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) { return "xxx-vethName100-yyy", nil})defer stubs.Reset()
In fact, the GOSTUB framework specifically provides the STUBFUNC function for function piling, and we refactor the piling code:
stubs := StubFunc(&Exec,"xxx-vethName100-yyy", nil)defer stubs.Reset()
Many functions in the product code call Golang Library functions or third-party library functions, and we cannot refactor them, so how do we pile these library functions?
The answer is simple: Define a variable for the library function in the adaptation layer, and then use that variable in the product code.
Define variables for library functions:
package adaptervar Stat = os.Statvar Marshal = json.Marshalvar UnMarshal = json.Unmarshal...
Code to use Unmarshal:
bytes, err := adapter.Marshal(&student)if err != nil { ... return err}......
We can now pile on the library function. Assuming that the library function currently in use is Marshal, because the Marshal function has either success or failure, it has two pile functions, but for each test case Unmarshal has only one pile function.
When serialization succeeds, the piling code is:
var liLei = `{"name":"LiLei", "age":"21"}`stubs := StubFunc(&adapter.Marshal, []byte(liLei), nil)defer stubs.Reset()
The piling code when serialization fails is:
stubs := StubFunc(&adapter.Marshal, nil, ERR_ANY)defer stubs.Reset()
Piling for a process
When a function does not return a value, the function is generally called a procedure. Many times, we define the resource cleanup class function as a procedure.
Our piling code for the process DestroyResource is:
stubs := StubFunc(&DestroyResource)defer stubs.Reset()
Any combination of the same or different atomic scenes
Whether you call the stub function or the Stubfunc function, you generate a stubs object that still has the stub and Stubfunc methods, so you can pile multiple global variables, functions, or procedures in one test case at the same time. These global variables, functions, or procedures will have an initial value in a map and be rolled back in a deferred statement uniformly by the Reset method.
Assuming that SF is a call to a stub or Stubfunc function, SM is a call to the stub or Stubfunc method, then the piling code for using the GOSTUB framework in a test case is:
stubs := Sfdefer stubs.Reset()stubs.Sm1...stubs.SmN
It is not recommended to write the piling code in the following form:
stubs := Sfdefer stubs.Sm1.(...).SmN.Reset()
Testfuncdemo
The author in the previous article "Goconvey Framework Use Guide" recommended that readers use convey statement nesting, that is, a function has a test function, 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. In each convey function in the second level, a stubs object is created, independent of each other and unaffected by each other.
Let's look at a more complete test function for the Gostub framework demo:
Func Testfuncdemo (t *testing. T) {convey ("Testfuncdemo", T, func () {convey ("for Succ", func () {stubs: = Stub (&num, 150) Defer stubs. Reset () stubs. Stubfunc (&exec, "xxx-vethname100-yyy", nil) var Lilei = ' {' name ': ' Lilei ', ' age ': ' + '} ' stubs. Stubfunc (&adapter. Marshal, []byte (Lilei), nil) stubs. Stubfunc (&destroyresource)//several so asserts}) convey ("for fail when NUM is too small", Fu NC () {stubs: = Stub (&num, defer) stubs. Reset ()//several so asserts}) convey ("for fail when Exec error", func () {stubs: = St UB (&num, defer) stubs. Reset () stubs. Stubfunc (&exec, "", Err_any)//several so assert}) convey (' for fail when Marshal error ', fun C () {stubs: = Stub (&num, defer) stubs. Reset () stubs. Stubfunc (&exec, "xxx-Vethname100-yyy ", nil) stubs. Stubfunc (&adapter. Marshal, Nil, err_any)//several so assert})}
Not applicable to complex situations
Although the GOSTUB framework has been able to gracefully solve many of the scene of the function piling problem, but for some complex situation, but can only despair:
The database read operation function interface Readdb is called several times in the measured function, and the database is Key-value type. The measured function first READDB the value of a parent directory and then reads the values of several subdirectories in the For loop. In multiple test cases there is a need to pile READDB to render different behavior in multiple invocations, that is, the value of the parent directory is different from the value of the subdirectory, and the values of the subdirectories are not equal
The same underlying operation function is called multiple times in the measured function, such as exec.command, and the function parameters are both command and command parameters. The measured function first creates an object and then queries the state of the object, deleting the object when the object's state is not up to expectations. In multiple test cases there is a need to pile exec.command into different behaviors in multiple calls, that is, creating objects, querying object state, and deleting objects are not expected to return values.
The OS is called multiple times in the measured function. The Stat function to access files of multiple different directories. There will be an OS in multiple test cases. Stat Piling is a requirement to present different behaviors in multiple invocations, that is, the file information stored in one directory is expected to be user data, while the other file information is expected to be platform data
There is an important operation function in the measured function, and several retries are performed in case of failure. In the test case of testing retry, there is a requirement to pile the operation function into different behavior in multiple calls, that is, the first n-1 operation failed and the nth operation succeeded
...
Summary
The gostub is a lightweight test framework that is interface-friendly and can be used to pile global variables, functions, or processes. In this paper, the use of the Gostub framework is described in detail, and a more complete test function demo is given, hoping that the reader can master the basic use method of the gostub frame, improve the level of unit test and deliver the high quality software.
In view of the complex situation that the GOSTUB framework does not apply, the author has developed the framework two times, elegantly solves the problem, and we give the answer in the next article.