Go语言:利用 TDD 驱动开发测试 学习结构体、方法和接口 | 您所在的位置:网站首页 › go语言开发app › Go语言:利用 TDD 驱动开发测试 学习结构体、方法和接口 |
表格驱动测试在我们要创建一系列相同测试方式的测试用例时很有用。
func TestArea(t *testing.T) {
areaTests := []struct {
shape Shape
want float64
}{
{Rectangle{12, 6}, 72.0},
{Circle{10}, 314.1592653589793},
}
for _, tt := range areaTests {
got := tt.shape.Area()
if got != tt.want {
t.Errorf("got %.2f want %.2f", got, tt.want)
}
}
}
这里唯一的新语法是创建了一个匿名的结构体。我们用含有两个域 shape 和 want 的 []struct 声明了一个结构体切片。
然后我们用测试用例去填充这个数组了。
我们可以像遍历任何其他切片一样来遍历这个数组,进而用这个结构体的域来做我们的测试。
你会看到开发人员能方便的引入一个新的几何形状,只需实现 Area 方法并把新的类型加到测试用例中。
另外发现 Area 方法有错误,我们可以在修复这个错误之前非常容易的添加新的测试用例。
列表驱动测试可以成为你工具箱中的得力武器。但是确保你在测试中真的需要使用它。
如果你要测试一个接口的不同实现,或者传入函数的数据有很多不同的测试需求,这个武器将非常给力。
让我们通过再添加一个三角形并测试它来演示所有这些技术。
先写测试函数
为我们的新类型添加测试用例非常容易,只需添加 "{Triangle{12,6},36.0}," 到我们的列表中去就行了。 func TestArea(t *testing.T) { areaTests := []struct { shape Shape want float64 }{ {Rectangle{12, 6}, 72.0}, {Circle{10}, 314.1592653589793}, {Triangle{12, 6}, 36.0}, } for _, tt := range areaTests { got := tt.shape.Area() if got != tt.want { t.Errorf("got %.2f want %.2f", got, tt.want) } } } 尝试运行测试函数记住,不断尝试运行这些测试函数并让编译器引导你找到正确的方案
为运行测试函数编写最少的代码并检查失败时的输出 ./shapes_test.go:25:4: undefined: Triangle 我们还没有定义 Triangle 类型: type Triangle struct { Base float64 Height float64 }再运行一次测试函数: ./shapes_test.go:25:8: cannot use Triangle literal (type Triangle) as type Shape in field value: Triangle does not implement Shape (missing Area method)
编译器告诉我们不能把 Triangle 当作一个类型因为它没有方法 Area()。所以我们添加一个空的实现让测试函数能工作 func (c Triangle) Area() float64 { return 0 } 最后代码编译通过。运行后得到如下错误: shapes_test.go:31: got 0.00 want 36.00 编写正确的代码让测试函数通过 func (c Triangle) Area() float64 { return (c.Base * c.Height) * 0.5 }最后测试通过了! 重构 虽然实现很好但我们的测试函数还能够改进。 注意如下代码: {Rectangle{12, 6}, 72.0}, {Circle{10}, 314.1592653589793}, {Triangle{12, 6}, 36.0}, 这些数字代表什么并不一目了然,我们应该让我们的测试函数更容易理解。 到目前为止我们仅仅学到一种创建结构体 MyStruct{val1, val2} 的方法,但是我们可以选择命名这些域。就像如下代码所示: {shape: Rectangle{Width: 12, Height: 6}, want: 72.0}, {shape: Circle{Radius: 10}, want: 314.1592653589793}, {shape: Triangle{Base: 12, Height: 6}, want: 36.0}, 在 Kent Beck 的这篇题为 测试驱动开发实例 的帖子中把测试用例重构成要点和断言: 当测试用例不是一系列操作,而是事实的断言时,测试才清晰明了。 现在我们的测试用例是关于几何图形的面积这些事实的断言了。 确保测试输出有效 记得当时我们实现三角形时的错误输出吗?它输出 shapes_test.go:31: got 0.00 want 36.00 我们知道它仅仅和三角形有关,但是如果在一个包含二十个测试用例的系统里出现类似的错误呢?开发人员如何知道是哪个测试用例失败了呢?这对于开发人员来说不是一个好的体验,他们需要手工检查所有的测试用例以定位到哪个用例失败了。 我们可以改进我们的错误输出为 "%#v got %.2f want %.2f. %#v",这样会打印结构体中域的值。这样开发人员能一眼看出被测试的属性。 关于列表驱动测试的最后一点提示是使用 t.Run。 在每个用例中使用 t.Run,测试用例的错误输出中会包含用例的名字: -------- FAIL: TestArea (0.00s) --- FAIL: TestArea/Rectangle (0.00s) shapes_test.go:33: main.Rectangle{Width:12, Height:6} got 72.00 want 72.10
我们可以通过如下命令来运行列表中指定的测试用例: go test -run TestArea/Rectangle
下面是满足要求的最终测试代码: func TestArea(t *testing.T) { areaTests := []struct { name string shape Shape hasArea float64 }{ {name: "Rectangle", shape: Rectangle{Width: 12, Height: 6}, hasArea: 72.0}, {name: "Circle", shape: Circle{Radius: 10}, hasArea: 314.1592653589793}, {name: "Triangle", shape: Triangle{Base: 12, Height: 6}, hasArea: 36.0}, } for _, tt := range areaTests { // using tt.name from the case to use it as the `t.Run` test name t.Run(tt.name, func(t *testing.T) { got := tt.shape.Area() if got != tt.hasArea { t.Errorf("%#v got %.2f want %.2f", tt.shape, got, tt.hasArea) } }) } } 总结这是进一步的 TDD 实践。我们在对一个基本数学问题的解决方案的迭代中,通过测试学习了语言的新特性。 声明结构体以创建我们自己的类型,让我们把数据集合在一起并达到简化代码的目地 声明接口,这样我们可以定义适合不同参数类型的函数(参数多态) 在自己的数据类型中添加方法以实现接口 列表驱动测试让断言更清晰,这样可以使测试文件更易于扩展和维护 这是重要的一课。因为我们开始定义自己的类型。在像 Go 这样的静态语言中,能定义自己的类型是开发易维护,低耦合,好测试的软件的基础。 接口是把负责从系统的其他部分隐藏起来的伟大工具。在我们的测试中,辅助函数的代码不需要知道具体的几何形状,只需要知道获取它的面积即可。 当你以后更熟悉 Go 后你会发现接口和标准库的真正威力。你会看到标准库中的随处可见的接口。通过在你自己的类型中实现这些接口你能很快的重用大量的伟大功能。 |
CopyRight 2018-2019 实验室设备网 版权所有 |