单元测试随着agile的流行已经家喻户晓了,这正反映了软件的一个本质特征:软件是Man-Made的,而人是不可靠的。软件出错的高频率必然导致控制 间隔的缩短。我最早是在编写matlab程序的时候独立的发现了单元测试的作用。因为matlab是弱类型的,横纵矢量也不区分,很容易犯错误,我就为每 一个matlab函数编写了测试脚本,约定了命名规范为xxx_test.m, 在测试的时候通过 can_assert来做出判断而不是输出变量内容进行人工检查。 每次作了修改的时候,运行一下can_test_all.m就自动搜索测试脚本并运行一遍。 后来xp出现了, 我也第一次听说了单元测试这回事,看来英雄所见略同吧 ^_^
单元测试可以看作是对编译器的补充。编译器只能进行语法检查(形式),而单元测试可以进行语义检查(内容),它其实维护了程序的一个语义框架。如果单元测 试程序编写出来了,即使一行业务实现代码也没编写,我们手中也已经拥有了一笔宝贵的财富,因为程序的语义已经在某种意义下确定下来了。重构一般是在维护单 元测试不变的情况下进行的。即我们发现在维护语义不变量的情况下,系统具有相当的调整余地。
坚持单元测试的另一个好处是它倾向于良好分离的模块,因为高内聚低耦合的模块才更容易进行测试。为了测试,我们会在不知不觉中在对象职责的分离上投注更多 的心力。在witrix平台中,虽然基于EasyMock提供了mock支持,在实际中却很少使用,因为模块功能一般很独立,不需要复杂的mock对象, 而对于一般的对象,eclipse有代码生成功能,可以非常轻易的生成简单的测试对象。
虽然JUnit非常流行,我们的单元测试也是基于JUnit进行的,但是我们还是进行了一个简单的封装,将JUnit框架的特定要求与具体测试代码剥离开来。具体的,测试类从
test.UnitTest继承,而不是从JUnit的TestCase继承。使用Debug.check()来做判断,而不是JUnit的assertEquals等。
class MyTest extends UnitTest{
public MyTest(String caseName){ super(caseName); }
public void testMy(){
MyObject my = new MyObject();
Object value = myObject.myFunc();
Debug.check(value.equals("aa"));
// 可以同时提供一个出错消息
Debug.check(value.equals("aa"),"myFunc return invalid : "+value);
}
public static void main(String[] args){
// 不需要IDE或者其他外部的支持就可以直接调用测试代码,将会自动输出运行时间等
UnitTest.exec(new MyTest("testMy"));
}
}