接口与单元测试

原创
2011/08/25 20:47
阅读数 8.2K

刚才在开源中国看到一则关于TDD的新闻,挺雷的......然后又想起以前跟别人解释关于为什么在Spring里需要先写个接口XXXInterface,然后再写实现类XXXInterfaceImpl的问题。写一点自己的想法,关于单元测试。

一个情景:

一个User类,提供一个静态方法:(这里不讨论框架或者异常控制流程的正确性)

public class User{

	public static User login(String username,String password) throw LoginException{
		//取得DAO
		//查询是否存在用户,如果存在,返回User,否则抛出异常
	}
	
	//其他全略
}

下面讨论如何对这个方法进行测试:

单纯对于这个模块进行单元测试: 

public class UserTest{

	public void testLogin(){
		User user = User.login("aaa", "123456");
		//对于user的正确性进行判定
	}
}

 这样的测试,正确性和速度实际上都保证不了。因为login中引入了数据库的各种操作,各种未知的可能性都是存在的。比如说数据库并未配置正确或者数据库当前无法访问,直接会导致这个测试失败,但测试失败并不意味着User.login方法错了。同时,由于访问数据库造成的速度可能使这么一个简单的测试需要几百毫秒甚至几秒,那么大量的测试用例加在一起,如文中提到的花费个几十分钟是绝对可能的。

实际上这个简单的测试方案违背了单元测试的基本原则:单元测试应该测试独立的单元模块,这个单元不应依赖于其他模块。在这里显然这个Login的方法使用DAO中的一些方法。

下面讨论DAO:

public class UserDAO implements DAO<User>{
	//各种方法的实现
}

public class DAOFactory{
	public DAO getDAO(Class pojoClass){
		//获取各种DAO
	}

	public static DAOFactory getInstance(){
		//单例工厂
	}
}
一般来说DAO部分都跟上面的差不多(或许是直接注入进User的,但实际原理是一致的):一共两个步骤:1、获取DAO;2、使用DAO。为了不让User的测试受到DAO部分的干扰,就需要使用Mock技术,对DAO对象进行模拟,保证其各种方法的正确性。然后当User类需要获取DAO时,将MockUserDAO代替UserDAO交给它。

public class MockUserDAO implements DAO<User>{
	//肯定准确的实现
}
而DAOFactory由于是单例,其方法getInstance是静态的,所以最后只能靠修改原始代码来实现获得另一个工厂——也就是MockDAOFactory。如果在程序员和单元测试人员不是同一个人的时候,真的是非常麻烦的事情..... (从这个角度上将,我极力反对静态方法)虽说这种情形可以靠反射来进行伪造实例的活动,但是这种解决方案总是有一种黑客的感觉....

另外就是如果DAO不是接口,而只有实现(这并不是不可能的情况,在有通用DAO的前提下,完全可以达到),情况也会很麻烦。由于DAO不是接口,导致无法Mock这个DAO。也许可以使用MockDAO extends DAO的方案,但也许在DAO的构造方法中依旧有着连接数据库,初始化连接池,初始化日志等等的初始化过程,由于其在构造方法中,是无法覆盖掉的。在这种情况下,就只能通过重构来实现测试了。(这也就是即使只有一个实现,也要尽量写一个接口的原因)

单元测试是至关重要的,我个人认为,如果一个团队中的程序员头脑都不错,那么包括getter和setter都应该测试。单元测试的度基本上可以说是宁滥勿缺的。

挺杂乱的,不过只是随想,所以也无所谓。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
6 收藏
1
分享
返回顶部
顶部