我的最后一个博客是有关测试代码方法的一系列博客中的第四篇,演示了如何创建使用存根对象隔离测试对象的单元测试。 今天的博客探讨了有时被视为对立的技术:使用模拟对象进行单元测试。 同样,我使用了从数据库检索地址的简单方案:

…并测试AddressService类:

@Component
public class AddressService {private static final Logger logger = LoggerFactory.getLogger(AddressService.class);private AddressDao addressDao;/*** Given an id, retrieve an address. Apply phony business rules.* * @param id*            The id of the address object.*/public Address findAddress(int id) {logger.info("In Address Service with id: " + id);Address address = addressDao.findAddress(id);address = businessMethod(address);logger.info("Leaving Address Service with id: " + id);return address;}private Address businessMethod(Address address) {logger.info("in business method");// Apply the Special Case Pattern (See MartinFowler.com)if (isNull(address)) {address = Address.INVALID_ADDRESS;}// Do some jiggery-pokery here....return address;}private boolean isNull(Object obj) {return obj == null;}@Autowired@Qualifier("addressDao")void setAddressDao(AddressDao addressDao) {this.addressDao = addressDao;}
}

…通过将他的数据访问对象替换为模拟对象。

在继续之前,最好定义一个模拟对象的确切含义以及它与存根的不同之处。 如果您阅读了我的上一篇博客,您会记得我让Martin Fowler将存根对象定义为:

“存根提供对测试过程中进行的呼叫的固定答复,通常通常根本不响应测试中编程的内容。”

……摘自他的论文《 Mocks Are n't Stubs》 。

那么,模拟对象与存根有何不同? 当您听到人们谈论模拟对象时,他们经常提到他们在嘲笑 行为嘲笑 角色 ,但这意味着什么? 答案在于单元测试和模拟对象共同测试对象的方式。 模拟对象场景如下所示:

  1. 测试中定义了一个模拟对象。
  2. 模拟对象被注入到您的测试对象中
  3. 该测试指定将调用模拟对象上的哪些方法,以及参数和返回值。 这就是所谓的“ 设定期望 ”。
  4. 然后运行测试。
  5. 然后,测试将要求模拟程序验证步骤3中指定的所有方法调用均已正确调用。 如果是,则测试通过。 如果不是,那么测试将失败。

因此,模拟行为或模拟角色实际上意味着检查被测对象是否正确调用了模拟对象上的方法,如果没有,则使测试失败。 因此,您是在断言方法调用的正确性和通过代码的执行路径,而不是在常规单元测试的情况下断言被测试方法的返回值。

尽管有几种专业的模拟框架,但在本例中,我首先决定产生自己的AddressDao模拟,它可以满足上述要求。 毕竟,这有多难?

public class HomeMadeMockDao implements AddressDao {/** The return value for the findAddress method */private Address expectedReturn;/** The expected arg value for the findAddress method */private int expectedId;/** The actual arg value passed in when the test runs */private int actualId;/** used to verify that the findAddress method has been called */private boolean called;/*** Set and expectation: the return value for the findAddress method*/public void setExpectationReturnValue(Address expectedReturn) {this.expectedReturn = expectedReturn;}public void setExpectationInputArg(int expectedId) {this.expectedId = expectedId;}/*** Verify that the expectations have been met*/public void verify() {assertTrue(called);assertEquals("Invalid arg. Expected: " + expectedId + " actual: " + expectedId, expectedId, actualId);}/*** The mock method - this is what we're mocking.* * @see com.captaindebug.address.AddressDao#findAddress(int)*/@Overridepublic Address findAddress(int id) {called = true;actualId = id;return expectedReturn;}
}

支持此模拟的单元测试代码为:

public class MockingAddressServiceWithHomeMadeMockTest {/** The object to test */private AddressService instance;/*** We've written a mock,,,*/private HomeMadeMockDao mockDao;@Beforepublic void setUp() throws Exception {/* Create the object to test and the mock */instance = new AddressService();mockDao = new HomeMadeMockDao();/* Inject the mock dependency */instance.setAddressDao(mockDao);}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the Mock Expectations */mockDao.setExpectationInputArg(id);mockDao.setExpectationReturnValue(expectedAddress);/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */mockDao.verify();}
}

好的,尽管这演示了使用模拟对象执行单元测试所需的步骤,但它相当粗糙且准备就绪,并且非常针对AddressDao / AddressService场景。 为了证明它已经做得更好,下面的示例使用easyMock作为模拟框架。 在这种更专业的情况下,单元测试代码为:

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class MockingAddressServiceWithEasyMockTest {/** The object to test */private AddressService instance;/*** EasyMock creates the mock object*/@Mockprivate AddressDao mockDao;/*** @throws java.lang.Exception*/@Beforepublic void setUp() throws Exception {/* Create the object to test */instance = new AddressService();}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Inject the mock dependency */instance.setAddressDao(mockDao);/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the expectations */expect(mockDao.findAddress(id)).andReturn(expectedAddress);replay();/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */verify();}
}

…我希望您会同意,这比我快速尝试编写模拟游戏更具进步性。

使用模拟对象的主要批评是它们将单元测试代码与生产代码的实现紧密耦合。 这是因为设置期望值的代码紧密跟踪生产代码的执行路径。 这意味着即使该类仍履行其接口协定,后续对生产代码的重构也可能破坏大量测试。 这引起了这样的断言,即模拟测试相当脆弱,并且您将花费不必要的时间修复它们,根据我的经验,尽管我使用了“非严格”模拟,但这种模拟并不关心方法的顺序,尽管我同意期望被称为,在一定程度上减轻了问题。

另一方面,一旦您知道如何使用诸如easyMock之类的框架,就可以非常快速有效地完成将您的对象隔离的单元测试。

在自我批评该示例代码时,我想指出的是,我认为在这种情况下使用模拟对象是过大的,此外,您还可以轻易地认为我将模拟作为存根使用。

几年前,当我第一次遇到easyMock时,我在各处使用了模拟,但是最近我开始更喜欢手动为应用程序边界类(例如DAO)和仅返回数据的对象编写存根。 这是因为基于存根的测试可以说比基于模拟的测试要脆弱得多,尤其是当您需要访问数据时。

为什么要使用模拟? 擅长测试使用“ 告诉不要询问 ”技术编写的应用程序,以验证是否调用了具有无效返回值的方法。

参考: Captain Debug博客上来自JCG合作伙伴 Roger Hughes的 使用Mocks进行单元测试-测试技术5

相关文章 :

  • 测试技巧–不编写测试
  • 端到端测试的滥用–测试技术2
  • 您应该对什么进行单元测试? –测试技术3
  • 常规单元测试和存根–测​​试技术4
  • 为旧版代码创建存根–测试技术6
  • 有关为旧版代码创建存根的更多信息–测试技术7
  • 为什么要编写单元测试–测试技巧8
  • 一些定义–测试技术9
  • 使用FindBugs产生更少的错误代码
  • 在云中开发和测试

翻译自: https://www.javacodegeeks.com/2011/11/unit-testing-using-mocks-testing.html

使用模拟的单元测试–测试技术5相关推荐

  1. 您应该对什么进行单元测试? –测试技术3

    昨天我在办公室里,和我的一位同事谈论测试,他对编写单元测试有些不服气. 他使用的原因之一是有些测试似乎毫无意义,这使我想到了什么是单元测试,什么也不需要打扰. 考虑下面一个简单的不可变的Name Be ...

  2. 端到端测试_端到端测试的滥用–测试技术2

    端到端测试 我的上一个博客是有关测试代码方法的一系列博客中的第一篇,概述了使用一种非常常见的模式从数据库检索地址的简单方案: -并描述了一种非常通用的测试技术: 不编写测试 , 而是手动进行所有操作. ...

  3. 9针串口定义测试方法_一些定义–测试技术9

    9针串口定义测试方法 我认为我即将结束有关测试技术的博客系列,感觉好像已经过去了. 对我来说更清楚的一件事是,测试方法仍处于起步阶段,因此是开发人员之间争执或讨论的明确来源,这是一件好事. 我怀疑我们 ...

  4. 存根类 测试代码 java_有关为旧版代码创建存根的更多信息–测试技术7

    存根类 测试代码 java 在我的上一个博客中 ,我谈到了如何处理行为不佳的不可测试的 (1) SitePropertiesManager 类,以及如何通过提取接口来创建存根. 但是,如果由于旧类的源 ...

  5. 单元测试编写_为什么要编写单元测试-测试技巧8

    单元测试编写 我对最近的博客"您应该测试什么"有很多React,有些人出于各种原因与我达成一致,另一些人则认为建议某些类可能不需要单元测试是完全危险的. 已经处理了什么测试,今天的 ...

  6. 存根类 测试代码 java_为旧版代码创建存根-测试技术6

    存根类 测试代码 java 任何阅读此博客的人都可能已经意识到,目前我正在开发一个项目,其中包含大量的旧代码,这些旧代码庞大,扩展且编写时从未进行过任何测试. 在使用此遗留代码时,有一个行为非常差的类 ...

  7. 端到端测试的滥用–测试技术2

    我的上一个博客是有关测试代码方法的一系列博客中的第一篇,概述了使用一种非常常见的模式从数据库检索地址的简单方案: -并描述了一种非常通用的测试技术: 不编写测试 , 而是手动进行所有操作. 今天的博客 ...

  8. 一些定义–测试技术9

    我认为我即将结束有关测试技术的博客系列,感觉好像已经过去了. 对我来说更清楚的一件事是,测试方法仍处于起步阶段,因此是开发人员之间争执或讨论的明确来源,这是一件好事. 我怀疑我们正处于职业发展史上的某 ...

  9. 为旧版代码创建存根–测试技术6

    任何阅读此博客的人都可能已经意识到,目前我正在开发一个包含大量旧代码的项目,这些旧代码庞大,扩展且编写时从未进行过任何测试. 在使用此遗留代码时,有一个行为异常的类非常普遍,整个团队都一次又一次地犯错 ...

最新文章

  1. 阿里巴巴公布“云钉一体”战略:阿里云与钉钉全面融合
  2. python【力扣LeetCode算法题库】13- 罗马数字转整数
  3. 【转】Win7安装Oracle10g经验分享
  4. Chrome 控制台指南
  5. JS/JQUERY函数库
  6. 阿里云云计算 14 使用阿里云中的OSS
  7. 用Rstudio进行ARIMA模型预测(小白系列)
  8. 国科大学习资料--矩阵分析与应用(李保滨)--2017年期末考试试卷
  9. 删除需要TrustedInstaller权限的文件
  10. wav是什么格式?怎么转成mp3?
  11. C#多线程操作界面控件的解决方案(转)
  12. visio双线方框怎么画_用VISIO怎样画出扇形
  13. jpa 动态查询条件 数组_JPA使用Specification构建动态查询
  14. Tensorflow2.* 加载和预处理数据之用 tf.data 加载 Numpy数据(2)
  15. 模拟量开环控制系统的组成
  16. 手写Vuex核心原理,再也不怕面试官问我Vuex原理
  17. easyui简单demo
  18. phpstudy启动MySQL服务遇到的问题及解决过程
  19. 特殊符号 与 unicode 转换
  20. DHCP欺骗和解决方案

热门文章

  1. 为wmi执行例外_称之为例外?
  2. 将json绑定为对象_了解自定义对象创建:JSON绑定概述系列
  3. 朝着理想坚实迈进_坚实原则:单一责任原则
  4. 缓冲池java_了解Java缓冲池
  5. 海贼王为什么画风突变_什么是突变测试?
  6. ant 路径_在Ant中显示路径
  7. JMetro版本11.5.11和8.5.11发布
  8. Spring Data JPA教程
  9. netbeans7.4_NetBeans 7.4 Beta提示警告无效的异常处理
  10. 您真的需要instanceof吗?