Go 应用测试
测试的覆盖率
命令:
go test ./ -v -cover
在《
Go Web
编程》一书中,有以下结论:
这并不是绝对的,测试文件可以在不同的包,进行测试也是不会出现问题的。
但是这样的说法引起了我的兴趣。
果然,执行测试的时候添加参数
-cover
的时候,如果不在同一个包,将会输出以下内容:
coverage: [no statements]
是没有办法得到,相关测试代码覆盖率的数据的。
如果测试文件和被测试文件处于同一包下,才可以得到测试代码覆盖率相关数据的输出。
coverage: 95.0% of statements
并行测试
命令:
go test ./ -v -cover -parallel 3
-parallel 3
表示最多希望
3
个测试并行执行。
并发测试,利用多核优势,使用
Parallel
方法的函数必须
> 1
,否则无法使用并发优势只有一个测试函数使用
Parallel
是没有效果的。
可通过运行以下代码,并对比测试时间即可验证结论:
package mainimport ("testing""time")func TestParallel_1(t *testing.T) {t.Parallel()time.Sleep(1 * time.Second)}func TestParallel_2(t *testing.T) {t.Parallel()time.Sleep(2 * time.Second)}func TestParallel_3(t *testing.T) {t.Parallel()time.Sleep(3 * time.Second)}
基准测试
命令:
go test -v -cover -bench="BenchmarkQueryUser" -run x
上面的命令既运行了基准测试,也运行了功能测试。如果需要,用户也可以通过运行标志
-run
来忽略功能测试。
-run
标志用于指定需要被执行的功能测试用例,如果用户把一个不存在的功能测试名字用作
-run
标志的参数,那么所有功能测试都将被忽略。
上面的命令中使用
-run x
,如果测试中不存在任何名字为
x
的功能测试用例,因此所有功能测试都不会被运行。
基准测试函数格式为:
func BenchmarkXX×(*testing. B){ ... }
举例:
//基准测试func BenchmarkQueryUser(b *testing.B) {for i := 0; i < b.N; i++ {user,err := userinfo.QueryUserById(1)if err != nil{b.Fatal("测试不通过,出现异常,err :",err)}else {fmt.Println("查询到数据:Result User=",*user)}}}
输出如下:
BenchmarkQueryUser-8 4711 282508 ns/opPASScoverage: 0.0% of statementsok Go_test/src/unitTest 1.416s
注意
for
循环里的
b.N
表示循环块内的语句将执行
b.N
次。
在进行基准测试时,测试用例的迭代次数是由Go自行决定的,虽然用户可以通过限制基准测试的运行时间达到限制迭代次数的目
的,但用户是无法直接指定迭代次数的——测试程序将进行足够多次的迭代,直到获得一个准确的测量值为止。
HTTP 测试
Go
不经为单元测试提供了包,还为
Go Web
应用提供了专有的包–
testing-httptest
包。
对
Go Web
应用的单元测试可以通过
testing/httptest
包来完成。这个包提供了模拟一个
Web
服务器所需的设施,用户可以利用
net/http
包中的客户端函数向这个服务器发送
HTTP
请求,然后获取模拟服务器返回的
HTTP
响应。
例子:
package mainimport ("encoding/json""net/http""net/http/httptest""strings""testing")func TestHandleGet(t *testing.T) {mux := http.NewServeMux() //创建一个多路复用器,用于运行测试mux.HandleFunc("/post/", handleRequest) //绑定需要测试的处理器writer := httptest.NewRecorder() //创建记录器,用于获取HTTP响应request, _ := http.NewRequest("GET", "/post/1", nil) //为被测试的处理器创建相应的请求mux.ServeHTTP(writer, request) //发送测试请求//对请求返回的响应结果进行检查处理if writer.Code != 200 {t.Errorf("Response code is %v", writer.Code)}var post Postjson.Unmarshal(writer.Body.Bytes(), &post)if post.Id != 1 {t.Errorf("Cannot retrieve JSON post")}}//与上面的测试方法同理func TestHandlePut(t *testing.T) {mux := http.NewServeMux()mux.HandleFunc("/post/", handleRequest)writer := httptest.NewRecorder()json := strings.NewReader(`{"content":"Updated post","author":"Sau Sheong"}`)request, _ := http.NewRequest("PUT", "/post/1", json)mux.ServeHTTP(writer, request)if writer.Code != 200 {t.Errorf("Response code is %v", writer.Code)}}
以上例子中,每个测试用例都会独立运行并启动各自独立的用于测试的
Web
服务器。
程序需要创建一个多路复用器并将
handleRequest
处理器与其进行绑定。除此之外,为了获取服务器返回的
HTTP
响应,程序使用
httptest.New Recorder
函数创建了一个
ResponseRecorder
结构,这个结构可以把响应存储起来以便进行后续的检查。
Go 的
testing
包允许用户通过
TestMain
函数,在进行测试时执行相应的预设(
setup
)操作或者拆卸(
teardown
)操作。一个典型的
TestMain
函数看上去是下面这个样子的:
func TestMain (m *testing.M){setUp ()code := m. Run ( )tearDown ()os.Exit (code)}
setUp
函数和
tearDown
函数分别定义了测试在预设阶段以及拆卸阶段需要执行的工作。需要注意的是,
setUp
函数和
tearDown
函数是为所有测试用例设置的,它们在整个测试过程中只会被执行一次,其中
setup
函数会在所有测试用例被执行之前执行,而
tearDown
函数则会在所有测试用例都被执行完毕之后执行。至于测试程序中的各个测试用例,则由
testing.M
结构的
Run
方法负责调用,该方法在执行之后将返回一个退出码(
exit code
),用户可以把这个退出码传递给
os.Exit
函数。
测试替身、依赖注入
测试替身(
test double
)是一种能够让单元测试用例变得更为独立的常用方法。当测试不方便使用实际的对象、结构或者函数时,我们就可以使用测试替身来模拟它们。因为测试替身能够提高被测试代码的独立性,所以自动单元测试环境经常会使用这种技术。
实现测试替身的一种设计方法是使用依赖注入(
dependency injection
)设计模式。这种模式迪过向被调用的对象、结构或者函数传人依赖关系,然后由依赖关系代替被调用者执行实际的操作,以此来解耦软件中的两个或多个层(
layer
),而在
Go
语言当中,(被传入的依赖关系通常会是一种接口类型。接下来,就让我们来看看,如何在第7章介绍的简单
Web
服务中使用依赖注入设计模式。
具体看图内的解耦过程: