通常我們寫完程式碼之後,為了確保程式碼的正確性,都需要自己測試一遍,看一下程式碼的執行結果和我們期望的結果是不是一樣的,也就是我們常說的單元測試,java中最常用的單元測試框架是junit,本文主要介紹3個內容:
1、玩轉junit
2、spring整合junit
3、開發工具中使用junit
1、背景我們寫了一個工具類,有2個方法
package com.javacode2018.junit.demo1; public class MathUtils { /** * 獲取最大的數字 * * @param args * @return */ public static int max(int... args) { int result = Integer.MIN_VALUE; for (int arg : args) { result = result > arg ? result : arg; } return result; } /** * 獲取最小的數字 * * @param args * @return */ public static int min(int... args) { int result = Integer.MAX_VALUE; for (int arg : args) { result = result < arg ? result : arg; } return result; } }
然後我們想測試這兩個方法,下面我們來寫測試程式碼,如下,測試一下max方法和min方法的結果和我們期望的結果是否一致,不一致的時候,輸出一段文字
package com.javacode2018.junit.demo1; public class MathUtilsTest1 { public static void main(String[] args) { testMax(); testMin(); } public static void testMax() { int result = MathUtils.max(1, 2, 3); if (result != 3) { System.out.println(String.format("max 方法有問題,期望結果是3,實際結果是%d", result)); } } public static void testMin() { int result = MathUtils.min(1, 2, 3); if (result != 1) { System.out.println(String.format("min 方法有問題,期望結果是3,實際結果是%d", result)); } }}
上面我們要測試的方法就2個,若需測試的方法很多的時候,咱們需要寫大量的這種測試程式碼,工作量還是蠻大的,而junit做的事情和上面差不多,都是用來判斷被測試的方法和期望的結果是否一致,不一致的時候給出提示,不過junit用起來更容易一些,還有各種開發用到的ide(eclipse、idea)結合的更好一些,用起來特別的順手。
2、junit用法詳解2.1、使用步驟1)、新增junit maven配置,這裡我們就用4.13,你們也可以用最新的
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version></dependency>
2)、寫測試用例,在寫好的測試方法上面新增@Test註解,比如我們需要對上面案例中的max方法進行測試,通常我們會新建一個測試類,類名為被測試的類加上Test字尾,即:MathUtilsTest,然後在這個類我們需要寫max方法的測試方法,如下,需要我們在max方法上面加上@Test註解
package com.javacode2018.junit.demo1; import org.junit.Assert;import org.junit.Test; public class MathUtilsTest { @Test public void max() throws Exception { int result = MathUtils.max(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 3); }}
3、執行測試用例,現在測試程式碼都寫好了,下面我們寫個類來啟動測試用例,這裡需要使用JUnitCore.runClasses方法來執行測試用例,如下:
package com.javacode2018.junit.demo1; import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.notification.Failure; public class Demo1TestRunner { public static void main(String[] args) { //使用JUnitCore.runClasses方法傳入測試用例的類,然後獲取測試用例的執行結果 Result result = JUnitCore.runClasses(MathUtilsTest.class); //獲取失敗的用例 for (Failure failure : result.getFailures()) { System.out.println(failure); } //獲取所有測試用例是否執行成功 System.out.println(result.wasSuccessful()); }}
2.2、同時執行多個測試用例可以一個測試類中寫多個測試方法,每個方法上加上@Test註解就可以了,然後透過JUnitCore來執行就可以,下面程式碼中我們寫2個方法對MathUtils中的max和min方法都進行測試,我們故意將執行結果和期望結果搞成不一致的,執行下面程式碼,然後看看執行結果。
package com.javacode2018.junit.demo2; import com.javacode2018.junit.demo1.MathUtils;import org.junit.Assert;import org.junit.Test;import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.manipulation.Ordering;import org.junit.runner.notification.Failure; public class MathUtilsTest2 { @Test public void max() throws Exception { int result = MathUtils.max(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 1); } @Test public void min() throws Exception { int result = MathUtils.min(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 3); } public static void main(String[] args) { Result result = JUnitCore.runClasses(MathUtilsTest2.class); System.out.println("失敗用例個數:" + result.getFailures().size()); for (Failure failure : result.getFailures()) { System.out.println(failure); } System.out.println("執行測試用例個數:" + result.getRunCount()); System.out.println("執行測試用例總耗時(ms):" + result.getRunTime()); System.out.println("測試用例是否都成功了:" + result.wasSuccessful()); }}
執行輸出如下,運行了2個用例,失敗了2個,測試的詳細資訊都被輸出了
失敗用例個數:2max(com.javacode2018.junit.demo2.MathUtilsTest2): expected:<3> but was:<1>min(com.javacode2018.junit.demo2.MathUtilsTest2): expected:<1> but was:<3>執行測試用例個數:2執行測試用例總耗時(ms):11測試用例是否都成功了:false
2.3、使用斷言
什麼是斷言?
斷言是用來判斷程式的執行結果和我們期望的結果是不是一致的,如果不一致,會丟擲異常,斷言中有3個資訊比較關鍵
1、被測試的資料
2、期望的資料
3、丟擲異常
斷言提供的方法將被測試的資料和期望的資料進行對比,如果不一樣的時候,將丟擲異常,程式可以捕獲這個異常,這樣就可以知道測試失敗了。
junit中的org.junit.Assert類中提供了大量靜態方法,用來判斷被測試的資料和期望的資料是否一致,不一致,將丟擲異常,這裡隨便列幾個大家看一下吧
//判斷condition如果不是true,將丟擲異常,異常的提示資訊是messagepublic static void assertTrue(String message, boolean condition) //判斷expected和actual是否相等,如果不相等,將丟擲異常public static void assertEquals(Object expected, Object actual)
用法,如:
int result = MathUtils.max(1, 2, 3);//判斷測試結果和我們期望的結果是否一致Assert.assertEquals(result, 1);
2.4、測試套件:批次執行測試用例
到目前為止,我們還只能一次執行一個測試類,如下
JUnitCore.runClasses(MathUtilsTest2.class)
但是在實際專案中,我們可能會有很多測試類,需要批次執行。
比如我們有下面2個測試類
MathUtilsTest3001.java
package com.javacode2018.junit.demo3; import com.javacode2018.junit.demo1.MathUtils;import org.junit.Assert;import org.junit.Test; public class MathUtilsTest3001 { @Test public void max() throws Exception { int result = MathUtils.max(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 3); } @Test public void min() throws Exception { int result = MathUtils.min(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 1); }}
MathUtilsTest3002.java
package com.javacode2018.junit.demo3; import com.javacode2018.junit.demo1.MathUtils;import org.junit.Assert;import org.junit.Test;import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.notification.Failure; public class MathUtilsTest3002 { @Test public void max() throws Exception { int result = MathUtils.max(100, 99, 200); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 200); } @Test public void min() throws Exception { int result = MathUtils.min(1, -1, 10); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, -1); }}
現在我們希望同時執行上面2個測試類,我們可以這麼做,建立一個AllTest.java類,注意這個類上有2個註解比較特殊,都是junit提供的,@RunWith表示這是一個測試套件類,需要批次執行測試類,具體要執行哪些測試類呢,透過@Suite.SuiteClasses來指定
package com.javacode2018.junit.demo3; import org.junit.runner.RunWith;import org.junit.runners.Suite; @RunWith(Suite.class)@Suite.SuiteClasses({MathUtilsTest3001.class, MathUtilsTest3002.class})public class AllTest {}
下面來個啟動類,將AllTest傳遞給JUnitCore.runClasses
失敗用例個數:0執行測試用例個數:4執行測試用例總耗時(ms):12測試用例是否都成功了:true
測試套件中不僅可以包含基本的測試類,而且可以包含其它的測試套件,這樣可以很方便的分層管理不同模組的單元測試程式碼,比如下面程式碼,Module2Test和Module2Test都是測試套件
@RunWith(Suite.class)@Suite.SuiteClasses({Test1.class, Test2.class})public class Module2Test {} @RunWith(Suite.class)@Suite.SuiteClasses({Test1.class, Test2.class})public class Module2Test {} @RunWith(Suite.class)@Suite.SuiteClasses({Module2Test.class, Module2Test.class, Test3.java})public class AllTest {} //執行AllTestJUnitCore.runClasses(AllTest.class);
2.5、Junit常用註解1)@Test註解@Test:將一個普通方法修飾成一個測試方法
@Test(excepted=xx.class):xx.class 表示異常類,表示測試的方法丟擲此異常時,認為是正常的測試透過的
@Test(timeout = 毫秒數):測試方法執行時間是否符合預期
2)@BeforeClass會在所有的方法執行前被執行,static 方法 (全域性只會執行一次,而且是第一個執行)
3)@AfterClass會在所有的方法執行之後進行執行,static 方法 (全域性只會執行一次,而且是最後一個執行)
4)@Before會在每一個測試方法被執行前執行一次
5)@After會在每一個測試方法執行後被執行一次
6)@Ignore所修飾的測試方法會被測試執行器忽略
7)@RunWith可以更改測試執行器 org.junit.runner.Runner
下面的案例,基本上用到了上面所有的註解,大家結合輸出理解一下。
package com.javacode2018.junit.demo4; import com.javacode2018.junit.demo1.MathUtils;import org.junit.*;import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.notification.Failure; import java.util.concurrent.TimeUnit; public class MathUtilsTest4 { @BeforeClass public static void bc() { System.out.println("@BeforeClass"); System.out.println("-----------------"); } @AfterClass public static void ac() { System.out.println("@AfterClass"); } @Before public void bf() { System.out.println("@Before:" + this); } @After public void af() { System.out.println("@After:" + this); System.out.println("##################"); } @Test public void max() throws Throwable { System.out.println("max():" + this); int result = MathUtils.max(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 3); } @Test public void min() throws Exception { System.out.println("min():" + this); int result = MathUtils.min(1, 2, 3); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(result, 1); } //方法執行時間超過了timeout,表示測試用例執行失敗 @Test(timeout = 1000) public void timeOutTest() throws InterruptedException { System.out.println("timeOutTest():" + this); TimeUnit.SECONDS.sleep(2000); } //方法若未丟擲expected指定的異常,表示測試用例執行失敗 @Test(expected = NullPointerException.class) public void expectedTest() { System.out.println("expectedTest():" + this); new RuntimeException("異常不匹配"); } @Test @Ignore public void ignoredMethod() { System.out.println("我是被忽略的方法"); } public static void main(String[] args) { Result result = JUnitCore.runClasses(MathUtilsTest4.class); System.out.println("-----------------"); System.out.println("執行測試用例個數:" + result.getRunCount()); System.out.println("失敗用例個數:" + result.getFailures().size()); for (Failure failure : result.getFailures()) { System.out.println(failure); } System.out.println("執行測試用例總耗時(ms):" + result.getRunTime()); System.out.println("測試用例是否都成功了:" + result.wasSuccessful()); }}
執行結果如下
@BeforeClass-----------------@Before:com.javacode2018.junit.demo4.MathUtilsTest4@78e03bb5timeOutTest():com.javacode2018.junit.demo4.MathUtilsTest4@78e03bb5@After:com.javacode2018.junit.demo4.MathUtilsTest4@78e03bb5##################@Before:com.javacode2018.junit.demo4.MathUtilsTest4@48533e64max():com.javacode2018.junit.demo4.MathUtilsTest4@48533e64@After:com.javacode2018.junit.demo4.MathUtilsTest4@48533e64##################@Before:com.javacode2018.junit.demo4.MathUtilsTest4@7e0b37bcmin():com.javacode2018.junit.demo4.MathUtilsTest4@7e0b37bc@After:com.javacode2018.junit.demo4.MathUtilsTest4@7e0b37bc##################@Before:com.javacode2018.junit.demo4.MathUtilsTest4@1a93a7caexpectedTest():com.javacode2018.junit.demo4.MathUtilsTest4@1a93a7ca@After:com.javacode2018.junit.demo4.MathUtilsTest4@1a93a7ca##################@AfterClass-----------------執行測試用例個數:4失敗用例個數:3timeOutTest(com.javacode2018.junit.demo4.MathUtilsTest4): test timed out after 1000 millisecondsmax(com.javacode2018.junit.demo4.MathUtilsTest4): hah expectedTest(com.javacode2018.junit.demo4.MathUtilsTest4): Expected exception: java.lang.NullPointerException執行測試用例總耗時(ms):1018測試用例是否都成功了:false
從輸出中可以看出
@BeforeClass和@AfterClass標註的方法只會執行一次每個@Test標註的方法執行之前會先執行@Before標註的方法,然後執行@Test標註的這個方法,之後再執行@After從this的輸出看出,每個@Test執行的時候,當前類的例項都會重新建立一個新的不論@Test標註的方法是否異常,@AfterClass、@After標註的方法都會執行,且異常會被淹沒,輸出中看不到異常資訊2.6、引數化測試Junit 4 引入了一個新的功能引數化測試。
引數化測試允許開發人員使用不同的值反覆運行同一個測試,你將遵循 5 個步驟來建立引數化測試。
用 @RunWith(Parameterized.class) 來註釋 test 類。建立一個由 @Parameters 註釋的公共的靜態方法,它返回一個物件的集合(陣列)來作為測試資料集合。建立一個公共的建構函式,它接受和一行測試資料相等同的東西。為每一列測試資料建立一個例項變數。用例項變數作為測試資料的來源來建立你的測試用例。可能大家看了上面的理解,還是比較迷糊。
比如我們com.javacode2018.junit.demo1.MathUtils#max測試下面幾組陣列
1,2,3100,99,8030,-1,100
我們可以這麼寫
package com.javacode2018.junit.demo5; import com.javacode2018.junit.demo1.MathUtils;import org.junit.Assert;import org.junit.Test;import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.RunWith;import org.junit.runner.notification.Failure;import org.junit.runners.Parameterized; import java.util.ArrayList;import java.util.Arrays;import java.util.List; @RunWith(Parameterized.class)public class MathUtilsTest5 { public static class TestData { int[] testData;//測試資料 int expectedValue;//預期的結果 public TestData(int[] testData, int expectedValue) { this.testData = testData; this.expectedValue = expectedValue; } @Override public String toString() { return "TestData{" + "testData=" + Arrays.toString(testData) + ", expectedValue=" + expectedValue + '}'; } } private TestData testData; @Parameterized.Parameters public static List<TestData> initTestData() { System.out.println("initTestData()"); //key:期望的結果,value:max方法需要測試的資料 List<TestData> result = new ArrayList<>(); result.add(new TestData(new int[]{1, 2, 3}, 3)); result.add(new TestData(new int[]{100, 99, 80}, 100)); result.add(new TestData(new int[]{30, -1, 100}, 100)); return result; } public MathUtilsTest5(TestData testData) { System.out.println("MathUtilsTest5構造器:" + testData); this.testData = testData; } @Test public void maxTest() throws Throwable { System.out.println(this.hashCode() + ",maxTest():" + this.testData); int result = MathUtils.max(this.testData.testData); //判斷測試結果和我們期望的結果是否一致 Assert.assertEquals(this.testData.expectedValue, result); System.out.println("###################"); } public static void main(String[] args) { Result result = JUnitCore.runClasses(MathUtilsTest5.class); System.out.println("-----------------"); System.out.println("執行測試用例個數:" + result.getRunCount()); System.out.println("失敗用例個數:" + result.getFailures().size()); for (Failure failure : result.getFailures()) { System.out.println(failure); } System.out.println("執行測試用例總耗時(ms):" + result.getRunTime()); System.out.println("測試用例是否都成功了:" + result.wasSuccessful()); }}
為了方便大家理解程式碼的執行過程,程式碼中添加了很多日誌輸出,執行結果如下,結合程式碼和輸出,理解很容易
initTestData()MathUtilsTest5構造器:TestData{testData=[1, 2, 3], expectedValue=3}721748895,maxTest():TestData{testData=[1, 2, 3], expectedValue=3}###################MathUtilsTest5構造器:TestData{testData=[100, 99, 80], expectedValue=100}463345942,maxTest():TestData{testData=[100, 99, 80], expectedValue=100}###################MathUtilsTest5構造器:TestData{testData=[30, -1, 100], expectedValue=100}195600860,maxTest():TestData{testData=[30, -1, 100], expectedValue=100}###################-----------------執行測試用例個數:3失敗用例個數:0執行測試用例總耗時(ms):12測試用例是否都成功了:true
3、Spring整合junitspring整合junit比較簡單,下面我們來個案例感受一下。
3.1、加入maven配置<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version></dependency>
3.2、來個spring的入口配置類
package com.javacode2018.springjunit; import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration; @Configurationpublic class MainConfig { @Bean public String name() { return "路人甲java"; } @Bean public int age() { return 30; }}
3.3、來個junit測試類整合spring下面我們來個測試類,對上面的MainConfig中註冊的2個bean進行測試
上面程式碼中name和age屬性上面都有@Autowired註解,這2個屬性會被自動從spring容器中注入進來。
3.4、來個測試啟動類package com.javacode2018.springjunit; import org.junit.runner.JUnitCore;import org.junit.runner.Result;import org.junit.runner.notification.Failure; public class TestRunner { public static void main(String[] args) { Result result = JUnitCore.runClasses(MainConfigTest.class); System.out.println("-----------------"); System.out.println("執行測試用例個數:" + result.getRunCount()); System.out.println("失敗用例個數:" + result.getFailures().size()); for (Failure failure : result.getFailures()) { System.out.println(failure); } System.out.println("執行測試用例總耗時(ms):" + result.getRunTime()); System.out.println("測試用例是否都成功了:" + result.wasSuccessful()); } }
執行輸出
30路人甲java-----------------執行測試用例個數:2失敗用例個數:0執行測試用例總耗時(ms):422測試用例是否都成功了:true
4、開發工具中使用junit
上面介紹的所有案例,都是透過main方法中用JUnitCore.runClasses來執行測試用例的,實際上有更簡單的方式。
java的常用開發工具有eclipse和idea,這兩個工具都將junit整合好了,透過開發工具整合的功能,執行測試用例更方便,不需要我們寫JUnitCore.runClasses程式碼了。
我們來演示一下,如下圖
5、總結1、本文詳細介紹了junit的用法,常用的註解有@Test、@BeforeClass、@AfterClass、@Before、@After、@Ignore、@RunWith,這些都要掌握
2、spring中整合junit,主要的配置就是在測試類上面需要加上下面程式碼
6、案例原始碼git地址:https://gitee.com/javacode2018/spring-series 本文案例對應原始碼: spring-series\lesson-008-junit spring-series\lesson-008-springjunit
大家star一下,所有系列程式碼都會在這個裡面,還有所有原創文章的連結也在裡面,方便查閱!!!
原文連結:https://itsoku.blog.csdn.net/article/details/110675698