3g網(wǎng)站開發(fā)怎么在百度上投放廣告
一、做好單測,慢即是快
對于單元測試的看法,業(yè)界同仁理解多有不同,尤其是在業(yè)務(wù)變化快速的互聯(lián)網(wǎng)行業(yè),通常的問題主要有,必須要做嗎?做到多少合適?現(xiàn)在沒做不也挺好的嗎?甚至一些大佬們也是存在不同的看法。我們?nèi)缦孪瓤匆唤M數(shù)字:
“在 STICKYMINDS 網(wǎng)站上的一篇名為 《 The Shift-Left Approach to Software Testing 》 的文章中提到,假如在編碼階段發(fā)現(xiàn)的缺陷只需要 1 分鐘就能解決,那么單元測試階段需要 4 分鐘,功能測試階段需要 10 分鐘,系統(tǒng)測試階段需要 40 分鐘,而到了發(fā)布之后可能就需要 640 分鐘來修復(fù)?!薄獊碜灾蹙W(wǎng)站節(jié)選
對于這些數(shù)字的準(zhǔn)確性我們暫且持保留意見。大家可以想想我們實際中遇到的線上問題大概需要消耗多少工時,除了要快速找到bug,修復(fù)bug上線,還要修復(fù)因為bug引發(fā)的數(shù)據(jù)問題,最后還要復(fù)盤,看后續(xù)如何能避免線上問題,這樣下來保守估計應(yīng)該不止幾人日吧。所以這篇文章作者所做的調(diào)研數(shù)據(jù)可信度還是很高的,
缺陷發(fā)現(xiàn)越到交付流程的后端,其修復(fù)成本就越高。
有人說寫單測太耗費時間了,會延長交付時間,其實不然:
1)研測同學(xué)大量的往返交互比編寫單測的時間要長的多,集成測試的時間被拖長。
2)沒經(jīng)過單測的代碼bug會多,開發(fā)同學(xué)忙于修復(fù)各種bug,對代碼debug跟蹤調(diào)試找問題,也要消耗很多精力。
3)后期的線上問題也會需要大量的精力去彌補。
如果有了單元測試的代碼,且能實現(xiàn)一個較高的行覆蓋率,則可以將問題盡可能消滅在開發(fā)階段。同時有了單測代碼的積累,每次代碼改動后可以提前發(fā)現(xiàn)這次改動引發(fā)的其他關(guān)聯(lián)問題,上線也更加放心。單測雖然使提測變慢了一些,軟件質(zhì)量更加有保障,從而節(jié)省了后續(xù)同學(xué)的精力,從整體看其實效率更高。
所以做好單測,慢即是快。
做為一名開發(fā)者我們需要對自己的代碼質(zhì)量負(fù)責(zé),也更能體現(xiàn)我們開發(fā)者的工匠精神。
二、編寫單元測試
Junit5使用
maven依賴
<!-- Springboot提供的單測框架,提供一些單測工具支持,默認(rèn)支持Mockito、junit5 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.5.4</version>
</dependency><!-- 或單獨引入 -->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.7.2</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.9.0</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>3.9.0</version><scope>compile</scope>
</dependency>
Juint常用注解
單類示例
通過idea的Squaretest插件直接生成的測試類如下
@ExtendWith(MockitoExtension.class)
public class MockUserServiceTest {@Mockprivate UserManager mockUserManager;@InjectMocksprivate MockUserService mockUserService;@BeforeEachpublic void setUp() {mockUserService = new MockUserService(mockUserManager);}@Testpublic void testGetUserByAge() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, "name", 0),new User(1, "name", 0)));// Run the testfinal List<User> result = mockUserService.getUserByAge(0);// Verify the results}@Testpublic void testGetUserByAge_UserManagerReturnsNoItems() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, "name", 0), new User(1, "name", 1)));// Run the testfinal List<User> result = mockUserService.getUserByAge(0);// Verify the resultsassertThat(result).isEqualTo(Collections.emptyList());}
}
需注意Junit5.x?與Junit4.x 生成的測試類中,Junit4的測試類和測試方法必須要public關(guān)鍵字修改。
因為JUnit 4.x使用Java反射機制來查找和運行測試,而Java反射要求被訪問的類和方法必須是public的。
JUnit 5.x(也稱為Jupiter)在設(shè)計和實現(xiàn)上更加現(xiàn)代化,它引入了一些新的特性和改進,包括更靈活的測試發(fā)現(xiàn)機制。在JUnit 5.x中,測試類和測試方法的訪問修飾符要求更加寬松。
將測試方法和類聲明為public也有助于確保它們能夠被其他測試框架或工具(如Maven、Gradle、IDE等)正確地發(fā)現(xiàn)和運行。因此,在編寫JUnit測試時,即使JUnit 5.x允許更寬松的訪問修飾符,但將測試類和測試方法聲明為public仍然是一個好習(xí)慣。
springboot集成測試
springboot集成測試旨在驗證Spring Boot應(yīng)用程序的各個組件之間的交互和整體行為。集成測試非常重要,因為它可以幫助開發(fā)人員確保應(yīng)用程序在不同的環(huán)境中都能正常運行。通過集成測試,可以檢測應(yīng)用程序中的潛在問題,提高代碼的可靠性和穩(wěn)定性。
Mockito常用注解
@MockBean: 用于在 Spring Boot 測試環(huán)境中創(chuàng)建并注入一個 mock 的 bean。
用途:用于在 Spring Boot 測試環(huán)境中創(chuàng)建一個 mock 的 bean,并將其注入到 Spring 應(yīng)用程序上下文中。
特點:
適用于集成測試,特別是在使用 @SpringBootTest 注解的測試類中。
替換掉 Spring 容器中已有的 bean,或者添加一個新的 mock bean。
可以在測試類中直接使用 @Autowired 注解來注入這個 mock bean。
@Mock: 用于創(chuàng)建一個 mock 對象,但不將其注入到 Spring 應(yīng)用程序上下文中。
用途:用于創(chuàng)建一個 mock 對象,但不將其注入到 Spring 應(yīng)用程序上下文中。
特點:
適用于單元測試,特別是在不需要 Spring 上下文的測試中。
需要手動注入到測試類或方法中。
通常與 @InjectMocks 一起使用,以便將 mock 對象注入到被測試的類中。
@Spy: 用于創(chuàng)建一個部分 mock 對象,即一個真實的對象,但可以對其中的某些方法進行 mock。
用途:用于創(chuàng)建一個部分 mock 對象,即一個真實的對象,但可以對其中的某些方法進行 mock。
特點:
適用于需要調(diào)用真實對象的方法,同時對某些方法進行 mock 的場景。
可以使用 doReturn(...).when(...) 或 when(...).thenReturn(...) 來模擬方法的行為。
@InjectMocks: 用于創(chuàng)建一個被測試的類的實例,并將帶有 @Mock 或 @Spy 注解的 mock 對象注入到該實例中。
用途:用于創(chuàng)建一個被測試的類的實例,并將帶有 @Mock 或 @Spy 注解的 mock 對象注入到該實例中。
特點:
適用于需要將 mock 對象注入到被測試的類中的場景。
自動將 mock 對象注入到被測試類的構(gòu)造函數(shù)、字段或 setter 方法中。
集成示例
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockInjectServiceImplTest{ /*** 通過@MockBean的方式創(chuàng)建一個Mock的MockRpcService的Bean* 并將其注入到spring的上下文中*/@MockBeanprivate MockRpcService mockRpcService;@Resourceprivate MockInjectService mockInjectService;@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);ReflectionTestUtils.setField(mockInjectService, "systemEnv", "{\"key\", \"value\"}");when(mockRpcService.queryCardNo(anyString())).thenReturn("cardNo");/*** 1、when(...).thenReturn(...) 語法:* 這種語法在某些情況下可能會被 Mockito 誤認(rèn)為是在調(diào)用 toString 方法,特別是當(dāng) mockRpcService 對象的 toString 方法被重寫時。* 若在使用 Mockito 模擬這個接口時遇到了 WrongTypeOfReturnValue 異常,這通常意味著 Mockito 誤認(rèn)為你在調(diào)用 toString 方法而不是 queryMockResp 方法* 如果 mockRpcService 的 toString 方法返回 MockResp 類型,那么 Mockito 會拋出 WrongTypeOfReturnValue 異常。** 2、doReturn(...).when(...) 語法:* 這種語法更加明確,直接指定了方法的返回值,避免了類型不匹配的問題。適用于所有需要模擬方法返回值的場景。* 為了確保代碼的健壯性和可讀性,建議使用 doReturn(...).when(...) 語法。**** 下面的例子 使用when(...).thenReturn(...)時 拋出了org.mockito.exceptions.misusing.WrongTypeOfReturnValue:* MockResp cannot be returned by toString() toString() should return String* 這樣的異常。*///when(mockRpcService.queryMockResp(any(MockReq.class))).thenReturn(MockRespReflection.getMockResp());doReturn(MockRespReflection.getMockResp()).when(mockRpcService).queryMockResp(any(MockReq.class));doReturn(MockRespReflection.getMockRespList()).when(mockRpcService).getMockRespList(anyInt());}@Testpublic void testGeneralDeal(){// 執(zhí)行被測方法MockReq mockReqInput1 = new MockReq();mockReqInput1.setName("True-Person");MockResp mockRespResult = mockInjectService.generalDeal(mockReqInput1);log.info("mockResp:{}", JSON.toJSONString(mockRespResult));// 結(jié)果比對斷言Assert.assertNotNull(mockRespResult);}@Testpublic void testInjectDeal() {// 執(zhí)行被測方法MockReq mockReqInput1 = new MockReq();mockReqInput1.setName("True-Person");MockResp mockRespResult = mockInjectService.injectDeal(mockReqInput1);// 結(jié)果比對斷言Assert.assertNotNull(mockRespResult);}@Testpublic void testBeautifulDeal() {// Setupfinal MockResp mockResp = new MockResp("cardNo", 0, false);// Run the testfinal String result = mockInjectService.beautifulDeal(mockResp);// Verify the resultsassertThat(result).isEqualTo("result");}@Testpublic void testVoidDeal() {// Setupfinal MockReq req = new MockReq();req.setName("name");// Run the testmockInjectService.voidDeal(req);}
}
以上示例,通過@MockBean創(chuàng)建一個Rpc服務(wù)MockRpcService的mock實例,可以對接口的相關(guān)方法通過when(...).thenReturn(...) 或doReturn(...).when(...)語法mock。
需注意when(...).thenReturn(...)語法在某些情況下可能會被 Mockito 誤認(rèn)為是在調(diào)用 toString 方法,特別是當(dāng) mockRpcService 對象的 toString 方法被重寫時。
而doReturn(...).when(...) 語法更加明確,直接指定了方法的返回值,避免了類型不匹配的問題。適用于所有需要模擬方法返回值的場景。建議使用 doReturn(...).when(...) 語法
RPC接口MockRpcService
*** Mockito框架研發(fā)場景-RPC接口*/
public interface MockRpcService {String queryCardNo(String name);MockResp queryMockResp(MockReq req);public List<MockResp> getMockRespList(Integer age);
通過MockRespReflection類中的靜態(tài)方法 對RPC接口的方法數(shù)據(jù)進行mock,可以采用直接字符串、文件等形式提前準(zhǔn)備數(shù)據(jù),這里采用讀取文件形式進行mock
ublic class MockRespReflection {public static MockResp getMockResp() {try {String json = new String(Files.readAllBytes(Paths.get("src/test/file/xxx.json")));return JSON.parseObject(json, new TypeReference<MockResp>(){});} catch (IOException e) {throw new RuntimeException(e);}}/*** 從指定的JSON文件中讀取并解析MockResp對象列表** @return 解析后的MockResp對象列表* @throws RuntimeException 如果讀取文件時發(fā)生IO異常,將其包裝成RuntimeException拋出*/public static List<MockResp> getMockRespList() {try {// 讀取JSON文件內(nèi)容并解析為MockResp對象列表String json = new String(Files.readAllBytes(Paths.get("src/test/file/mockRespList.json")));return JSON.parseObject(json, new TypeReference<List<MockResp>>(){});} catch (IOException e) {// 捕獲IO異常并將其包裝成RuntimeException拋出throw new RuntimeException(e);}}
}
通過以上配置就可以進行springboot流程的集成測試。Spring Boot集成測試是確保應(yīng)用程序正確性和可靠性的重要手段。通過上述實踐,可以有效地進行集成測試并提高代碼質(zhì)量。
參考:
一臺不容錯過的Java單元測試代碼“永動機”-CSDN博客