如何进行Java 单元测试
什么是單元測試
維基百科中是這樣描述的:在計算機編程中,單元測試又稱為模塊測試,是針對程序模塊來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對于面向對象編程,最小單元就是方法,包括基類、抽象類、或者派生類中的方法。
單元測試和集成測試的區別
回到測試的本質來看,測試工作就是模擬真實環境,在代碼正式上線前進行驗證的工作,即使沒有任何工具和方法,這項工作也能夠通過人工操作來手動完成。但這種方式顯然不符合軟件從業者的習慣,于是開始出現了各種各樣的自動化測試方法,框架和工具。單元測試和集成測試使用的測試框架和工具大部分是相同的,而社區中對集成測試的介紹不盡相同,導致很多看過不同文章的同學對這兩種測試的認知存在爭議。首先需要達成一致的是,無論是單元測試還是集成測試,它們都是自動化測試。為了更好地區分,我們可以這樣理解:和生產代碼以及單元測試代碼在同一個代碼倉庫中,由開發同學自己編寫的,對外部環境(數據庫、文件系統、外部系統、消息隊列等)有真實調用的測試就是集成測試。下表中也從各種角度來對比了單元測試、集成測試和系統級別測試(包括端到端測試、鏈路測試、自動化回歸測試、UI測試等)的區別。
實例:
以下是一個簡單的服務代碼
@Service public class UserService {/** 定義依賴對象 *//** 用戶DAO */@Autowiredprivate UserDAO userDAO;/*** 查詢用戶* * @param companyId 公司標識* @param startIndex 開始序號* @param pageSize 分頁大小* @return 用戶分頁數據*/public PageDataVO<UserVO> queryUser(Long companyId, Long startIndex, Integer pageSize) {//入參校驗if(ValidationUtil.validate(companyId)){throw new InvalidRequestException(companyId, "Invalid company Id");}// 查詢用戶數據// 查詢用戶數據: 總共數量Long totalSize = userDAO.countByCompany(companyId);// 查詢接口數據: 數據列表List<UserVO> dataList = null;if (NumberHelper.isPositive(totalSize)) {dataList = userDAO.queryByCompany(companyId, startIndex, pageSize);}// 返回分頁數據return new PageDataVO<>(totalSize, dataList);} }單元測試
@RunWith(MockitoJUnitRunner.class) public class UserServiceTest {/** 定義靜態常量 *//** 資源路徑 */private static final String RESOURCE_PATH = "testUserService/";/** 模擬依賴對象 *//** 用戶DAO */@Mockprivate UserDAO userDAO;/** 定義測試對象 *//** 用戶服務 */@InjectMocksprivate UserService userService;/*** 測試: 查詢用戶-無數據*/@Testpublic void testQueryUser_Succeed_NoData() {// 模擬依賴方法// 模擬依賴方法: userDAO.countByCompanyLong companyId = 123L;Long startIndex = 90L;Integer pageSize = 10;Mockito.doReturn(0L).when(userDAO).countByCompany(companyId);// 調用測試方法String path = RESOURCE_PATH + "testQueryUserWithoutData/";PageDataVO<UserVO> pageData = userService.queryUser(companyId, startIndex, pageSize);String text = ResourceHelper.getResourceAsString(getClass(), path + "pageData.json");Assert.assertEquals("分頁數據不一致", text, JSON.toJSONString(pageData));// 驗證依賴方法// 驗證依賴方法: userDAO.countByCompanyMockito.verify(userDAO).countByCompany(companyId);// 驗證依賴對象Mockito.verifyNoMoreInteractions(userDAO);}/*** 測試: 查詢用戶-有數據*/@Testpublic void testQueryUser_Succeed_WithData() {// 模擬依賴方法String path = RESOURCE_PATH + "testQueryUserWithData/";// 模擬依賴方法: userDAO.countByCompanyLong companyId = 123L;Mockito.doReturn(91L).when(userDAO).countByCompany(companyId);// 模擬依賴方法: userDAO.queryByCompanyLong startIndex = 90L;Integer pageSize = 10;String text = ResourceHelper.getResourceAsString(getClass(), path + "dataList.json");List<UserVO> dataList = JSON.parseArray(text, UserVO.class);Mockito.doReturn(dataList).when(userDAO).queryByCompany(companyId, startIndex, pageSize);// 調用測試方法PageDataVO<UserVO> pageData = userService.queryUser(companyId, startIndex, pageSize);text = ResourceHelper.getResourceAsString(getClass(), path + "pageData.json");Assert.assertEquals("分頁數據不一致", text, JSON.toJSONString(pageData));// 驗證依賴方法// 驗證依賴方法: userDAO.countByCompanyMockito.verify(userDAO).countByCompany(companyId);// 驗證依賴方法: userDAO.queryByCompanyMockito.verify(userDAO).queryByCompany(companyId, startIndex, pageSize);// 驗證依賴對象Mockito.verifyNoMoreInteractions(userDAO);}@Testpublic void testQueryUser_Fail_WithBadInput() {}} // void 方法可以用Argument Captor 驗證單元測試帶來的好處有:
單測成本低,速度快。
單測是最佳的、自動化的、可執行的文檔。
測試的要訣是:測試你最擔心出錯的部分,這樣你就能從測試工作中得到最大的利益,100%覆蓋率的單測會逐漸消磨開發人員對測試的耐心和好感。
單測驅動設計,提升代碼簡潔度,確保安全重構,代碼修改后,單測仍然能通過,能夠增強開發者的信心。
快速反饋,更快的發現問題。
定位缺陷比集成測試更快更準確,降低修復成本。
單元測試開發規范
【強制】好的單元測試必須遵守AIR原則。說明:單元測試在線上運行時,感覺像空氣(AIR)一樣感覺不到,但在測試質量的保障上,卻是非常關鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重復執行的特點。
A:Automatic(自動化)
I:Independent(獨立性)
R:Repeatable(可重復)
【強制】單元測試應該是全自動執行的,并且非交互式的。測試用例通常是被定期執行的,執行過程必須完全自動化才有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。單元測試中不準使用System.out來進行人肉驗證,必須使用assert來驗證。
【強制】保持單元測試的獨立性。為了保證單元測試穩定可靠且便于維護,單元測試用例之間決不能互相調用,也不能依賴執行的先后次序。反例:method2需要依賴method1的執行,將執行結果做為method2的參數輸入。
【強制】單元測試是可以重復執行的,不能受到外界環境的影響。說明:單元測試通常會被放到持續集成中,每次有代碼check in時單元測試都會被執行。如果單測對外部環境(網絡、服務、中間件等)有依賴,容易導致持續集成機制的不可用。正例:為了不受外界環境影響,要求設計代碼時就把SUT的依賴改成注入,在測試時用spring 這樣的DI框架注入一個本地(內存)實現或者Mock實現。
【強制】對于單元測試,要保證測試粒度足夠小,有助于精確定位問題。單測粒度至多是類級別,一般是方法級別。說明:只有測試粒度小才能在出錯時盡快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域。
【強制】核心業務、核心應用、核心模塊的增量代碼確保單元測試通過。說明:新增代碼及時補充單元測試,如果新增代碼影響了原有單元測試,請及時修正。
【強制】單元測試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業務代碼目錄下。說明:源碼編譯時會跳過此目錄,而單元測試框架默認是掃描此目錄。
【推薦】單元測試的基本目標:語句覆蓋率達到70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到100%。說明:在工程規約>應用分層中提到的DAO層,Manager層,可重用度高的Service,都應該進行單元測試。
【推薦】編寫單元測試代碼遵守BCDE原則,以保證被測試模塊的交付質量。
B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。
C:Correct,正確的輸入,并得到預期的結果。
D:Design,與設計文檔相結合,來編寫單元測試。
E:Error,強制錯誤信息輸入(如:非法數據、異常流程、非業務允許輸入等),并得到預期的結果。
【推薦】對于數據庫相關的查詢,更新,刪除等操作,不能假設數據庫里的數據是存在的,或者直接操作數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據。反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增加一行作為刪除目標,但是這一行新增數據并不符合業務插入規則,導致測試結果異常。
【推薦】和數據庫相關的單元測試,可以設定自動回滾機制,不給數據庫造成臟數據。或者對單元測試產生的數據有明確的前后綴標識。正例:在企業智能事業部的內部單元測試中,使用ENTERPRISE_INTELLIGENCE_UNIT_TEST_的前綴來標識單元測試相關代碼。
【推薦】對于不可測的代碼在適當時機做必要的重構,使代碼變得可測,避免為了達到測試要求而書寫不規范測試代碼。
【推薦】在設計評審階段,開發人員需要和測試人員一起確定單元測試范圍,單元測試最好覆蓋所有測試用例(UC)。
【推薦】單元測試作為一種質量保障手段,在項目提測前完成單元測試,不建議項目發布后補充單元測試用例。
【參考】為了更方便地進行單元測試,業務代碼應避免以下情況:
構造方法中做的事情過多。
存在過多的全局變量和靜態方法。
存在過多的外部依賴。
存在過多的條件語句。說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。
【參考】不要對單元測試存在如下誤解:
那是測試同學干的事情。本文是開發規約,凡是本文出現的內容都是與開發同學強相關的。
單元測試代碼是多余的。軟件系統的整體功能是否正常,與各單元部件的測試正常與否是強相關的。
單元測試代碼不需要維護。一年半載后,那么單元測試幾乎處于廢棄狀態。
單元測試與線上故障沒有辯證關系。好的單元測試能夠最大限度地規避線上故障。
總結
以上是生活随笔為你收集整理的如何进行Java 单元测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 默纳克系统电梯服务器怎么封超载,默纳克系
- 下一篇: 关于前端url加密方式总结 (Vue-c