1、我们应该怎么做
编写代码之前,先编写单元测试,即测试先行。也就是所谓的测试驱动开发(TDD)
单元测试是代码的一部分,所有的代码必须有单元测试,并且测试通过。
在修改代码之前,先修改单元测试,并且测试通过。
当我们在对代码进行重构和进行设计优化时,如果我们对所有的类都编写了测试,
那么我们重构代码的时候很轻松的进行测试我们的修改是否正确。
当我们接到一个bug报告后,我们总是先修改测试代码,然后修改实现代码,使之测试成功。
2、为什么要编写单元测试
编写单元测试并不会增加了工作负担延缓项目进度
(1)、以一个web应用开发为例:业务代码编写完成->打包->发布到服务器->进行功能测试->发现问题->修改代码->再打包……如此循环。
任何一个web程序员对于这种开发情景都不会感到陌生。往往不断的打包,发布,功能测试的时间是代码编写的10倍以上。
通过集成系统来发现程序的bug,我们往往很难一下子准确的定位bug产生的地方。如果第一次没彻底修改这个bug或者修改这个bug
已经影响到其它的地方,这样部署上去肯定又有bug。又的业务代码编写完成->打包->发布到服务器->进行功能测试。
如果为每一个类都编写单元测试并让每一个方法测试通过,又会是怎么样的开发情景呢?
编写测试代码->编写业务代码->运行测试方法->修改代码让测试通过->所有的类都通过测试->打包->发布到服务器->进行功能测试->发现bug->修改测试代码->修改业务代码->测试通过->再打包…如此循环。
这样降低了打包->发布到服务器->进行功能测试的次数。
(2) 另外,如果没有单元测试,会经常出现一些低级的错误,如拼写错误,空指针异常等。
就因为一个小小的拼写错误而需要重新打包,发布一次。如果有单元测试,就可以避免这些低级的错误。
(3)在离bug产生越近,修正bug就越容易;在bug产生越远,修正bug的代价就越昂贵。
假设我们去集成一个星期(甚至更长时间)前编写的代码,当发现问题时,我们已经忘掉了很多重要的实现细节,
所以修改变得困难重重。
3、编写单元测试的基本原则
(1)学会使用断言:断言就是让我们为方法设置一个期望值。当方法执行结果与期望值不一致时,测试组件就会报告测试不通过。
(2)最大化测试覆盖率:我们除了测试一个正确的路径外,我们还需要测试方法的每一个分支逻辑。
需要编写尽可能多的测试程序逻辑的测试。写一个充分的测试。
(3)不要依赖于测试方法的执行顺序:使用Junit来进行单元测试,它不能保证测试方法按照我们的意图的顺序来执行。
当一个测试类有多个测试方法时,我们不能让一个测试方法必须在某一个测试之后执行才能成功。
Junit不能为我们做这样的保证,我们不能依赖于测试方法的执行顺序。
(4)针对接口测试:我们有“针对接口编程”的oo设计原则。同样对于测试,我们也需要针对接口测试。
也就是说在编写单元测试时,测试对象总是使用接口,而不是使用具体类。
4、编写单元测试的作用
单元测试能够提高程序员对程序的信心,保证程序的质量,加快软件开发速度,使程序易于维护。
5、编写测试用例
public int divide(int a, int b) throws Exception{
if(0 == b){
throw new Exception("除数不能为0");
}
return a / b;
}
(1)对方法有抛出异常的方法设计测试用例
如果被测试方法抛出异常。则需要设计两个测试用例
1),设计正常的用例
在测试用例中try catche 然后捕获后设置断言为Assert.fail();
public void testDivide(){
int result = 0;
try{
result = cal.divide(6, 2);
}
catch (Exception e){
e.printStackTrace();
Assert.fail("测试失败");
}
Assert.assertEquals(3, result);
}
2),设计抛出异常的用例
public void testDivideDivideByZero(){
Throwable tx = null;
try{
cal.divide(6, 0);
Assert.fail("测试失败");
}
catch(Exception ex){
tx = ex;
}
//判断是否抛了异常
Assert.assertNotNull(tx);
//判断异常类型是否一致
Assert.assertEquals(Exception.class, tx.getClass());
//判断异常信息是否一致
Assert.assertEquals("除数不能为0", tx.getMessage());
}
3)、私有方法的测试,使用类的反射机制
public void testAdd()
{
try{
Calculator cal = new Calculator();
Class
Method method = clazz.getDeclaredMethod("add", new Class[] {
Integer.TYPE, Integer.TYPE });
method.setAccessible(true);
Object result = method.invoke(cal, new Object[] { 2, 3 });
Assert.assertEquals(5, result);
}
catch (Exception ex){
Assert.fail();
}
}