Merging other languages
This commit is contained in:
312
en/11.3.md
312
en/11.3.md
@@ -1,149 +1,163 @@
|
||||
# 11.3 Go怎么写测试用例
|
||||
开发程序其中很重要的一点是测试,我们如何保证代码的质量,如何保证每个函数是可运行,运行结果是正确的,又如何保证写出来的代码性能是好的,我们知道单元测试的重点在于发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让线上的程序能够在高并发的情况下还能保持稳定。本小节将带着这一连串的问题来讲解Go语言中如何来实现单元测试和性能测试。
|
||||
|
||||
Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test`命令来实现单元测试和性能测试,`testing`框架和其他语言中的测试框架类似,你可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例,那么接下来让我们一一来看一下怎么写。
|
||||
|
||||
## 如何编写测试用例
|
||||
由于`go test`命令只能在一个相应的目录下执行所有文件,所以我们接下来新建一个项目目录`gotest`,这样我们所有的代码和测试代码都在这个目录下。
|
||||
|
||||
接下来我们在该目录下面创建两个文件:gotest.go和gotest_test.go
|
||||
|
||||
1. gotest.go:这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Division(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("除数不能为0")
|
||||
}
|
||||
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
2. gotest_test.go:这是我们的单元测试文件,但是记住下面的这些原则:
|
||||
|
||||
- 文件名必须是`_test.go`结尾的,这样在执行`go test`的时候才会执行到相应的代码
|
||||
- 你必须import `testing`这个包
|
||||
- 所有的测试用例函数必须是`Test`开头
|
||||
- 测试用例会按照源代码中写的顺序依次执行
|
||||
- 测试函数`TestXxx()`的参数是`testing.T`,我们可以使用该类型来记录错误或者是测试状态
|
||||
- 测试格式:`func TestXxx (t *testing.T)`,`Xxx`部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如`Testintdiv`是错误的函数名。
|
||||
- 函数中通过调用`testing.T`的`Error`, `Errorf`, `FailNow`, `Fatal`, `FatalIf`方法,说明测试不通过,调用`Log`方法用来记录测试的信息。
|
||||
|
||||
下面是我们的测试用例的代码:
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Division_1(t *testing.T) {
|
||||
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
|
||||
t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("第一个测试通过了") //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
t.Error("就是不通过")
|
||||
}
|
||||
|
||||
我们在项目目录下面执行`go test`,就会显示如下信息:
|
||||
|
||||
--- FAIL: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go:16: 就是不通过
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL gotest 0.013s
|
||||
从这个结果显示测试没有通过,因为在第二个测试函数中我们写死了测试不通过的代码`t.Error`,那么我们的第一个函数执行的情况怎么样呢?默认情况下执行`go test`是不会显示测试通过的信息的,我们需要带上参数`go test -v`,这样就会显示如下信息:
|
||||
|
||||
=== RUN Test_Division_1
|
||||
--- PASS: Test_Division_1 (0.00 seconds)
|
||||
gotest_test.go:11: 第一个测试通过了
|
||||
=== RUN Test_Division_2
|
||||
--- FAIL: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go:16: 就是不通过
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL gotest 0.012s
|
||||
上面的输出详细的展示了这个测试的过程,我们看到测试函数1`Test_Division_1`测试通过,而测试函数2`Test_Division_2`测试失败了,最后得出结论测试不通过。接下来我们把测试函数2修改成如下代码:
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
if _, e := Division(6, 0); e == nil { //try a unit test on function
|
||||
t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错
|
||||
} else {
|
||||
t.Log("one test passed.", e) //记录一些你期望记录的信息
|
||||
}
|
||||
}
|
||||
然后我们执行`go test -v`,就显示如下信息,测试通过了:
|
||||
|
||||
=== RUN Test_Division_1
|
||||
--- PASS: Test_Division_1 (0.00 seconds)
|
||||
gotest_test.go:11: 第一个测试通过了
|
||||
=== RUN Test_Division_2
|
||||
--- PASS: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go:20: one test passed. 除数不能为0
|
||||
PASS
|
||||
ok gotest 0.013s
|
||||
|
||||
## 如何编写压力测试
|
||||
压力测试用来检测函数(方法)的性能,和编写单元功能测试的方法类似,此处不再赘述,但需要注意以下几点:
|
||||
|
||||
- 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母
|
||||
|
||||
func BenchmarkXXX(b *testing.B) { ... }
|
||||
|
||||
- `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数
|
||||
- 在压力测试用例中,请记得在循环体内使用`testing.B.N`,以使测试可以正常的运行
|
||||
- 文件名也必须以`_test.go`结尾
|
||||
|
||||
下面我们新建一个压力测试文件webbench_test.go,代码如下所示:
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Division(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ { //use b.N for looping
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TimeConsumingFunction(b *testing.B) {
|
||||
b.StopTimer() //调用该函数停止压力测试的时间计数
|
||||
|
||||
//做一些初始化的工作,例如读取文件数据,数据库连接之类的,
|
||||
//这样这些时间不影响我们测试函数本身的性能
|
||||
|
||||
b.StartTimer() //重新开始时间
|
||||
for i := 0; i < b.N; i++ {
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
我们执行命令`go test -file webbench_test.go -test.bench=".*"`,可以看到如下结果:
|
||||
|
||||
PASS
|
||||
Benchmark_Division 500000000 7.76 ns/op
|
||||
Benchmark_TimeConsumingFunction 500000000 7.80 ns/op
|
||||
ok gotest 9.364s
|
||||
|
||||
上面的结果显示我们没有执行任何`TestXXX`的单元测试函数,显示的结果只执行了压力测试函数,第一条显示了`Benchmark_Division`执行了500000000次,每次的执行平均时间是7.76纳秒,第二条显示了`Benchmark_TimeConsumingFunction`执行了500000000,每次的平均执行时间是7.80纳秒。最后一条显示总共的执行时间。
|
||||
|
||||
## 小结
|
||||
通过上面对单元测试和压力测试的学习,我们可以看到`testing`包很轻量,编写单元测试和压力测试用例非常简单,配合内置的`go test`命令就可以非常方便的进行测试,这样在我们每次修改完代码,执行一下go test就可以简单的完成回归测试了。
|
||||
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一节: [使用GDB调试](<11.2.md>)
|
||||
* 下一节: [小结](<11.4.md>)
|
||||
# 11.3 Writing test cases
|
||||
|
||||
In the course of development, a very important step is to test our code to ensure its quality and integrity. We need to make sure that every function returns the expected result, and that our code performs optimally. We already know that the focus of unit tests is to find logical errors in the design or implementation of programs. They are used to detect and expose problems in code early on so that we can more easily fix them, before they get out of hand. We also know that performance tests are conducted for the purpose of optimizing our code so that it is stable under load, and can maintain a high level of concurrency. In this section, we'll take a look at some commonly asked questions about how unit and performance tests are implemented in Go.
|
||||
|
||||
The Go language comes with a lightweight testing framework called `testing`, and we can use the `go test` command to execute unit and performance tests. Go's `testing` framework works similarly to testing frameworks in other languages. You can develop all sorts of test suites with them, which may include tests for unit testes, benchmarking, stress tests, etc. Let's learn about testing in Go, step by step.
|
||||
|
||||
## How to write test cases
|
||||
|
||||
Since the `go test` command can only be executed in a directory containing all corresponding files, we are going to create a new project directory `gotest` so that all of our code and test code are in the same directory.
|
||||
|
||||
Let's go ahead and create two files in the directory called gotest.go and gotest_test.go
|
||||
|
||||
1. Gotest.go: This file declares our package name and has a function that performs a division operation:
|
||||
|
||||
<pre>package gotest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Division(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("Divisor can not be 0")
|
||||
}
|
||||
return a / b, nil
|
||||
}</pre>
|
||||
|
||||
2. Gotest_test.go: This is our unit test file. Keep in mind the following principles for test files:
|
||||
|
||||
- File names must end in `_test.go` so that `go test` can find and execute the appropriate code
|
||||
- You have to import the `testing` package
|
||||
- All test case functions begin with `Test`
|
||||
- Test cases follow the source code order
|
||||
- Test functions of the form `TestXxx()` take a `testing.T` argument; we can use this type to record errors or to get the testing status
|
||||
- In functions of the form `func TestXxx(t * testing.T)`, the `Xxx` section can be any alphanumeric combination, but the first letter cannot be a lowercase letter [az]. For example, `Testintdiv` would be an invalid function name.
|
||||
- By calling one of the `Error`, `Errorf`, `FailNow`, `Fatal` or `FatalIf` methods of `testing.T` on our testing functions, we can fail the test. In addition, we can call the `Log` method of `testing.T` to record the information in the error log.
|
||||
|
||||
Here is our test code:
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Division_1(t *testing.T) {
|
||||
// try a unit test on function
|
||||
if i, e := Division(6, 2); i != 3 || e != nil {
|
||||
// If it is not as expected, then the test has failed
|
||||
t.Error("division function tests do not pass ")
|
||||
} else {
|
||||
// record the expected information
|
||||
t.Log("first test passed ")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
t.Error("just does not pass")
|
||||
}
|
||||
|
||||
|
||||
When executing `go test` in the project directory, it will display the following information:
|
||||
|
||||
--- FAIL: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go: 16: is not passed
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL gotest 0.013s
|
||||
|
||||
We can see from this result that the second test function does not pass since we wrote in a dead-end using `t.Error`. But what about the performance of our first test function? By default, executing `go test` does not display test results. We need to supply the verbose argument `-v` like `go test -v` to display the following output:
|
||||
|
||||
=== RUN Test_Division_1
|
||||
--- PASS: Test_Division_1 (0.00 seconds)
|
||||
gotest_test.go: 11: first test passed
|
||||
=== RUN Test_Division_2
|
||||
--- FAIL: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go: 16: is not passed
|
||||
FAIL
|
||||
exit status 1
|
||||
FAIL gotest 0.012s
|
||||
|
||||
The above output shows in detail the results of our test. We see that the test function 1 `Test_Division_1` passes, and the test function 2 `Test_Division_2` fails, finally concluding that our test suite does not pass. Next, we modify the test function 2 with the following code:
|
||||
|
||||
func Test_Division_2(t *testing.T) {
|
||||
// try a unit test on function
|
||||
if _, e := Division(6, 0); e == nil {
|
||||
// If it is not as expected, then the error
|
||||
t.Error("Division did not work as expected.")
|
||||
} else {
|
||||
// record some of the information you expect to record
|
||||
t.Log("one test passed.", e)
|
||||
}
|
||||
}
|
||||
|
||||
We execute `go test-v` once again. The following information should now be displayed -the test suite has passed~:
|
||||
|
||||
=== RUN Test_Division_1
|
||||
--- PASS: Test_Division_1 (0.00 seconds)
|
||||
gotest_test.go: 11: first test passed
|
||||
=== RUN Test_Division_2
|
||||
--- PASS: Test_Division_2 (0.00 seconds)
|
||||
gotest_test.go: 20: one test passed. divisor can not be 0
|
||||
PASS
|
||||
ok gotest 0.013s
|
||||
|
||||
## How to write stress tests
|
||||
|
||||
Stress testing is used to detect function performance, and bears some resemblance to unit testing (which we will not get into here), however we need need to pay attention to the following points:
|
||||
|
||||
- Stress tests must follow the following format, where XXX can be any alphanumeric combination and its first letter cannot be a lowercase letter.
|
||||
|
||||
func BenchmarkXXX (b *testing.B){...}
|
||||
|
||||
- By default, `Go test` does not perform function stress tests. If you want to perform stress tests, you need to set the flag `-test.bench` with the format: `-test.bench="test_name_regex"`. For instance, to run all stress tests in your suite, you would run `go test -test.bench=".*"`.
|
||||
- In your stress tests, please remember to use testing.BN any loop bodies, so that the tests can be run properly.
|
||||
- As before, test file names must end in `_test.go`
|
||||
|
||||
Here we create a stress test file called webbench_test.go:
|
||||
|
||||
package gotest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Benchmark_Division(b *testing.B) {
|
||||
for i := 0; i < bN; i++ { // use bN for looping
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TimeConsumingFunction(b *testing.B) {
|
||||
b.StopTimer() // call the function to stop the stress test time count
|
||||
|
||||
// Do some initialization work, such as reading file data, database connections and the like,
|
||||
// So that our benchmarks reflect the performance of the function itself
|
||||
|
||||
b.StartTimer() // re-start time
|
||||
for i := 0; i < b.N; i++ {
|
||||
Division(4, 5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
We then execute the `go test -file webbench_test.go -test.bench =".*"` command, which outputs the following results:
|
||||
|
||||
PASS
|
||||
Benchmark_Division 500000000 7.76 ns/ op
|
||||
Benchmark_TimeConsumingFunction 500000000 7.80 ns/ op
|
||||
ok gotest 9.364s
|
||||
|
||||
The above results show that we did not perform any of our `TestXXX` unit test functions, and instead only performed our `BenchmarkXXX` tests (which is exactly as expected). The first `Benchmark_Division` test shows that our `Division()` function executed 500 million times, with an average execution time of 7.76ns. The second `Benchmark_TimeConsumingFunction` shows that our `TmeConsumingFunction` executed 500 million times, with an average execution time of 7.80ns. Finally, it outputs the total execution time of our test suite.
|
||||
|
||||
## Summary
|
||||
|
||||
From our brief encounter with unit and stress testing in Go, we can see that the `testing` package is very lightweight, yet packed with useful utilities. We saw that writing unit and stress tests can be very simple, and running them can be even easier with Go's built-in `go test` command. Every time we modify our code, we can simply run `go test` to begin regression testing.
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [Debugging using GDB](11.2.md)
|
||||
- Next section: [Summary](11.4.md)
|
||||
|
||||
Reference in New Issue
Block a user