针对行为测试, 而非实现

本文改编自TotT中的一掌.你可以从这下载打印版本贴在你的办公室里:)

你可能的开源项目也会会有一个有很多用户使用的可靠的Calculator类:

public class Calculator {
public int add(int a, int b) {
return a + b;
}
}

你也会写个测试来确保它是正常的:

[java]
public void testAdd() {
assertEquals(3, calculator.add(2, 1));
assertEquals(2, calculator.add(2, 0));
assertEquals(1, calculator.add(2, -1));
}
[/java]

但是一个有特点的库往往会使用一些高级的编码方法来代替简单的加法运算符, 你可以把你的代码改成这样:

[java]
public class Calculator {
private AdderFactory adderFactory;
public Calculator(AdderFactor adderFactory) {
this.adderFactory = adderFactory;
}
public int add(int a, int b) {
Adder adder = adderFactory.createAdder();
ReturnValue returnValue = adder.compute(new Number(a),
new Number(b));
return returnValue.convertToInteger();
}
}
[/java]

改代码很容易,但是你的测试要怎么改变呢?
答案就是: 现有的测试不需要做任何改变. 因为你所改变的仅仅是代码的实现方式,面向用户的行为没有发生任何变化.在大多数哦情况下, 测试应该专注于测试你的代码的public API, 代码实现的细节不应该暴露在测试中.

测试与具体实现分离, 意味着测试不需要随着具体实现的修改而修改, 这更易于维护. 同时, 测试就如同代码样例, 帮助人们了解这个类的各种使用方法, 即使一个人不怎么熟悉这个库的具体实现也能够通过测试来了解如何使用.

确实有时候你需要测试具体的代码实现细节(比如想确认你的程序是从cache而不是数据库中读取数据),但这并不常见, 大多数情况的你的测试和具体实现是分离的.

需要注意的是如果具体实现发生变化, 测试的初始化往往会发生变化(比如你为类的构造函数的新加了一个参数, 那么测试实例化对象的时候也需要传入这个参数).如果面向用户的行为为发生变化, 那么测试就不需要改变