基本原則
單元測試,是指對程序中的最小可測試單元進(jìn)行驗(yàn)證,在Java中的話,就是類。其有兩個(gè)目的:
- 驗(yàn)證程序?qū)崿F(xiàn)的邏輯是否與設(shè)計(jì)的邏輯正確
- 在涉及到代碼修改時(shí),用單元測試去保證原有功能不被破壞,
而一個(gè)好的單元測試應(yīng)該具備以下FIRST 原則和AIR原則中的任何一條:
- 單元測試的FIRST 規(guī)則
- Fast 快速原則,測試的速度要比較快,
- Independent 獨(dú)立原則,每個(gè)測試用例應(yīng)該互不影響,不依賴于外部資源。
- Repeatable 可重復(fù)原則,同一個(gè)測試用例多次運(yùn)行的結(jié)果應(yīng)該是相同的
- Self-validating 自我驗(yàn)證原則,單元測試可以自動(dòng)驗(yàn)證,并不需要手工干預(yù)
- Thorough 及時(shí)原則 單元測試必須即使進(jìn)行編寫,更新,維護(hù)。保證測試用例隨著業(yè)務(wù)動(dòng)態(tài)變化
- AIR原則
- Automatic 自動(dòng)化原則 單元測試應(yīng)該是自動(dòng)運(yùn)行,自動(dòng)校驗(yàn),自動(dòng)給出結(jié)果。
- Independent 獨(dú)立原則 單元測試應(yīng)該獨(dú)立運(yùn)行,吧相互之間無依賴,對外無依賴,多次運(yùn)行之間無依賴。
- Repeatable 可重復(fù)原則 單元測試是可重復(fù)運(yùn)動(dòng)的,每次的結(jié)果都穩(wěn)定可靠。
一個(gè)整套完善的單元測試可以保障后續(xù)的增添功能時(shí),程序迭代過程中,代碼的邏輯正確性。驗(yàn)證程序的輸入和輸出與最初設(shè)計(jì)一致。這對后續(xù)的集成測試等會(huì)提供巨大的幫助。同時(shí)也會(huì)有利于集成測試的順利進(jìn)行。
單元測試粒度
目前一些代碼掃描工具基本都給出了最低30%的單元測試代碼覆蓋率。這是一個(gè)最低限度,然而一個(gè)項(xiàng)目的覆蓋率,要綜合去考慮項(xiàng)目成本,人員安排等等因素。
關(guān)于單元測試的粒度,可以總結(jié)以下幾點(diǎn):
- DAO層的單元測試: 對于基本CRUD,可以考慮跳過這一部分單元測試,而一些比較復(fù)雜的動(dòng)態(tài)更新、查詢等操作,建議用使用H2去做模擬單元測試。
- Service層的單元測試:基本上一個(gè)Service里面肯定會(huì)依賴很多其他的service(此處也建議將成員變量通過構(gòu)造方法進(jìn)行注入,以便于單元測試去Mock),此時(shí)建議我們將依賴其他service的方法用Mock替代,Service里面的一些數(shù)據(jù)庫的操作也進(jìn)行Mock。這樣可以保證service測試的獨(dú)立性,不過對于邏輯復(fù)雜的方法可能要花很多時(shí)間在Mock上面。 如果發(fā)現(xiàn)需要Mock的方法過多,那么可能就需要考慮將要測試的方法是不是需要重構(gòu)。
- Controller(API)層的單元測試:主要著重測試HTTP status在 200,400,500 等情況下的異常處理,request及response的轉(zhuǎn)換等。由于其余部分的代碼測試都已經(jīng)在其對應(yīng)的單元測試覆蓋,那么此時(shí)可以Mock絕大部分Serivce層中的方法。
- 一般工具類的單元測試:一些工具類里面包含了比較多的邏輯,所以需要盡可能考慮多種情況下測試用例。
單元測試示例
單元測試可使用的第三方工具非常多,然而我們不可能精通每一個(gè),只有在不斷 的使用提高熟練程度。 對于SpringBoot而言可以直接引入spring-boot-starter-test , 它將會(huì)引入JUnit、Spring Test、AssertJ、Hamcrest(匹配對象) Mockito、JSONassert、JsonPath等工具庫。
- 如下是一個(gè)關(guān)于用戶登錄功能的測試示例
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserBizImplTest extends ParentTest {
@Resource
UserBiz userBiz;
@MockBean
UserService userService;
private static List<User> userList = new ArrayList<>();
@BeforeClass
public static void setUp() throws Exception {
System.out.println("setUp");
// mock userService
int code = 102;
for (int i = 0; i < 10; i++) {
User user = new User();
user.setId((long) i + 1);
user.setUsername("Tom" + code);
user.setPassword("P@ssw0rd");
userList.add(user);
code++;
}
}
@AfterClass
public static void tearDown() throws Exception {
System.out.println("tearDown");
}
@Test
public void test_08_login() {
// scenario 1: login success
// input data
LoginInfoDTO loginInfoDTO = new LoginInfoDTO();
loginInfoDTO.setPassword("P@ssw0rd");
loginInfoDTO.setUsername("admin");
// mock data & methods
String dbPassword = CommonUtils.encode(loginInfoDTO.getPassword());
User user = new User();
user.setUsername("admin");
user.setPassword(dbPassword);
Mockito.when(userService.getOne(Mockito.any())).thenReturn(user);
// verify
try {
String token = userBiz.login(loginInfoDTO);
System.out.println(token);
} catch (Exception e) {
TestCase.fail();
}
// scenario 2: user not exist
// input data
loginInfoDTO.setUsername("notExistedUser");
//mock data & methods
Mockito.when(userService.getOne(Mockito.any())).thenReturn(null);
// verify
try {
String token = userBiz.login(loginInfoDTO);
System.out.println(token);
TestCase.fail();
} catch (UserAuthorityException ignored) {
System.out.println(ignored);
} catch (Exception e) {
TestCase.fail();
}
// scenario 3: password error
// input data
loginInfoDTO.setUsername("admin");
loginInfoDTO.setPassword("wrongPassword");
//mock data & methods
Mockito.when(userService.getOne(Mockito.any())).thenReturn(user);
// verify
try {
String token = userBiz.login(loginInfoDTO);
System.out.println(token);
TestCase.fail();
} catch (UserAuthorityException ignored) {
System.out.println(ignored);
} catch (Exception e) {
TestCase.fail();
}
}
}
- 代碼詳解:
這段代碼是一個(gè)單元測試類,用于測試`UserBizImpl`類的登錄功能。讓我們逐步解釋這段代碼:
1. `@FixMethodOrder(MethodSorters.NAME_ASCENDING)`: 這是JUnit的一個(gè)注解,用于指定測試方法的執(zhí)行順序。在這里,它指定了按照方法名的字母順序(升序)執(zhí)行測試方法。
2. `public class UserBizImplTest extends ParentTest`: 這是測試類的定義,它繼承了一個(gè)名為`ParentTest`的父類。
3. `@Resource UserBiz userBiz`: 使用`@Resource`注解注入了`UserBiz`類的一個(gè)實(shí)例,即要被測試的業(yè)務(wù)類。
4. `@MockBean UserService userService`: 使用`@MockBean`注解模擬了`UserService`類的實(shí)例,這個(gè)類是`UserBizImpl`的一個(gè)依賴。
5. `private static List<User> userList = new ArrayList<>();`: 定義了一個(gè)靜態(tài)的`User`對象列表,用于存儲(chǔ)模擬數(shù)據(jù)。
6. `@BeforeClass public static void setUp() throws Exception`: 在所有測試方法運(yùn)行之前執(zhí)行的方法。在這里,它用于準(zhǔn)備測試數(shù)據(jù),模擬`UserService`的行為。
7. `@AfterClass public static void tearDown() throws Exception`: 在所有測試方法運(yùn)行之后執(zhí)行的方法。在這里,它用于清理測試數(shù)據(jù)或資源。
8. `@Test public void test_08_login()`: 這是一個(gè)測試方法,用于測試用戶登錄功能。
9. 在`test_08_login`方法中:
?? - 首先,定義了三個(gè)測試場景:
???? - 登錄成功的情況
???? - 用戶不存在的情況
???? - 密碼錯(cuò)誤的情況
?? - 對于每個(gè)場景,設(shè)置了輸入數(shù)據(jù)(用戶名和密碼)和模擬的行為(使用`Mockito.when`方法模擬了`UserService`的`getOne`方法的返回值)。
?? - 使用`userBiz`對象調(diào)用登錄方法,并對返回結(jié)果進(jìn)行驗(yàn)證。在成功登錄和用戶不存在的情況下,驗(yàn)證登錄成功;在密碼錯(cuò)誤的情況下,驗(yàn)證拋出了`UserAuthorityException`異常。
這段代碼通過模擬`UserService`的行為,測試了`UserBizImpl`類的登錄功能在不同場景下的行為。文章來源:http://www.zghlxwxcb.cn/news/detail-842261.html
參考
SpringBoot單元測試指南 - 知乎文章來源地址http://www.zghlxwxcb.cn/news/detail-842261.html
到了這里,關(guān)于Springboot Unit Test(單元測試)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!