一位客戶最近請求緊急釋放一些代碼,以出于法律原因在其網站的相應頁面上在屏幕上顯示一條消息。
方案是,如果一條信息存在于數據庫中,則應該在屏幕上顯示一條信息–這是一個非常簡單的方案,可以用幾行簡單的代碼行覆蓋。 但是,在急于編寫代碼的過程中,開發人員沒有編寫任何單元測試,并且代碼中包含一個直到補丁到達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