做签名照的网站,沈阳顺天建设集团网站,网站优化 seo和sem,企业建设网站优势大多数班级都有合作者。 在进行单元测试时#xff0c;您通常希望避免使用那些协作者的实际实现方式来避免测试的脆弱性和绑定/耦合#xff0c;而应使用测试双打#xff1a;模拟#xff0c;存根和双打。 本文引用了有关该主题的两篇现有文章#xff1a;Martin Fowler的Mock… 大多数班级都有合作者。 在进行单元测试时您通常希望避免使用那些协作者的实际实现方式来避免测试的脆弱性和绑定/耦合而应使用测试双打模拟存根和双打。 本文引用了有关该主题的两篇现有文章Martin Fowler的Mocks Are nt Stubs和Bob Uncle的The Little Mocker 。 我都推荐他们。 术语 我将从Gerard Meszaros的书xUnit Test Patterns中借用一个术语。 在其中他引入了术语“ 被测系统 SUT ”即我们正在测试的东西。 “测试中的类”是更适用于面向对象的世界的一种替代方法但是我会坚持使用SUT因为Fowler也会这样做。 我还将使用状态验证和行为验证这两个术语。 状态验证是通过检查SUT或其协作者的状态来验证代码是否正常工作。 行为验证是在验证协作者是否按照我们期望的方式被调用或调用。 测试双打 好回到如何与被测系统的合作者打交道。 对于SUT的每个协作者您可以使用该协作者的实际实现。 例如如果您有一个与数据访问对象DAO协作的服务如下面的WidgetService示例中所示则可以使用真实的DAO实现。 但是它很可能与数据库冲突这绝对不是我们要进行单元测试所需的数据库。 另外如果DAO实现中的代码发生更改则可能导致我们的测试开始失败。 我个人不喜欢当被测代码本身未更改时测试开始失败。 因此我们可以使用有时称为“测试双打”的测试。 “测试双打”一词也来自Meszaros的xUnit测试模式书。 他将它们描述为“为了明确运行测试而安装的代替实际组件的任何对象或组件”。 在本文中我将介绍我使用的三种主要的测试双打类型模拟存根和傻瓜。 我还将简要介绍两个我很少明确使用的东西间谍和假货。 1.嘲弄 首先“模拟”是一个过载的术语。 它通常用作任何测试双精度测试的总称 也就是说任何类型的对象都可以代替测试中的类中的真实协作者。 我对此感到满意因为大多数模拟框架都支持此处讨论的大多数测试双打。 但是出于本文的目的我将以更严格更有限的含义使用模拟。 具体来说 模拟是一种使用行为验证的测试替身类型 。 马丁·福勒Martin Fowler将模拟描述为“用期望进行预编程的对象这些对象构成了期望接收的调用的规范”。 正如Bob叔叔所说的那样模拟程序会监视正在测试的模块的行为并且知道期望的行为。 一个例子可以使它更清楚。 想象一下WidgetService的实现 public class WidgetService {final WidgetDao dao;public WidgetService(WidgetDao dao) {this.dao dao;}public void createWidget(Widget widget) {//misc business logic, for example, validating widget is valid//...dao.saveWidget(widget);}
} 我们的测试可能看起来像这样 public class WidgetServiceTest {//test fixturesWidgetDao widgetDao mock(WidgetDao.class);WidgetService widgetService new WidgetService(widgetDao);Widget widget new Widget();Testpublic void createWidget_saves_widget() throws Exception {//call method under testwidgetService.createWidget(widget);//verify expectationverify(widgetDao).saveWidget(widget);}
} 我们创建了一个WidgetDao的模拟并验证它是否如预期的那样被调用。 我们还可以告诉模拟程序在调用时如何响应。 这是模拟的重要组成部分允许您操纵模拟以便可以测试代码的特定单元但是在这种情况下测试不是必需的。 模拟框架 在此示例中我将Mockito用于模拟框架但Java空间中还有其他对象包括EasyMock和JMock 。 自己动手玩 请注意您不必使用模拟框架即可使用模拟。 您也可以自己编写模拟甚至可以在模拟中构建断言。 例如在这种情况下我们可以创建一个名为WidgetDaoMock的类该类实现WidgetDao接口并且该类的createWidget方法的实现仅记录其被调用的情况。 然后您可以验证呼叫是否按预期进行。 尽管如此现代的模拟框架仍然使这种“劳碌自在”的解决方案变得多余。 2.存根 存根是为了测试目的而“存根”或提供实现的大大简化版本的对象。 例如如果我们的WidgetService类现在也也依赖于ManagerService。 请参阅此处的标准化方法 public class WidgetService {final WidgetDao dao;final ManagerService manager;public WidgetService(WidgetDao dao, ManagerService manager) {this.dao dao;this.manager manager;}public void standardize(Widget widget) {if (manager.isActive()) {widget.setStandardized(true);}}public void createWidget(Widget widget) {//omitted for brevity}
} 并且我们想测试当管理器处于活动状态时标准化方法是否“标准化”了一个小部件我们可以使用如下所示的存根 public class WidgetServiceTest {WidgetDao widgetDao mock(WidgetDao.class);Widget widget new Widget();class ManagerServiceStub extends ManagerService {Overridepublic boolean isActive() {return true;}}Testpublic void standardize_standardizes_widget_when_active() {//setupManagerServiceStub managerServiceStub new ManagerServiceStub();WidgetService widgetService new WidgetService(widgetDao, managerServiceStub);//call method under testwidgetService.standardize(widget);//verify stateassertTrue(widget.isStandardized());}
} 由于模拟通常用于行为验证而存根可用于状态验证或行为验证。 该示例非常基础也可以使用模拟来完成但是存根可以为测试夹具的可配置性提供一种有用的方法。 我们可以对ManagerServiceStub进行参数化以使其将“活动”字段的值用作构造函数参数因此可以在否定测试用例中重用。 也可以使用更复杂的参数和行为。 其他选项包括将存根创建为匿名内部类或为存根创建基类例如ManagerServiceStubBase以供其他人扩展。 后者的优点是如果ManagerService接口发生更改则只有ManagerServiceStubBase类会中断并且需要更新。 我倾向于经常使用存根。 我喜欢他们提供的灵活性以便能够自定义测试装置并提供纯Java代码提供的清晰度。 将来的维护者不需要能够理解某个框架。 我的大多数同事似乎更喜欢使用模拟框架。 找到最适合您的方法并运用最佳判断。 3.假人 顾名思义虚拟对象是非常愚蠢的类。 它几乎不包含任何内容基本上只足以使您的代码得以编译。 当您不在乎如何使用虚拟对象时可以将其传递给某些对象。 例如作为测试的一部分当您必须传递参数时但是您不希望使用该参数。 例如在前面的示例中的standardize_standardizes_widget_when_active测试中我们仍然继续使用模拟的WidgetDao。 虚拟对象可能是一个更好的选择因为我们根本不希望在createWidget方法中完全使用WidgetDao。 public class WidgetServiceTest {Widget widget new Widget();class ManagerServiceStub extends ManagerService {Overridepublic boolean isActive() {return true;}}class WidgetDaoDummy implements WidgetDao {Overridepublic Widget getWidget() {throw new RuntimeException(Not expected to be called);}Overridepublic void saveWidget(Widget widget) {throw new RuntimeException(Not expected to be called);}}Testpublic void standardize_standardizes_widget_when_active() {//setupManagerServiceStub managerServiceStub new ManagerServiceStub();WidgetDaoDummy widgetDao new WidgetDaoDummy();WidgetService widgetService new WidgetService(widgetDao, managerServiceStub);//call method under testwidgetService.standardize(widget);//verify stateassertTrue(widget.isStandardized());}
} 在这种情况下我创建了一个内部类。 在大多数情况下由于Dummy功能很少会在测试之间发生变化因此创建非内部类并为所有测试重用更为有意义。 还要注意在这种情况下使用模拟框架创建类的模拟实例也是可行的选择。 我个人很少使用假人而是创建这样的模拟 WidgetDaoDummy widgetDao mock(WidgetDao.class); 尽管可以肯定的是当确实发生意外调用时抛出异常会更加困难这取决于您选择的模拟框架但是它确实具有简洁性的巨大优势。 虚拟变量可能很长因为它们需要在接口中实现每种方法。 与存根一样假人可用于状态或行为验证。 间谍与伪造 我将简要介绍另外两种测试双打间谍和伪造。 我之所以简短地说是因为我个人很少自己明确使用这两种类型的双打而且还因为术语可能会引起混乱而又不会引起更多细微差别 但是为了完整性…… 间谍 当您想确保系统调用了某个方法时可以使用间谍程序。 它还可以记录各种事情例如计算调用次数或记录每次传递的参数。 但是对于间谍来说存在将测试与代码实现紧密耦合的危险。 间谍专用于行为验证。 大多数现代的模拟框架也很好地涵盖了这种功能。 假货 马丁·福勒Martin Fowler对伪造品的描述如下伪造品具有有效的实现方式但通常采取一些捷径这使其不适合生产内存数据库是一个很好的例子。 我个人很少使用它们。 结论 测试双打是单元测试不可或缺的一部分。 嘲笑存根和双打都是有用的工具了解它们之间的差异很重要。 从最严格的意义上讲模拟只是使用行为验证的双精度形式。 指定了两倍的期望值然后在调用SUT时进行验证。 但是工作模拟也已经变得越来越笼统地描述了此处描述的任何双打实际上大多数现代模拟框架都可以这种通用方式使用。 最后您应该使用哪种双精度型 这取决于所测试的代码但是我建议您遵循使您的测试意图最清楚的任何方式进行指导。 资料来源 莫蒂不是存根作者 马丁·福勒Martin Fowler 小嘲笑 “叔叔”鲍勃·马丁 xUnit测试模式 作者Gerard Meszaros 翻译自: https://www.javacodegeeks.com/2015/11/test-doubles-mocks-dummies-and-stubs.html