2. 变质的对象
测试驱动开发的总体流程如下:
- 写一个测试程序。
- 让测试程序运行。
- 编写合格的代码。
计划清单(to-do list)
当瑞士法郎与美元的兑换率为 2:1 的时候,5 美元 + 10 瑞士法郎 = 10 美元
将 "amount" 定义为私有
5 美元 * 2 = 10 美元Dollar 类有副作用吗?
钱数必须为整数?
假设对资金乘以 2 倍后再调用一次 dollar.times(3) 方法,我们希望的是以初始金额 5 美元乘以倍数 3,但程序是否会按照我们的意图工作呢?
假设代码如下:
func TestMultiplication(t *testing.T){
dollar := Dollar{5}
dollar.times(2)
got := dollar.amount
want := 10
assertEquals(t,got,want)
dollar.times(3)
got = dollar.amount
want = 15
assertEquals(t,got,want)
}
func assertEquals(t *testing.T, got,want int) {
if got != want {
t.Errorf("got %v want %v",got, want)
}
}
运行结果:
--- FAIL: TestMultiplication (0.00s)
multiplication_test.go:24: got 30 want 15
FAIL
exit status 1
FAIL v0.0.0 0.040s
当我们调用 dollar.times(3) 时,希望得到的是 15,但实际得到是 30,意味着再次的调用作用于了上一次调用的 amount。我们希望每次的调用作用于初始的 amount 之上。
?? 小步快跑
实现代码 func(d *Dollar) times(multiplier int)
中的接收者为 Dollar 类型指针,我们将接收者改为 Dollar 类型。
func(d Dollar) times(multiplier int) {
d.amount *= multiplier
}
先以最小的改动让代测试通过
--- FAIL: TestMultiplication (0.00s)
multiplication_test.go:24: got 5 want 10
multiplication_test.go:24: got 5 want 15
FAIL
exit status 1
FAIL v0.0.0 0.023s
?? 可惜的是,改动并未让测试通过,此时我们有两个选择,将修改的代码恢复为最后一次修改前的状态,或者继续根据现有运行结果查找问题原因并修改。我选择了第二种。
从运行结果看,我们两次调用 dollar.times() 方法,但得到的结果是 5,而不是我们期望的 10 或 15。因为没有结果接收方,所以调用 dollar.times() 方法后产生的结果消失了。
添加变量 amount 接收调用 dollar.times() 方法产生的结果
func TestMultiplication(t *testing.T){
dollar := Dollar{5}
amount := dollar.times(2)
got := amount
want := 10
assertEquals(t,got,want)
dollar.times(3)
got = dollar.amount
want = 15
assertEquals(t,got,want)
}
编译未通过,提示 dollar.times() 方法被用作了值,但实际上方法并未返回值。
# v0.0.0 [v0.0.0.test]
.\multiplication_test.go:7:24: dollar.times(2) used as value
FAIL v0.0.0 [build failed]
代码中添加返回值
func(d Dollar) times(multiplier int) int {
return d.amount * multiplier
}
运行测试通过,但代码中有明显重复代码,需要去除。
?? 代码重构
这里引入"表驱动测试",将测试的输入和期望的结果写在一起组成一个数据表,表中的每条记录都是一个含有输入和期望值的完整测试用例。
dollar := Dollar{5}
amountTests := []struct {
multiplier int
want int
}{
{multiplier: 2, want: 10},
{multiplier: 3, want: 15},
}
再次运行测试,测试通过。完成后完整代码如下:
测试代码
func TestMultiplication(t *testing.T) {
dollar := Dollar{5}
amountTests := []struct {
multiplier int
want int
}{
{multiplier: 2, want: 10},
{multiplier: 3, want: 15},
}
for _, tt := range amountTests {
got := dollar.times(tt.multiplier)
if got != tt.want {
t.Errorf("got %v want %v", got, tt.want)
}
}
}
实现代码
type Dollar struct {
amount int
}
func(d Dollar) times(multiplier int) int {
return d.amount * multiplier
}