(原文)http://www.blogjava.net/BlueDavy/archive/2006/01/22/28976.html
既然测试是好的,那就把它发挥到极限。
测试是好的,这一点无可厚非,几乎做软件的人都是认可的,本篇只是谈谈测试中的单元测试部分,单元测试的目的是为了保证类中的方法是符合设计时的需求的,需求驱动似的类实现,^_^
单元测试的好处
1、保证类对于设计以及需求的符合
在没有单元测试的情况下,其实是很难保证类对于设计以及需求的符合的,很多情况往往会因为开发人员本身的因素将实现代码复杂化,并且编写出很多设计和需求根本不需要的东西。
2、降低调试的复杂性
想想在没有单元测试的情况下,调试通常是集成时才做的,这个时候要通过慢慢的跟踪来查找问题的原因,而在web系统中就更痛苦了,总是要重启,如果不想那么痛苦,就采用单元测试吧。
3、减少集成时出错的机率
单元测试可保证暴露给外部的API的正确性,减少要通过集成才发现错误的现象。
4、保证重构和简单设计的可行
想想,如果没有单元测试,怎么敢对代码做重构呢,如果没有单元测试,简单设计很难通过重构去演变成为将来更好的设计。
单元测试的实现
单元测试的实现采取的方法通常是xUnit,在Java界就是junit了,最重要的仍然不是工具,而是怎么去实现单元测试的方法,测试驱动开发无疑是最佳的编写测试的方法,首先根据设计或需求编写测试,根据测试编写代码,直到测试通过为止。
在代码出现bug时,一定要先把出现bug的情况补充到测试中去,接下来仍然是修改实现代码,直到测试通过。
单元测试编写的原则其实很简单,就是测试一定情况下类的执行是否符合预期。
还是举例来说:
假设需要编写一个根据用户名和密码验证用户的服务,按照TDD我们首先编写单元测试类,我们应该怎么来编写这个单元测试类呢,一般可按照一个这样的步骤:
1、分析类的输入。
这点通常是分析类依赖外部什么类,需要在测试类中提前注入。
以上面的服务来说,通常需要依赖的是用户的Dao类。
2、分析方法的输入造成的输出的影响。
这点通常是分析方法输入的参数对执行结果造成的影响。
以上面的服务来说,输入的参数为用户名和密码,这个时候会有几种情况会出现:
2.1 用户名或密码为null
在这种情况下,假设期待的输出为false
2.2 用户名和密码都为null
在这种情况下,假设期待的输出为false
2.3 输入的用户名和密码在系统中存在
在这种情况下,假设期待的输出为true
2.4 用户名或密码其中一项输入不正确,验证不通过
在这种情况下,假设期待的抛出AuthronizedException
在经过这样的分析后,就可以开始编写测试类了,编写的测试类如下(示例代码而已):
public class UserServiceTest extends TestCase {
private UserDao dao=null;
private UserService service=null;
private User user=null;
public static void main(String[] args) {
junit.textui.TestRunner.run(UserServiceTest.class);
}
protected void setUp() throws Exception {
super.setUp();
dao=new UserDaoImpl();
user=new User();
user.setName("TEST_BLUEDAVY");
user.setPass("JERRY");
dao.save(user);
service=new UserServiceImpl();
service.setDao(dao);
}
protected void tearDown() throws Exception {
super.tearDown();
dao.delete(user);
}
public void testWhenNameAndPassAreNull(){
assertEquals(false,service.login(user.getName(),user.getPass()));
}
public void testWhenNameOrPassIsNull(){
assertEquals(false,service.login(user.getName(),user.getPass()));
}
public void testWhenNameAndPassAreCorrect(){
assertEquals(true,service.login(user.getName(),user.getPass()));
}
public void testWhenNameOrPassIsError(){
try{
service.login(user.getName(),user.getPass());
}
catch(Exception e){
assertEquals(AuthronizedException.class,e.getClass());
}
}
}
在编写完测试类后就可以开始编写实现代码了,实现代码在编写的时候很简单,只要能够保证测试通过就完事,在测试通过后可以开始考虑重构的事,重构仍然只要保证测试通过即可,其实这个时候就可以看到,简单设计就变得可行了,因为可以通过重构来提升设计。
如果将来这段代码出现bug,就把bug中的输入情况也编写为一个测试方法进行测试,开始运行就应该和bug一样出现问题,这时只需去修正实现代码,直到测试通过为止,那么bug也就自然被修正了。
简单的单元测试的编写较为简单,复杂的单元测试则可能需要使用Mock来模拟一些环境,Mock方面的工具有很多,大家可以去参考相关的开源工具的网站。
经验总结
对 于单元测试通常很多人都有疑问,执行起来的时候经常是不够彻底,特别是在项目时间紧张的情况下,总是觉得编写测试是一种耽误时间的事,其实编写单元测试会 为你节省非常多的时间,想想我们大部分的项目都是在集成、修改bug和维护上消耗了大量的时间,既然单元测试这么好,那么我们就实现单元测试吧。
在单元测试中最重要的注意点就是不要依赖于正常的运行数据,所有的数据都要通过代码模拟出来,在测试完毕后清除,避免造成测试对于运行数据的依赖,同时也避免测试数据对于实际运行系统的影响。