目前最主流的單元測試框架是junit,其中spring boot 1.x系列主要使用junit 4,spring boot 2.x主要使用junit 5;mock類和打樁的主要框架是mockito,主要有1.x(spring boot 1.x依賴),2.x(spring boot 2.0, 2.1依賴),3.x(spring boot 2.2依賴)三個版本。
0、關於單元測試首先需要理解的是的,單元測試不能代替接口測試,前者是開發的事情,后者是開發為輔、測試為主。其目的是為了驗證某個方法自身的邏輯沒有問題、而沒有職責驗證其依賴的服務是否存在問題。因此,單元測試應該是很輕量的,甚至都不應該依賴spring環境,不需要啟動servlet容器,否則就成了自動化半集成測試,所以簡單的增刪改查不適合作為單元測試的對象。
1、參考https://www.cnblogs.com/Maoscn/p/10313660.html,安裝Junit4插件。
2、復習下junit中的注解。
@BeforeClass:針對所有測試,只執行一次,且必須為static void
@Before:初始化方法,執行當前測試類的每個測試方法前執行。
@SpringBootTest:獲取啟動類、加載配置,確定裝載Spring Boot,如果找不到@SpringBootConfiguration啟動類將運行出錯;
@Test:測試方法,在這里可以測試期望異常和超時時間
@After:釋放資源,執行當前測試類的每個測試方法后執行
@AfterClass:針對所有測試,只執行一次,且必須為static void
@Ignore:忽略的測試方法(只在測試類的時候生效,單獨執行該測試方法無效)
@RunWith:標識為JUnit的運行環境 ,缺省值 org.junit.runner.Runner,也可以是JUnit4.class。
一個單元測試類執行順序為:
@BeforeClass
–> @Before
–> @Test
–> @After
–> @AfterClass
每一個測試方法的調用順序為:
@Before
–> @Test
–> @After
斷言測試
斷言測試也就是期望值測試,是單元測試的核心之一也就是決定測試結果的表達式,Assert對象中的斷言方法:
- Assert.assertEquals 對比兩個值相等
- Assert.assertNotEquals 對比兩個值不相等
- Assert.assertSame 對比兩個對象的引用相等
- Assert.assertArrayEquals 對比兩個數組相等
- Assert.assertTrue 驗證返回是否為真
- Assert.assertFlase 驗證返回是否為假
- Assert.assertNull 驗證null
- Assert.assertNotNull 驗證非null
除了常規的測試外,JUnit還通過其它特性的測試。
超時測試
如果一個測試用例比起指定的毫秒數花費了更多的時間,那么 Junit 將自動將它標記為失敗。timeout 參數和 @Test注釋一起使用。現在讓我們看看活動中的 @test(timeout)。
@Test(timeout = 1000) public void testTimeout() throws InterruptedException { TimeUnit.SECONDS.sleep(2); System.out.println("Complete"); }
上面測試會失敗,在一秒后會拋出異常 org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds
異常測試
你可以測試代碼是否它拋出了想要得到的異常。expected 參數和 @Test 注釋一起使用。現在讓我們看看活動中的 @Test(expected)。
@Test(expected = NullPointerException.class) public void testNullException() { throw new NullPointerException(); }
上面代碼會測試成功。
套件測試
public class TaskOneTest { @Test public void test() { System.out.println("Task one do."); } } public class TaskTwoTest { @Test public void test() { System.out.println("Task two do."); } } public class TaskThreeTest { @Test public void test() { System.out.println("Task Three."); } } @RunWith(Suite.class) // 1. 更改測試運行方式為 Suite // 2. 將測試類傳入進來 @Suite.SuiteClasses({TaskOneTest.class, TaskTwoTest.class, TaskThreeTest.class}) public class SuitTest { /** * 測試套件的入口類只是組織測試類一起進行測試,無任何測試方法, */ }
3、Spring Boot 中使用 JUnit
Spring 框架提供了一個專門的測試模塊(spring-test),用於應用程序的集成測試。 在 Spring Boot 中,你可以通過spring-boot-starter-test啟動器快速開啟和使用它,其中包含了junit、hamcrest、mockito及asset相關類。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
// 獲取啟動類,加載配置,確定裝載 Spring 程序的裝載方法,它回去尋找 主配置啟動類(被 @SpringBootApplication 注解的) @SpringBootTest // 讓 JUnit 運行 Spring 的測試環境, 獲得 Spring 環境的上下文的支持 @RunWith(SpringRunner.class) public class EmployeeServiceImplTest { // do }
在微服務架構中,一般來說前后端是分離的,后端一般controller層會極其弱化,或者rpc服務自動暴露為REST API接口。所以webmvc層的單元測試在設計合理的架構中是不必要的,雖然Spring Boot Test提供了相當完備的功能供單元測試(具體可見https://blog.csdn.net/qq_35915384/article/details/80227297的前半部分。),但是在微服務架構中,它太重了。在一個典型的服務中,它的調用是這樣的:
為了測試A類,必須把B-E類全部服務都構建好,如果其中有其它微服務提供的接口,則不得不依賴擋板或集成測試環境,這樣測試成本就會很高。所以,更好的做法是為B、C做mock類(對於每個被測類來說,Mock是類級別的,跟分支數無關),為B、C類的方法做stub(stub是根據B/C類對應方法有多少不同出入參對來決定的,一一對應。注:單元測試幾乎所有被測類依賴的有狀態類都需要Mock,接口測試則只需要Mock其他微服務的接口),這樣就可以不用依賴spring環境完成測試。如下:
但是事情通常要比這更復雜,有些非業務服務類可能需要依賴spring的配置信息,有一些利用了spring ioc的各種特性比如ApplicationContext.getBean()、多數據源切換、AOP攔截器、復雜邏輯生成文件等,對於這些特殊場景,仍然是需要仔細設計單元測試。正常情況下單元測試僅僅是為了測試邏輯,通常不包括事務,否則清理和准備通常也要花費不低的成本。如果一定要測試數據庫,在單元測試上方法上增加@Transactional(加上會使得最后所有事務被回滾)注解反而不一定合適了,因為既然要測試數據庫,則起碼ACID應該測試。
其次,對於樁而言,通常都是為了返回某個結果,對於一些包含很多字段的pojo和List,每次造這些數據也是比較耗時的,因此建議在json文件中維護相關的dto和pojo實例化數據及其配套工具類,進行統一的注入,這樣單元測試的重復代碼就可以大大減少。
對於一個方法來說,單元測試的最低要求是100%的代碼覆蓋率,至少已知的通過、不通過、拋出的Exception得測到。除了簡單的業務查詢外,幾乎不可能只有一個test case,如果一個方法只有一個單元測試,幾乎可以肯定單元測試是為了應付,所以對每個方法,在javadoc上維護好場景清單,至少應包括:場景說明,入參(線程上下文變量),返回值/XXXException很重要,只有這樣單測才會有效果。
4、要實踐好單元測試,首先得掌握事半功倍的技巧,不然很容易事倍功半,對單元測試總結得最好的一篇文章之一可以參見https://www.jianshu.com/p/afb04b925db3、https://www.jdon.com/53146也可以參考。