文章目录
《Learn Go with tests》
完成该教程“迭代”章节的练习,理解TDD、重构、测试、基准测试等概念。自己选择一个算法如“快排”,模仿教程内容结构,写一个Go语言某算法实现TDD实践报告
写测试
- 创建包目录
github.com/user/iteration
- 在包目录下创建
repeat_test.go
,代码如下
package iteration
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected '%q' but got '%q'", expected, repeated)
}
}
- 在包目录下运行
go test
由于尚未创建包含Repeat函数的文件,测试结果为失败
补充代码使能通过测试
- 尝试让失败的测试跑起来,在包目录下创建
repeat.go
,代码如下
package iteration
func Repeat(character string) string {
return ""
}
- 再次在包目录下运行
go test
- 把代码补充完整,使这个函数具有重复字符 5 次的功能
func Repeat(character string) string {
var repeated string
for i := 0; i < 5; i++ {
repeated = repeated + character
}
return repeated
}
- 再次在包目录下运行
go test
成功通过测试
测试
Go拥有一个轻量级的测试框架,它由 go test 命令和 testing 包构成。
你可以通过创建一个名字以 _test.go 结尾的,包含名为 TestXXX 且签名为 func (t *testing.T) 函数的文件来编写测试。 测试框架会运行每一个这样的函数;若该函数调用了像 t.Error 或 t.Fail 这样表示失败的函数,此测试即表示失败。
重构
我理解为使用更加简化、高效、可重用的代码达到相同的功能。
例如if...else
,for ... range
改动,将可复用代码封装成函数,抽象出新的接口层等
下面为对repeat.go
的重构,引入另一个构造(construct)+=
赋值运算符。
const repeatCount = 5
func Repeat(character string) string {
var repeated string
for i := 0; i < repeatCount; i++ {
repeated += character
}
return repeated
}
基准测试
在Go语言中,提供了测试函数性能(CPU和Memory)的测试方法,基准测试。
基准测试主要用来测试CPU和内存的效率问题,来评估被测代码的性能。测试人员可以根据这些性能指标的反馈,来优化我们的代码,进而提高性能。
使用规则如下:
1.基准测试的代码文件必须以_test.go结尾。
2.基准测试的函数必须以Benchmark开头。
3.基准测试函数必须接受一个指向testing.B类型的指针作为唯一参数。
4.测试代码需要写在for循环中,并且循环中的最大之是b.N。
5.用go test -bench="."
来运行基准测试(windows下)
- 在包目录下创建
bench_test.go
,代码如下
package iteration
import "testing"
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
- 在包目录下运行
go test -bench="."
以上结果说明运行一次这个函数需要 128纳秒(在我的电脑上)。这挺不错的,为了测试它运行了 9173259 次。
TDD
TDD,也就是测试驱动开发(Test-Driven development),是一种“测试先行”的程序设计方法论,其基本流程围绕着测试->编码(重构)->测试的循环展开。
TDD三原则
- 除非为了通过一个单元测试,否则不允许编写任何产品代码。
- 在一个单元测试中只允许编写刚好能够导致失败的内容。
- 一次只能写通过一项单元测试的产品代码,不能多写。
根据三原则,TDD的开发过程描述如图:
练习
- 修改测试代码,以便调用者可以指定字符重复的次数,然后修复代码
- 首先修改测试代码
repeat_test.go
package iteration
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a",5)
expected := "aaaaa"
if repeated != expected {
t.Errorf("expected '%q' but got '%q'", expected, repeated)
}
}
- 在包目录下运行
go test
- 根据出错信息,相应地修改业务代码,使之通过测试
package iteration
func Repeat(character string,len int) string {
var repeated string
for i := 0; i < len; i++ {
repeated += character
}
return repeated
}
- 写一个 ExampleRepeat 来完善你的函数文档
写一个ExampleRepeat函数来展现Repeat函数的实际功能
示例将出现在 godoc 的文档中,这将使代码更容易理解
在repeat_test.go
中添加函数
func ExampleRepeat() {
repeated := Repeat("a", 8)
fmt.Println(repeated)
// Output: aaaaaaaa
}
运行go test -v
,结果如下
查看go document
中Example的具体效果
- 安装godoc
$env:GOPROXY = "https://goproxy.io"
go get -v golang.org/x/tools/cmd/godoc
- 启动Go Documentation Server
godoc -http=:6060
- 在浏览器中,打开url
http://localhost:6060 - godoc命令会去Go语言根目录和环境变量GOPATH包含的工作区目录中查找代码包。我们可以通过加入标记-goroot来制定一个Go语言根目录
godoc -goroot E:\Workplace\go
- 刷新浏览器
示例测试
- 函数名为func Example{要提供示例的函数名}
- 检测单行输出格式为“// Output: <期望字符串>”;
- 检测多行输出格式为“// Output: \ <期望字符串> \ <期望字符串>”,每个期望字符串占一行;
- 检测无序输出格式为"// Unordered output: \ <期望字符串> \ <期望字符串>",每个期望字符串占一行;
- 测试字符串时会自动忽略字符串前后的空白字符;
- 如果测试函数中没有“Output”标识,则该测试函数不会被执行;
- 执行测试可以使用go test,此时该目录下的其他测试文件也会一并执行;
- 执行测试可以使用go test <xxx_test.go>,此时仅执行特定文件中的测试函数;
- 看一下 strings 包。找到你认为可能有用的函数,并对它们编写一些测试。投入时间学习标准库会慢慢得到回报。
godoc:strings
选择函数:Count,Index
package main
import (
"fmt"
"strings"
)
func main() {
//Count
fmt.Println(strings.Count("apple", "p"))
fmt.Println(strings.Count("five", "")) // before & after each rune
//Index
fmt.Println(strings.Index("chicken", "ken"))
fmt.Println(strings.Index("chicken", "abc"))
}
TDD实践报告:选择排序
回顾TDD开发过程
写测试
- 创建包目录
github.com/user/selectsort
- 在包目录下创建
SelectSort_test.go
,代码如下
package selectsort
import (
"testing"
)
func TestSelectSort(t *testing.T) {
var flag bool
flag = true
arr := []int{6, 9, 2, 5, 3, 1, 4, 7, 0, 8}
sorted := SelectSort(arr)
expected := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
for i := 0; i < 10; i++ {
if sorted[i] != expected[i] {
flag = false
}
}
if !flag {
t.Errorf("expected %v but got %v", expected, sorted)
}
}
测试失败
写产品代码
用最少的代码先使测试跑起来
package selectsort
func SelectSort(data []int) []int{
return data
}
根据出错信息补充代码
package selectsort
func SelectSort(data []int) []int {
length := len(data)
for i := 0; i < length; i++ {
temp := data[i]
flag := i
for j := i + 1; j < length; j++ {
if data[j] < temp {
temp = data[j]
flag = j
}
}
if flag != i {
data[flag] = data[i]
data[i] = temp
}
}
return data
}
测试通过
重构
用下标索引flag指向数组中最小值即可,删除temp
package selectsort
func SelectSort(data []int) []int {
length := len(data)
for i := 0; i < length; i++ {
flag := i
for j := i + 1; j < length; j++ {
if data[j] < data[flag] {
flag = j
}
}
if flag != i {
var temp int
temp = data[flag]
data[flag] = data[i]
data[i] = temp
}
}
return data
}
小结
go test
单元测试——测试和验证代码的框架
对软件中的最小可测试单元进行检查和验证
- 准备一个 go 源码文件,在命名文件时需要让文件必须以
_test
结尾。 - 在命令行使用
go test
命令,自动测试源码包下的所有test文件
这里介绍几个常用的参数:
-bench regexp 执行相应的 benchmarks,例如 -bench=.;
-cover 开启测试覆盖率;
-run regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数;
-v 显示测试的详细命令。
- 每个测试用例函数需要以Test为前缀,例如:
func TestXXX( t *testing.T )
- 在 go test 后跟文件名,表示测试这个文件里的所有测试用例。
go test helloworld_test.go
- go test指定文件时默认执行文件内的所有测试用例。可以使用-run参数选择需要的测试用例单独执行,例如
go test -v -run TestA select_test.go
-单元测试框架提供的日志方法
Log 打印日志,同时结束测试
Logf 格式化打印日志,同时结束测试
Error 打印错误日志,同时结束测试
Errorf 格式化打印错误日志,同时结束测试
Fatal 打印致命日志,同时结束测试
Fatalf 格式化打印致命日志,同时结束测试
基准测试——获得代码内存占用和运行效率的性能数据
在Go语言中,提供了测试函数性能(CPU和Memory)的测试方法,基准测试。
基准测试主要用来测试CPU和内存的效率问题,来评估被测代码的性能。测试人员可以根据这些性能指标的反馈,来优化我们的代码,进而提高性能。
使用规则如下:
1.基准测试的代码文件必须以_test.go结尾。
2.基准测试的函数必须以Benchmark开头。
3.基准测试函数必须接受一个指向testing.B类型的指针作为唯一参数。
4.测试代码需要写在for循环中,并且循环中的最大之是b.N。
5.用go test -bench="."
来运行基准测试(windows下)
例子
go test -v -bench="." -benchtime=5s benchmark_test.go
- bench="."表示运行 benchmark_test.go 文件里的所有基准测试,和单元测试中的-run类似。
bench=“Alloc” 指定只测试 Benchmark_Alloc() 函数。 - benchtime 通过此参数可以自定义测试时间
- 在命令行中添加-benchmem参数以显示内存分配情况
基准测试中的计时器控制
// 重置计时器
b.ResetTimer()
// 停止计时器
b.StopTimer()
// 开始计时器
b.StartTimer()
```