Martin Fowler在文章中详细讲解了Mock的应用理念
http://martinfowler.com/articles/mocksArentStubs.html
MockTest是区别于传统测试方法之处在于传统测试风格是状态验证测试,而Mock是行为验证测试
Martin Fowler比较了Mock与其他Test Double的不同之处
哑对象(Dummy Object):从来不会被使用,一般是用来填充参数列表。
伪造对象(Fake Object):拥有方法的实现,通常为了避免直接操作生产环境而使用一些捷径,比如内存数据库测试DAO。
桩对象(Stub Object): 提供和真实对象一样的接口,提供更易于测试和与外界环境隔离的实现。
模拟对象(Mock object): 与上面三个不同的是,上面三个是状态验证的测试方式,模拟对象关注的行为的执行状态。
所以Mock对象的引入是一种测试风格和一种测试理念的革新,更适合于行为驱动设计BDD的设计方法
但是由于Mock较复杂,一般情况下还是应该尽量优先使用传统测试,何时使用Mock?我总结为以下几点
1、当协作类(collaborator)需要通过应用服务器运行时环境生成,或者IOC容器注入时,总不能让单元测试也跑在服务器上吧?!
2、当协作类需要与外界环境发生交互时,比如发送一封邮件,创建一个物理文件,插入数据库一条数据等等。
3、当协作类复杂到需要单独写单元测试时,我一直认为对一个类的测试应该只关注这个类自己的行为,如果类中传入的协作类需要单独写测试处理,那么对于这个测试类的测试,我们只需要关心协作类的方法是否在需要的地方执行了即可,至于是否执行对了,让协作类自己的测试用例去测吧。倘若此时测试类和协作类都用了传统的测试方法,那么如果协作类中出现了一个错误,你在单元测试测试列表中就会看到协作类和引用这个类的类两个测试发生错误,但其实错误只有一处,会产生混淆。
在编写测试用例的时候,经常会产生另外一种状况,很多人抱怨说Mock测试用例不好写,甚至没法写,比如一下代码。
class A{
public int foo(){
B b = BFactory.getB();
......
}
}
如果B是一个很复杂的对象,那么这样的代码乍一看上去确实没法编写Mock测试用例,因为无法用模拟取代真正对象,如果其再一个希望后天补充单元测试的遗留系统中,那么解决办法只有一个:彻底重构。结构如下:
class A{
private B b;
public A(B b){
this.b = b;
}
public int foo(){
......
}
}
此时B就可以从外界环境中传入了,这两段代码比较说明了测试驱动的好处,测试先行可以督促你在测试过程中编写可测试的代码,编写可测试代码与编写单元测试本身同样的重要也同样不容易,如果在编写代码过程中发现一些类没法测或者测试不全面,那么很可能就是这个类需要重构的标志,所以测试驱动是一个工具,更是一个方法,一种思路,一种保持代码简洁,督促开发人员整理结构的手段。