单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。 测试的目的是确认目标代码在给定的场景下,有没有按照期望工作。 一个场景是正向路经测试,就是在正常执行的情况下,保证代码不产生错误的测试。 这种测试可以用来确认代码可以成功地向数据库中插入一条工作记录。
另外一些单元测试可能会测试负向路径的场景,保证代码不仅会产生错误,而且是预期的错误。 这种场景下的测试可能是对数据库进行查询时没有找到任何结果,或者对数据库做了无效的更新。 在这两种情况下,测试都要验证确实产生了错误,且产生的是预期的错误。 总之,不管如何调用或者执行代码,所写的代码行为都是可预期的。
基础测试(basic test)只使用一组参数和结果来测试一段代码。 表组测试(table test)也会测试一段代码,但是会使用多组参数和结果进行测试。 也可以使用一些方法来模仿(mock)测试代码需要使用到的外部资源,如数据库或者网络服务器 这有助于让测试在没有所需的外部资源可用的时候,模拟这些资源的行为使测试正常进行。
测试的输出是代码文档的一部分。测试的输出需使用完整易读的语句,来记录为什么需要这个测试,具体测试了什么,以及测试的结果是什么。
go test
工具用来运行测试。
测试文件总是以_test.go 作为文件名的结尾。
t.Fatal 方法不但报告这个单元测试已经失败,而且会向测试输出写一些消息,而后立刻停止这个测试函数的执行。
如果除了这个函数外还有其他没有执行的测试函数,会继续执行其他测试函数。
这个方法对应的格式化版本名为 t.Fatalf。
如果需要报告测试失败,但是并不想停止当前测试函数的执行,可以使用 t.Error 系列方法。
如果测试函数执行时没有调用过 t.Fatal 或者 t.Error 方法,就会认为测试通过了。
如果测试可以接受一组不同的输入并产生不同的输出的代码,那么应该使用表组测试的方法进行测试。 表组测试除了会有一组不同的输入值和期望结果之外,其余部分都很像基础单元测试。 测试会依次迭代不同的值,来运行要测试的代码。每次迭代的时候,都会检测返回的结果。 这便于在一个函数里测试不同的输入值和条件。表组测试是利用一个测试函数测试多组值的好办法。
模仿(mocking)是一个很常用的技术手段,用来在运行测试时模拟访问不可用的资源。
mock 服务器示例:chapter9/sample/unittest/test2_test.go
服务端点 (endpoint) 是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径。 如果在构造网络 API,你会希望直接测试自己的服务的所有服务端点,而不用启动整个网络服务。包 httptest 正好提供了做到这一点的机制。
mock 服务端点示例:chapter9/sample/handlers/handlers_test.go
包中的示例代码,既能用于测试,也能用于文档。
示例:chapter9/sample/handlers/handlers_example_test.go
示例基于已经存在的函数或者方法。我们需要使用 Example 作为函数名的开始。
对于示例代码,需要遵守一个规则。示例代码的函数名字必须基于已经存在的公开的函数或者方法。
写示例代码的目的是展示某个函数或者方法的特定使用方法。为了判断测试是成功还是失败,需要将程序最终的输出和示例函数底部列出的输出做比较。
Output:标记用来在文档中标记出示例函数运行后期望的输出。 Go 的测试框架知道如何比较注释里的期望输出和标准输出的最终输出。 如果两者匹配,这个示例作为测试就会通过, 并加入到包的 Go 文档里。 如果输出不匹配,这个示例作为测试就会失败。
示例也是测试的一部分,可以使用go test工具来运行示例函数。
基准测试是一种测试代码性能的方法。
示例:chapter9/sample/benchmark/benchmark1_test.go
go test 参数
-run="none" # 保证在运行指定的基准测试函数之前没有单元测试会被运行。也可以用来指定要运行的特定函数。
-bench="BenchmarkFormat" # 可以为具体的基准测试函数或者 "." 表示执行所有基准测试
-benchtime # 更改测试执行的最短时间
-benchmem # 提供每次操作分配内存的次数,以及总共分配内存的字节数,allocs/op 的值表示每次操作从堆上分配内存的次数,B/op 的值表示每次操作分配的字节数