1、模拟对象 计算机科学与技术学院议程n 基于状态的测试和交互基于状态的测试和交互测试测试n 模拟模拟对象与桩对象的对象与桩对象的区别区别n 模拟对象的例子模拟对象的例子n 同时使用模拟对象和桩对象同时使用模拟对象和桩对象n 桩链桩链n 手写手写模拟对象与桩对象的问题模拟对象与桩对象的问题计算机科学与技术学院场景n 被测代码的复杂逻辑需要基于对其他对被测代码的复杂逻辑需要基于对其他对象的调用象的调用n 被调用对象,可能被调用对象,可能l 不返回结果不返回结果l 不保存状态不保存状态n 如何如何测试对象间的测试对象间的调用调用l 模拟对象模拟对象计算机科学与技术学院什么是交互测试n 交互测试用来测试
2、一个对象如何向另一交互测试用来测试一个对象如何向另一个对象传递消息,或者如何从其他对象个对象传递消息,或者如何从其他对象接收消息,即测试对象如何与其他对象接收消息,即测试对象如何与其他对象进行交互进行交互计算机科学与技术学院两种测试的区分n 交互测试交互测试=动作驱动测试动作驱动测试l 测试对象的某个特定动作测试对象的某个特定动作n 基于状态的测试基于状态的测试=结果驱动测试结果驱动测试l 测试某些最终结果是否测试某些最终结果是否成立成立n 通常,倾向于测试对象的最终结果通常,倾向于测试对象的最终结果n 有时,对象间的交互就是最终结果,此有时,对象间的交互就是最终结果,此时需要测试交互本身时需
3、要测试交互本身l 如:调用一个如:调用一个web 服务服务计算机科学与技术学院示例比较n 假设:一个灌溉系统,事先设定好时间假设:一个灌溉系统,事先设定好时间让它给院子里的树木浇水,一天浇几次让它给院子里的树木浇水,一天浇几次,每次多少水,每次多少水n 基于状态的测试基于状态的测试l 在指定的一段时间内(如:在指定的一段时间内(如:24小时)让系统小时)让系统持续运行,结束后检查树木状态持续运行,结束后检查树木状态l 土地的湿度够不够?树的状态好不好?土地的湿度够不够?树的状态好不好?l 叶子够不够绿?等等叶子够不够绿?等等l 测试很难操作测试很难操作计算机科学与技术学院n 交互测试交互测试l
4、 在水龙头末端安装一个设备,记录灌溉的开在水龙头末端安装一个设备,记录灌溉的开始时间与结束时间,以及每次灌溉的用水量始时间与结束时间,以及每次灌溉的用水量l 系统运行结束后,不必检查树木,只需检查系统运行结束后,不必检查树木,只需检查该设备调用次数是否正确,每次用水量是否该设备调用次数是否正确,每次用水量是否正确正确计算机科学与技术学院n 实际上,无需真正的树木以测试系统实际上,无需真正的树木以测试系统n 可改变灌溉单元的时钟,让它认为浇水可改变灌溉单元的时钟,让它认为浇水时间到了,这样它就会在你指定的任何时间到了,这样它就会在你指定的任何时间浇水,而不必等很长时间才能确定时间浇水,而不必等很
5、长时间才能确定系统是否正常工作系统是否正常工作计算机科学与技术学院模拟对象n 用来记录灌溉信息的是什么设备?用来记录灌溉信息的是什么设备?l 可以说它是一个伪水龙头,或桩对象可以说它是一个伪水龙头,或桩对象l 比桩对象更智能比桩对象更智能记录其每次调用的桩对象记录其每次调用的桩对象,模拟对象的职责,模拟对象的职责n 模拟对象模拟对象l 系统中的一个伪对象,用来决定一个单元测系统中的一个伪对象,用来决定一个单元测试是通过还是失败试是通过还是失败l 通过验证被测对象和伪对象之间是否进行预通过验证被测对象和伪对象之间是否进行预期的交互来判断期的交互来判断计算机科学与技术学院n 模拟对象的用法与桩对象
6、很相似模拟对象的用法与桩对象很相似n 模拟对象比桩对象做的事情更多模拟对象比桩对象做的事情更多l 保存通信历史信息保存通信历史信息l 验证这些记录验证这些记录计算机科学与技术学院区别n 基本区别基本区别l 桩对象不会使测试失败桩对象不会使测试失败l 模拟对象可以导致测试失败模拟对象可以导致测试失败n 桩桩对象用来替换依赖项,确保测试顺利对象用来替换依赖项,确保测试顺利运行,断言是针对被测类的运行,断言是针对被测类的n 测试利用模拟对象来验证测试是否失败测试利用模拟对象来验证测试是否失败,断言是针对模拟对象的,断言是针对模拟对象的计算机科学与技术学院使用桩对象的交互图计算机科学与技术学院使用模拟
7、对象的交互图计算机科学与技术学院业务背景n 增加增加LogAnalyzer需求需求l 当接收到一个长度太短的文件名,就发送一当接收到一个长度太短的文件名,就发送一条错误信息给某个外部的条错误信息给某个外部的web服务服务n 测试遇到的问题测试遇到的问题l Web 服务未实现服务未实现l 直接调用该服务会导致测试时间过长直接调用该服务会导致测试时间过长计算机科学与技术学院重构设计n 新建接口新建接口l 包含调用包含调用web服务服务所需的方法所需的方法l 便于新建模拟对便于新建模拟对象象l 测试中使用该接测试中使用该接口而不是直接调口而不是直接调用用web服务服务计算机科学与技术学院接口计算机科
8、学与技术学院模拟对象计算机科学与技术学院被测类计算机科学与技术学院测试代码计算机科学与技术学院测试策略n 没有在模拟对象代码内部编写测试,原没有在模拟对象代码内部编写测试,原因因l 希望重用该模拟对象,以便于对消息做不同希望重用该模拟对象,以便于对消息做不同的断言的断言l 若在模拟对象内部放置断言,那么阅读的人若在模拟对象内部放置断言,那么阅读的人无法理解在断言什么无法理解在断言什么l 隐藏测试代码的重要信息,会导致测试的可隐藏测试代码的重要信息,会导致测试的可读性和可维护性下降读性和可维护性下降计算机科学与技术学院业务背景n LogAnalyzer调用调用web服务服务n 如果如果Web服务
9、抛出错误,服务抛出错误,LogAnalyzer必须必须记录该错误记录该错误n 发送邮件给管理员发送邮件给管理员计算机科学与技术学院两个外部依赖的交互图计算机科学与技术学院被测类计算机科学与技术学院面对的问题n 如何测试如何测试LogAnalyzer在在web服务抛出异常服务抛出异常时正确调用电子邮件服务?时正确调用电子邮件服务?n 如何替换如何替换web服务?服务?n 如何模拟如何模拟web服务抛出的异常?服务抛出的异常?n 如何知道电子邮件是否正确调用?如何知道电子邮件是否正确调用?计算机科学与技术学院解决思路n 使用桩对象使用桩对象l 替换替换web服务服务l 模拟模拟web服务抛出异常服
10、务抛出异常n 使用模拟对象使用模拟对象l 替换邮件服务替换邮件服务计算机科学与技术学院分析n 桩对象桩对象l 模拟模拟web服务抛出异常服务抛出异常l 用来保证测试正确运行用来保证测试正确运行n 模拟对象模拟对象l 验证是否向电子邮件服务传入正确的参数验证是否向电子邮件服务传入正确的参数l 针对它做断言,验证它是否被正确调用针对它做断言,验证它是否被正确调用计算机科学与技术学院测试交互图计算机科学与技术学院接口计算机科学与技术学院桩对象web服务计算机科学与技术学院模拟对象计算机科学与技术学院被测类计算机科学与技术学院测试代码计算机科学与技术学院问题n 为什么要在一个测试中做多次断言?为什么要
11、在一个测试中做多次断言?n 将测试拆分为将测试拆分为3个测试,每个测试一个断个测试,每个测试一个断言,会更简单?可以把言,会更简单?可以把3个断言组合成一个断言组合成一个逻辑测试吗?个逻辑测试吗?n 为每个测试或测试类手工新建模拟对象为每个测试或测试类手工新建模拟对象和桩对象很枯燥乏味,如何克服?和桩对象很枯燥乏味,如何克服?n 更更重要的是:在一个测试中可以使用多重要的是:在一个测试中可以使用多少个桩对象和模拟对象?少个桩对象和模拟对象?计算机科学与技术学院原则n 一个测试只测一件事一个测试只测一件事n 每个测试只能有一个模拟对象每个测试只能有一个模拟对象n 每个测试可以有多个桩对象每个测试
12、可以有多个桩对象n 一一个测试中存在多个模拟对象意味着正个测试中存在多个模拟对象意味着正在测试多件事,这会导致测试变得复杂在测试多件事,这会导致测试变得复杂或脆弱或脆弱n 在遇到更复杂的测试时,首先找到模拟在遇到更复杂的测试时,首先找到模拟对象,其他的就是桩对象,前者影响断对象,其他的就是桩对象,前者影响断言言计算机科学与技术学院桩链n 有时,我们希望从一个伪组件中返回另有时,我们希望从一个伪组件中返回另外一个伪组件,从而在测试中形成了一外一个伪组件,从而在测试中形成了一个小小的桩链个小小的桩链n 示例代码示例代码l IServiceFactory factory=GetServiceFact
13、ory()l Iservice service=factory.GetService()l String conn=GlobalUtil.Configuration.DBConfiguration.ConnectionString计算机科学与技术学院弊端n 假设你想在测试时替换连接字符串,你假设你想在测试时替换连接字符串,你可以将可以将Configuration属性设计为一个桩对属性设计为一个桩对象,然后测试时替换它象,然后测试时替换它n 这个技术很强大,但是否不利于重构?这个技术很强大,但是否不利于重构?计算机科学与技术学院虚方法n String conn=GetConnString()n
14、Proteced virtual string GetConnString()n n return GlobalUtil.Configuration.DBConfiguration.ConnectionStringn 计算机科学与技术学院n 测试时,重写该方法测试时,重写该方法n 这种方式这种方式l 能增强代码的可读性和可维护性能增强代码的可读性和可维护性l 不用为插桩,新增接口不用为插桩,新增接口计算机科学与技术学院手写模拟对象和桩对象的问题n 编写模拟对象和桩对象很费时编写模拟对象和桩对象很费时n 如果类和接口有很多方法、属性和事件如果类和接口有很多方法、属性和事件,将很难为它们编写模拟对
15、象和桩对象,将很难为它们编写模拟对象和桩对象n 如果模拟的方法被多次调用,一旦涉及如果模拟的方法被多次调用,一旦涉及状态保存问题,就需要写很多代码(方状态保存问题,就需要写很多代码(方法内部)法内部)计算机科学与技术学院n 如果要验证一个方法的所有参数,就需如果要验证一个方法的所有参数,就需要写多个断言要写多个断言l 第一第一个断言失败时,程序抛出异常,从而导个断言失败时,程序抛出异常,从而导致后续断言无法执行致后续断言无法执行n 很难在其他测试中重用模拟对象和桩对很难在其他测试中重用模拟对象和桩对象象计算机科学与技术学院解决方案n 采用隔离框架采用隔离框架n 下下一讲介绍一讲介绍l 隔离框架
16、隔离框架l Rhino Mock计算机科学与技术学院小结n 基于状态的测试和交互测试基于状态的测试和交互测试l 前者前者=结果驱动测试结果驱动测试l 后者后者=动作驱动测试动作驱动测试n 模拟对象与桩对象的区别模拟对象与桩对象的区别l 模拟对象测试依赖项的交互模拟对象测试依赖项的交互l 桩对象返回状态桩对象返回状态l 模拟对象会导致断言失败,桩对象则不会模拟对象会导致断言失败,桩对象则不会计算机科学与技术学院测试交互图计算机科学与技术学院使用n 同时使用桩对象与模拟对象同时使用桩对象与模拟对象l 每个测试最多只能有一个模拟对象每个测试最多只能有一个模拟对象l 每个测试可有多个桩对象每个测试可有多个桩对象n 采用桩链时采用桩链时l 优先考虑采用虚方法注入桩对象优先考虑采用虚方法注入桩对象l 提高代码可读性与可维护性提高代码可读性与可维护性n 避免过度使用模拟对象避免过度使用模拟对象