好处
体现设计
- 测试驱动迫使我们从调用者的角度去观察和思考问题,迫使我们把代码设计成可测试的,松耦合的。
验证行为
保证代码的正确性
回归测试:即使到项目后期,我们仍然有勇气去增加新功能,修改程序结构,而不用担心破坏重要功能
给重构带来保证
作为文档
- 单元测试是一种无价的文档,精确的描述了代码的行为,是如何使用函数和类的最佳文档。
原则
测试代码和被测代码是同等重要的,需要被同时维护
测试代码不是附属品!
不但要重构代码,也要重构单元测试!
单元测试一定是隔离的
一个测试用例的运行结果不能影响其他测试用例
测试用不能互相依赖,应该能够以任何次序执行
单元测试一定是可以重复执行的
- 不能依赖环境的变化
保持单元测试的简单性和可读性
尽量对接口进行测试
- 尽量公共方法,私有方法就不用
单元测试应该可以迅速执行
给程序员提供及时的反馈
使用Mock对象对数据库,网络的依赖进行解耦
自动化单元测试
- 集成到build过程中去
常规测试用例:
public class CalculatorTest{
@BeforeClass
public void beforeClass(){
}
@AfterClass
public void afterClass(){
}
@Before
public void setUp(){
}
@After
public void tearDown(){
}
@Test
public void testCase1(){
}
@Test
public void testCase2(){
}
}
以上方法的执行顺序是:
beforeClass();
setUp();
testCase1();
tearDown();
setUp();
testCase2();
tearDown();
afterClass();
保证真正的测试用例有前置条件,并做好后置工作。
测试suite:
AllTests组织V1,V2,V3测试,V1再组织其它测试用例,依此类推。。。
- AllTests.java
@RunWith(Suite.class)
@SuiteClasses({
V1AllTests.class,
V2AllTests.class,
V3AllTests.class,
...
})
public class AllTests{
//...
}
- V1Tests.java
@RunWith(Suite.class)
@SuiteClasses({
ATest.class,
BTest.class,
CTest.class,
...
})
public class V1Tests{
//...
}
高级使用:
使用Mock对象
真实的对象不易构造
例如httpservlet 必须在servlet容器中才能创建出来
真实的对象非常复杂
如jdbc中的Connection, ResultSet
真实的对象的行为具有不确定性,难于控制他们的输出或者返回结果
真实的对象的有些行为难于触发, 例如硬盘已满,网络连接断开
真实的对象可能还不存在,例如依赖的另外一个模块还没开发完毕
使用Mock 对象“替代”或者“冒充” 真实模块和被测试对象进行交互
开发人员可以精确的定制期待的行为
对TDD提供有力的支持
帮助你发现对象的角色和职责
对接口编程,而不是对实现编程
Mock Object例子:
public class URLParser{
public void parse(HttpServletRequest request){
String startRow = request.getParameter(“startRow”);
String endRow = request.getParameter(“endRow”);
… do some business logic…
}
}
使用:
//step 1: 创建mock 对象
MockControl control = MockControl.createControl(HttpServletRequest.class);
HttpServletRequest request = (HttpServletRequest) control.getMock();
//step2: 设置并记录mock对象的行为
request.getParameter("startRow");
control.setReturnValue("10");
request.getParameter("endRow");
control.setReturnValue("20");
// step3: 转换为回放模式
control.replay();
// step 4: 测试代码
URLParser parser = new URLParser(request);
parser.parse();
Assert xxx
其它技巧
重构遗留代码
- 遗留代码不可避免
虽然TDD是很有效的编程方法,但是我们的工作很少从第一行代码开始。
- 遗留代码不是坏代码
它是可以工作的软件/组件,但是在设计和开发式没有考虑“可测试性”
- 遗留代码难于测试
长久失修,导致业务逻辑难于理解
依赖的资源太多,导致测试无从下手
不敢修改,害怕牵一发而动全身
重构代码,提高可测试性
使用Mock Object解除依赖
测试分解
先写粗粒度的测试代码,然后编写细粒度的代码
Package -> Class -> method
处理遗留代码的步骤
确定要测试的类和函数
解除依赖
编写测试用例
重构代码
好的单元测试
- 简单
防止过度的Setup,否则不知道是测试用例的错误,还是业务逻辑的错误
隔离
可重复
防止在一台机器上可以运行,在另外一台机器上失败
防止今天成功,明天失败
运行快
防止长时间的运行
代码覆盖面广
防止测试通过,但是没测到什么代码