一位客户最近请求紧急释放一些代码,以出于法律原因在其网站的相应页面上在屏幕上显示一条消息。
方案是,如果一条信息存在于数据库中,则应该在屏幕上显示一条信息–这是一个非常简单的方案,可以用几行简单的代码行覆盖。 但是,在急于编写代码的过程中,开发人员没有编写任何单元测试,并且代码中包含一个直到补丁到达UAT才发现的错误。 您可能会问到错误是什么,这是我们在职业生涯中某个时候都已经完成的事情:添加不需要的分号';'。 到一行的结尾。
我将使用我以前的“测试技术”博客中使用的AddressService场景来演示代码的重写特技双重版本,如下UML图所示:
在此演示中,功能已更改,但是逻辑和示例代码结构基本上保持不变。 在AddressService世界中,逻辑运行如下:
- 从数据库获取地址。
- 如果地址存在,则将其格式化并返回结果字符串。
- 如果地址不存在,则返回null。
- 如果格式化失败,请不要担心,并返回null。
重新编写的AddressService.findAddress(…)看起来像这样:
@Component
public class AddressService {private static final Logger logger = LoggerFactory.getLogger(AddressService.class);private AddressDao addressDao;public String findAddressText(int id) {logger.info("In Address Service with id: " + id);Address address = addressDao.findAddress(id);String formattedAddress = null;if (address != null);try {formattedAddress = address.format();} catch (AddressFormatException e) {// That's okay in this business case so ignore it}logger.info("Leaving Address Service with id: " + id);return formattedAddress;}@Autowired@Qualifier("addressDao")void setAddressDao(AddressDao addressDao) {this.addressDao = addressDao;}
}
您发现错误了吗? 当我查看代码时,我没有……为了以防万一,我在下面注释了一个屏幕截图:
演示一个琐碎的错误(任何人都可以犯的一个简单错误)的目的是强调编写一些单元测试的重要性,因为单元测试可以发现问题并节省大量的时间和费用。 当然,每个组织都不同,但是发布上面的代码会导致以下事件序列:
- 该应用程序已部署到开发,测试和UAT。
- 测试团队检查了修改后的屏幕是否可以正常工作并通过了更改。
- 其他屏幕经过回归测试,发现不正确。 记录所有失败的屏幕。
- 提出了紧急的错误报告。
- 该报告通过各个管理级别。
- 该报告已传递给我(我想念午餐),以调查可能的问题。
- 该报告将发送给团队的其他三名成员进行调查(四双眼睛比一只更好)
- 找到并修复了有问题的分号。
- 该代码在dev中进行了重新测试,并签入到源代码管理中。
- 该应用程序已构建并部署到开发,测试和UAT。
- 测试团队检查修改后的屏幕是否可以正常工作并通过更改。
- 其他屏幕经过回归测试并通过。
- 紧急修复程序已通过。
上述一系列事件显然浪费了大量的工时,浪费了大量现金,不必要地提高了人们的压力水平,并破坏了我们在客户中的声誉: 所有这些都是编写单元测试的很好理由。
为了证明这一点,我编写了三个缺失的单元测试,只花了我额外的15分钟开发时间,与浪费的工时相比,这似乎是开发人员时间的一种很好的利用。
@RunWith(UnitilsJUnit4TestClassRunner.class)
public class WhyToTestAddressServiceTest {private AddressService instance;@Mockprivate AddressDao mockDao;@Mockprivate Address mockAddress;/*** @throws java.lang.Exception*/@Beforepublic void setUp() throws Exception {instance = new AddressService();instance.setAddressDao(mockDao);}/*** This test passes with the bug in the code* * Scenario: The Address object is found in the database and can return a* formatted address*/@Testpublic void testFindAddressText_Address_Found() throws AddressFormatException {final int id = 1;expect(mockDao.findAddress(id)).andReturn(mockAddress);expect(mockAddress.format()).andReturn("This is an address");replay();instance.findAddressText(id);verify();}/*** This test fails with the bug in the code* * Scenario: The Address Object is not found and the method returns null*/@Testpublic void testFindAddressText_Address_Not_Found() throws AddressFormatException {final int id = 1;expect(mockDao.findAddress(id)).andReturn(null);replay();instance.findAddressText(id);verify();}/*** This test passes with the bug in the code* * Scenario: The Address Object is found but the data is incomplete and so a* null is returned.*/@Testpublic void testFindAddressText_Address_Found_But_Cant_Format() throws AddressFormatException {final int id = 1;expect(mockDao.findAddress(id)).andReturn(mockAddress);expect(mockAddress.format()).andThrow(new AddressFormatException());replay();instance.findAddressText(id);verify();}
}
最后,冒着自鸣得意的风险,我不得不承认,尽管在这种情况下该错误不是我的,但在我学会编写单元测试之前,我过去曾大范围地发布了类似的错误……
可从GitHub上获得源代码:
git://github.com/roghughe/captaindebug.git
参考: 为什么要编写单元测试-来自JCG合作伙伴 Roger Hughes的测试技巧8 ,位于Captain Debug's Blog中 。
相关文章 :
- 测试技巧–不编写测试
- 端到端测试的滥用–测试技术2
- 您应该对什么进行单元测试? –测试技术3
- 常规单元测试和存根–测试技术4
- 使用模拟的单元测试–测试技术5
- 为旧版代码创建存根–测试技术6
- 有关为旧版代码创建存根的更多信息–测试技术7
- 一些定义–测试技术9
翻译自: https://www.javacodegeeks.com/2011/12/why-you-should-write-unit-tests-testing.html