如何更好的做單元測試並用它來提升程式碼質量(下)
現代化的spring-test使用方式
以下例子可以在
javaconfig-spring-test
中找到。
在classic-spring-test
中演示的單元測試,還是用配置檔案的方式,但是從Spring4之後,官方就鼓勵使用Java的方式對spring進行配置,而不是用以前那樣的xml配置形式了,因此我們基於註解可以來簡化單元測試的編寫,我們稱之為現代化的spring-test
方式。
修改單元測試
測試不用繼承AbstractJUnit4SpringContextTests
,通過註解即可,然後對於bean的配置,可以通過Java配置風格完成
註解
使用RunWith
和ContextConfiguration
配置即可將一個類宣告為支援Spring容器的測試用例。
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MemberJavaConfigTest.MemberServiceConfig.class)
public class MemberJavaConfigTest {
}
註解 | 說明 |
---|---|
RunWith |
該註解是junit 提供的,表示用那種方式來執行這個測試,這裡是SpringRunner ,由spring-test 提供 |
ContextConfiguration |
對測試的Spring容器的配置,比如:配置的位置等 |
配置與示例
通過註解可以宣告按照何種方式去執行測試,以及測試的Spring容器如何組裝,但是還或缺在Spring容器中如何配置Bean,以前這是通過xml來進行配置的。
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = MemberJavaConfigTest.MemberServiceConfig.class)
public class MemberJavaConfigTest {
@Autowired
private MemberService memberService;
@Test
public void insert_member() {
System.out.println(memberService.insertMember("windowsxp", "abc123"));
Assert.assertNotNull(memberService.insertMember("windowsxp", "abc123"));
}
@Configuration
static class MemberServiceConfig {
@Bean
public MemberService memberService(UserDAO userDAO) {
MemberServiceImpl memberService = new MemberServiceImpl();
memberService.setUserDAO(userDAO);
return memberService;
}
@Bean
public UserDAO userDAO() {
UserDAO mock = Mockito.mock(UserDAO.class);
Mockito.when(mock.insertMember(Mockito.any())).thenReturn(System.currentTimeMillis());
return mock;
}
}
}
可以看到只需要有一個類,被註解了Configuration
,該類就是一個配置型別,而這種Java Config Style已經是Spring官方推薦的方式了。
Bean
註解類似xml中的bean
標籤,這裡配置了兩個Bean一個MemberService
的實現,另外一個是mock的UserDAO
。其中對MemberService
的配置需要依賴UserDAO
。
剩下的測試過程就和之前classic-spring-test
完全一致了,可以看到新的方式沒有了惱人的xml配置,變得更加直接和高效。
SpringBoot環境下的測試方法
以下例子可以在
spring-boot-test
中找到。
Spring
框架實際上是依靠SpringBoot完成了續命,由它煥發了第二春,開啟了一個全新的戰場。在今天微服務大放異彩的環境下,針對SpringBoot的測試也會有所不同。
SpringBoot
實際是用來啟動你的應用,所以它會有配置以及一系列約定大於配置的環境準備,所以需要依賴spring-boot-test
支援來完成單元測試。
修改單元測試
如果需要在單元測試啟動時啟動SpringBoot,需要做一下相關的配置,增加一些註解。
依賴
增加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
註解
和JavaConfig
的方式非常類似,通過註解可以宣告該測試是SpringBootTest
,並且可以指定執行的SpringBoot
容器的配置。
@SpringBootTest(classes = SpringBootMemberTest.Config.class)
@TestPropertySource(locations = "classpath:test-application.properties")
@RunWith(SpringRunner.class)
public class SpringBootMemberTest {
註解 | 說明 |
---|---|
SpringBootTest |
描述了該SpringBoot單元測試是根據哪個配合來啟動容器 |
TestPropertySource |
應用的配置使用哪個 |
配置
通過註解可以宣告按照何種方式去執行測試,以及測試的Spring容器如何組裝,還或缺在Spring容器中如何配置Bean。
@SpringBootTest(classes = SpringBootMemberTest.Config.class)
@TestPropertySource(locations = "classpath:test-application.properties")
@RunWith(SpringRunner.class)
public class SpringBootMemberTest {
@Autowired
private Environment env;
@MockBean
private UserDAO userDAO;
@Autowired
private MemberService memberService;
@Test
public void environment() {
Assert.assertEquals("Alibaba", env.getProperty("brand-owner.name"));
}
@Before
public void init() {
Mockito.when(userDAO.insertMember(Mockito.any())).thenReturn(System.currentTimeMillis());
}
@Test
public void insert_member() {
System.out.println(memberService.insertMember("windowsxp", "abc123"));
Assert.assertNotNull(memberService.insertMember("windowsxp", "abc123"));
}
@Configuration
static class Config {
@Bean
public MemberService memberService(UserDAO userDAO) {
MemberServiceImpl memberService = new MemberServiceImpl();
memberService.setUserDAO(userDAO);
return memberService;
}
}
}
可以看到新增了一個註解MockBean
,這個用來幫助我們建立一個Mock的UserDAO
,而不用通過編碼來進行建立,回憶之前在classic
以及javaconfig
中的Mock方式,都需要呼叫Mockito.mock(Class type)
方法來建立一個Mock物件,而在SpringBootTest
中就不需要了,直接在成員變數上增加MockBean
的註解就可以了。
同時可以看到在單元測試中增加了一個注入屬性,Environment
,它代表Spring執行的環境,可以從中獲取配置,以下是test-application.application
中的內容:
brand-owner.name=Alibaba
brand-owner.company=Alibaba-inc.
在environment
測試方法中,可以訪問測試的配置內容,從這裡可以看到SpringBootTest
在spring-test
基礎上,除了啟動一個Spring容器,還準備好了一個SpringBoot
執行時環境。
但是從側面上講,使用SpringBootTest
就依賴了執行時環境,這不是一個好的選擇,所以在大多數情況下,對於程式碼的單元測試spring-test
就可以完全應對。
單元測試覆蓋率
就像刻意的刷分數一樣,單元測試覆蓋率也是一個我們追求的目標,當單元測試行覆蓋率超過70%的時候,整個專案的質量會很不錯。持續穩定的單元測試覆蓋率,會保障一個應用一直處於較穩定的狀態,後續投入維護的資源會降低。
在不少IDE中,如:IDEA,都內建了統計單元測試的工具,只需要按照package
執行測試即可,在這裡我們不依賴具體的IDE,而是用maven外掛來做。
jacoco
該外掛對java8的語法支援較好,在pom
檔案中增加配置。
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.1</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>post-unit-test</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
當執行mvn test
時會生產單元測試覆蓋率報告。
位置一般在專案的
target/jacoco-ut
目錄下。
覆蓋率
開啟目錄下的index.html
可以看到各個類的覆蓋率情況。
<img src="https://raw.githubusercontent.com/weipeng2k/mockito-sample/master/resources/chapter6-1.png" />
缺失路徑
點選到對應的package
中的類,可以檢視缺失的測試路徑,這樣就可以指導哪些分支沒有納入單測。
<img src="https://raw.githubusercontent.com/weipeng2k/mockito-sample/master/resources/chapter6-2.png" />
測試驅動開發簡介
測試驅動開發的基本思想就是在開發功能程式碼之前,先編寫測試程式碼,然後只編寫使測試通過的功能程式碼,從而以測試來驅動整個開發過程的進行。這有助於編寫簡潔可用和高質量的程式碼,有很高的靈活性和健壯性,能快速響應變化,並加速開發過程。
測試開發驅動模式
測試驅動開發的基本過程如下:
- 快速新增一個測試
- 執行所有的測試(有時候只需要執行一個或一部分),發現新增的測試不能通過
- 做一些小小的改動,儘快地讓測試程式可執行,為此可以在程式中使用一些不合情理的方法
- 執行所有的測試,並且全部通過
- 重構程式碼,以消除重複設計,優化設計結構
簡單來說,就是不可執行/可執行/重構——這正是測試驅動開發的口號。
可取之處
測試驅動開發能夠讓程式碼上生產環境之前,能夠以使用者的角度審視編寫的程式碼:
- 如果程式碼難測,那就是對問題的分析還沒有到位
- 如果大量的Mock,那就是依賴過於複雜
除了能夠通過反向刺激讓我們看到程式碼的不足,它還能以使用者的角度去看:
- 這個方法命名是否夠妥帖
- 別人用這個函式會誤用嗎
- 這個類是不是承擔了過多的職責
相關文章
- 如何編寫優秀的測試程式碼|單元測試
- 提高程式碼質量——使用Jest和Sinon給已有的程式碼新增單元測試
- 如何提升 Web 應用的程式碼質量Web
- 精準測試:如何判斷兩次測試中哪次的質量更好?
- 我是如何在公司專案中使用ESLint來提升程式碼質量的EsLint
- 程式碼重構與單元測試——重構1的單元測試(四)
- 如何做程式碼單元壓力測試?【JWordPress前臺專案實戰】
- 寫好測試,提升應用質量
- 如何在spring環境中做單元測試Spring
- 你寫的前端程式碼有做過單元測試嗎?使用什麼工具?怎麼測試的?前端
- Laravel 單元測試實戰(2)- 編寫實際功能並讓程式碼測試透過Laravel
- Mockito提升單元測試覆蓋率Mockito
- 面試題:如何權量測試版本的質量?面試題
- 程式碼重構與單元測試(一)
- 高效易用的C++單元測試框架:輕鬆構建高質量程式碼C++框架
- 程式碼重構與單元測試——測試專案(二)
- 單元測試:單元測試中的mockMock
- 單元測試-一份如何寫好單元測試的參考
- 測試工作在有效推動產品質量提升方面可以做哪些內容
- 如何寫出好的單元測試
- 吃透單一職責原則,100倍效果提升程式碼質量
- 人工智慧中的線性代數:如何理解並更好地應用它人工智慧
- 如何利用大模型提升前端研發效率和程式碼質量大模型前端
- 程式設計師必看:如何充分利用程式碼審查提升你的程式碼質量?程式設計師
- 為程式碼編寫穩定的單元測試 [Go]Go
- 利用SonarCloud和Azure DevOps提升程式碼質量Clouddev
- 測試 之Java單元測試、Android單元測試JavaAndroid
- 如何寫好單元測試
- 掌握這些程式碼安全檢視方法,提升你的程式碼質量
- 從零開始做Vue前端架構(6)單元測試 & 程式碼覆蓋率Vue前端架構
- 單元測試怎麼做的一些思考
- 如何優雅的寫單元測試?
- 如何執行指定的單元測試
- 如何單元測試Java的private方法Java
- C&C++程式碼單元整合測試培訓C++
- 使用Gradle做Java程式碼質量檢查GradleJava
- 單元測試如何測試私有方法_1
- 如何測試 Flutter 應用? ー 單元測試Flutter