出了什么问题? 单元测试的目标是一次只验证一个方法小步的前进细粒度的测试但是假如某个方法依赖于其他一些难以操控的东东比如说网络连接数据库连接或者是Servlet容器那么我们该怎么办呢? 要是你的测试依赖于系统的其他部分甚至是系统的多个其他部分呢?在这种情况下倘若不小心你最终可能会发现自己几乎初始化了系统的每个组件而这只是为了给一个测试创造足够的运行环境让它们可以运行起来忙乎了大半天看上去我们好像有点违背了测试的初衷了这样不仅仅消耗时间还给测试过程引入了大量的耦合因素比如说可能有人兴致沖沖地改变了一个接口或者数据库的一张表突然你那卑微的单元测试的神秘的挂掉了在这种情况发生几次之后即使是最有耐心的开发者也会洩气甚至最终放弃所有的测试那样的话后果就不能想像了 再让我们看一个更加具体的情况在实际的面向对象软件设计中我们经常会碰到这样的情况我们在对现实对象进行构建之后对象之间是通过一系列的接口来实现这在面向对象设计里是最自然不过的事情了但是随着软件测试需求的发展这会产生一些小问题举个例子用户A现在拿到一个用户B提供的接口他根据这个接口实现了自己的需求但是用户A编译自己的代码后想简单模拟测试一下怎么办呢?这点也是很现实的一个问题我们是否可以针对这个接口来简单实现一个代理类来测试模拟期望代码生成自己的结果呢? 幸运的是有一种测试模式可以帮助我们mock对象Mock对象也就是真实对象在调试期的替代品 现在需要Mock对象吗? 关于什么时候需要Mock对象Tim Mackinnon给我们了一些建议 真实对象具有不可确定的行为(产生不可预测的结果如股票的行情) 真实对象很难被创建(比如具体的web容器) 真实对象的某些行为很难触发(比如网络错误) 真实情况令程序的运行速度很慢 真实对象有用户界面 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了) 真实对象实际上并不存在(当需要和其他开发小组或者新的硬件系统打交道的时候这是一个普遍的问题) 如何实现Mock对象? 使用mock对象进行测试的时候我们总共需要个步骤分别是 使用一个接口来描述这个对象 为产品代码实现这个接口 以测试为目的在mock对象中实现这个接口 在此我们又一次看到了针对接口编程的重要性了因为被测试的代码只会通过接口来引用对象所以它完全可以不知道它引用的究竟是真实的对象还是mock对象下面看一个实际的例子一个闹钟根据时间来进行提醒服务如果过了下午点钟就播放音频文件提醒大家下班了如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点然后把耳朵放在音箱旁我们可不想这么笨我们应该利用mock对象来进行测试这样我们就可以模拟控制时间了而不用苦苦等待时钟转到下午点钟了下面是代码 public interface Environmental { private boolean playedWav = false; public long getTime(); public void playWavFile(String fileName); public boolean wavWasPlayed(); public void resetWav(); } 真实的实现代码 public class SystemEnvironment implements Environmental { public long getTime() { return SystemcurrentTimeMillis(); } public void playWavFile(String fileName) { playedWav = true; } public boolean wavWasPlayed() { return playedWav; } public void resetWav() { playedWav = false; } } 下面是mock对象 public class MockSystemEnvironment implements Environmental { private long currentTime; public long getTime() { return currentTime; } public void setTime(long currentTime) { thiscurrentTime = currentTime; } public void playWavFile(String fileName) { playedWav = true; } public boolean wavWasPlayed() { return playedWav; } public void resetWav() { playedWav = false; } } 下面是一个调用getTime的具体类
import javautilCalendar; public class Checker { private Environmental env; public Checker(Environmental env) { thisenv = env; } public void reminder() { Calendar cal = CalendargetInstance(); calsetTimeInMills(envgetTime()); int hour = calget(CalendarHOUR_OF_DAY); if(hour >= ) { envplayWavFile(quit_whistlewav); } } } 使用envgetTime()的被测代码并不知道测试环境和真实环境之间的区别因为它们都实现了相同的接口现在你可以借助mock对象通过把时间设置为已知值并检查行为是否如预期那样来编写测试 import javautilCalendar; import junitframeworkTestCase; public class TestChecker extends TestCase { public void testQuittingTime() { MockSystemEnvironment env = new MockSystemEnvironment(); Calendar cal = CalendargetInstance(); calset(CalendarYEAR ); calset(CalendarMONTH ); calset(CalendarDAY_OF_MONTH); calset(CalendarHOUR_OF_DAY ); calset(CalendarMINUTE ); long t = calgetTimeInMillis(); envsetTime(t); Checker checker = new Checker(env); checkerreminder(); assertFalse(envwavWasPlayed()); t += (**); envsetTime(t); checkerreminder(); assertTrue(envwavWasPlayed()); envresetWav(); t += ***; envsetTime(t); checkerreminder(); assertTrue(envwavWasPlayed()); } } 这就是mock对象的全部伪装出真实世界的某些行为使你可以集中精力测试好自己的代码 好像有一些麻烦 如果每次都像上面那样自己写具体的mock对象问题虽然解决了但是好像有一些麻烦不要着急已经有一些第三方现成的mock对象供我们使用了使用Mock Object进行测试主要是用来模拟那些在应用中不容易构造(如HttpServletRequest必须在Servlet容器中才能构造出来)或者比较复杂的对象(如JDBC中的ResultSet对象)从而使测试顺利进行的工具目前在Java阵营中主要的Mock测试工具有JMockMockCreatorMockrunnerEasyMockMockMaker等在微软的Net阵营中主要是NmockNetMock等 下面就以利用EasyMock模拟测试Servlet组件为例代码如下 编译并将其当做一个Test Case运行会发现两个测试方法均测试成功我们可以看到easymock已经帮助我们实现了一些servlet组件的mock对象这样我们就可以摆脱web容器和servlet容器来轻松的测试servlet了 import orgeasymock*; import junitframework*; import javaxservlethttp*; public class MockRequestTest extends TestCase{ private MockControl control; private HttpServletRequest mockRequest; public void testMockRequest(){ //创建一个Mock HttpServletRequest的MockControl对象 control = MockControlcreateControl(HttpServletRequestclass); //获取一个Mock HttpServletRequest对象 mockRequest = (HttpServletRequest) controlgetMock(); //设置期望调用的Mock HttpServletRequest对象的方法 mockRequestgetParameter(name); //设置调用方法期望的返回值并指定调用次数 //以下后两个参数表示最少调用一次最多调用一次 controlsetReturnValue(kongxx ); //设置Mock HttpServletRequest的状态 //表示此Mock HttpServletRequest对象可以被使用 controlreplay(); //使用断言检查调用 assertEquals(kongxxmockRequestgetParameter(name)); //验证期望的调用 controlverify(); } } 编译并将其当做一个Test Case运行会发现两个测试方法均测试成功我们可以看到easymock已经帮助我们实现了一些servlet组件的mock对象这样我们就可以摆脱web容器和servlet容器来轻松的测试servlet了 底层技术是什么? 让我们来回忆一下如果用户使用C++和java的程序的生成C++在最后的阶段还需要连接才能生成一个整体程序这在灵活性与java源代码的机制是不能比的java的各个类是独立的打包的那些类也是独立的只有在加载进去才进行连接这在代码被加载进去的时候我们还可以执行很多的动作如插入一些相关的业务需求这也是AOP的一个焦点javassit代码库的实现类似于这正是利用这些所以用java实现Mock对象是很简单的 一些相关的资源 MockObject的主页 ;介绍了关键Mock Object的基本概念和目前在各个环境下主要的Mock测试工具 JMock的主页;可以获取JMock的最新代码和开发包以及一些说明文档 EasyMock的主页;可以获取JMock的最新代码和开发包以及一些说明文档 NMock的主页;介绍了在Microsoft Net平台上进行Mock测试的开发工具 |