0 前言
junit是一個開源的Java語言的單元測試框架。目前junit主要有版本junit3,junit4和junit5。因在junit3中,是通過對測試類和測試方法的命名來確定是否是測試,且所有的測試類必須繼承junit的測試基類TestCase,所以本文不再討論junit3,只討論使用比較多的junit4和junit5。
0.1 特性
- 提供注解標(biāo)識測試方法;
- 提供斷言測試預(yù)期結(jié)果;
- 提供測試套件,組織測試用例和其他測試套件;
- 其他參見junit官網(wǎng):JUnit4/JUnit5/Introduction - Junit 5官方文檔中文版
1 基本用法
1.1 常用注解介紹
(1)@Test
使用@Test注解測試方法。但測試方法必須是 public void。方法名一般為testXXX,通常需要見名知起義。
(2)@BeforeClass和@AfterClass
- @BeforeClass:會在測試類測試方法執(zhí)行之前執(zhí)行一次;
- @AfterClass:會在測試內(nèi)測試方法均執(zhí)行完成后執(zhí)行一次;
注意,@BeforeClass和@AfterClass注解的方法必須是static方法。
(3)@Before和@After
- @Before:會在每個測試方法執(zhí)行之前執(zhí)行一次;
- @After:會在每個測試方法執(zhí)行之后執(zhí)行一次;
(4)@Parameters
使用@Parameters注解數(shù)據(jù)源方法。
(5)@Ignore
使用@Ignore忽略測試方法,被該注解標(biāo)識的測試方法會被忽略不執(zhí)行。
1.2 測試樣例
本文代碼詳情請見:https://github.com/X-NaN/studyjunit
public class JunitAnnotationTest {
/**
* @BeforeClass 注解的必須是static方法
*/
@BeforeClass
public static void beforeClass() {
System.out.println("@BeforeClass: 在該測試類內(nèi)所有方法之前執(zhí)行,只執(zhí)行一次");
}
@Before
public void beforeMethod() {
System.out.println("@Before: 在每個測試方法之前執(zhí)行一次");
}
@Test
public void testCaseA() {
System.out.println("@Test: 標(biāo)識測試方法testCaseA");
}
@Test
public void testCaseB() {
System.out.println("@Test: 標(biāo)識測試方法testCaseB");
}
/**
* 異常測試
*/
@Test(expected = ArithmeticException.class)
public void testCaseException() {
System.out.println("@Test: 標(biāo)識測試方法testCaseException, 異常測試");
System.out.println(1 / 0);
}
/**
* 超時測試
*
* @throws InterruptedException
*/
@Test(timeout = 1000)
public void testCaseTimeOut() throws InterruptedException {
System.out.println("@Test: 標(biāo)識測試方法testCaseTimeOut,超時");
// 若方法的超時時間超過timeout,則用例失敗,否則成功
Thread.sleep(1000);
}
@Ignore
public void testCaseC() {
System.out.println("@Ignore: 標(biāo)識測試方法被忽略,不執(zhí)行");
}
@After
public void afterMethod() {
System.out.println("@After: 在每個測試方法之后執(zhí)行一次");
}
/**
* @AfterClass 注解的必須是static方法
*/
@AfterClass
public static void afterClass() {
System.out.println("@AfterClass: 在該測試類中所有測試方法執(zhí)行完之后執(zhí)行,只執(zhí)行一次");
}
}
測試類運(yùn)行結(jié)果:
2 參數(shù)化測試
參數(shù)化測試指的是通過傳入不同的測試數(shù)據(jù),從而可以多次運(yùn)行同一個用例。junit使用@Parameters注解數(shù)據(jù)源方法。編寫參數(shù)化測試的步驟是
- 使用@Parameters注解測試數(shù)據(jù)源方法;
- 聲明實(shí)例變量用于接收測試數(shù)據(jù),并使用@Parameter注解。若測試方法需要兩個入?yún)?,則需要聲明兩個實(shí)例變量分別接收。除了通過注解@Parameter接收測試數(shù)據(jù),也可以通過定義構(gòu)造函數(shù)用于給實(shí)例變量賦值實(shí)現(xiàn)測試數(shù)據(jù)綁定到實(shí)例變量。
- 定義測試方法,使用實(shí)例變量。
- 測試類執(zhí)行器使用Parameterized,即在測試類增加注解@RunWith(Parameterized.class)。
@RunWith(Parameterized.class)
public class ParameterizedTest {
/**
* 必須是public且用@Parameterized.Parameter注解,括號內(nèi)的為某行的第幾個測試數(shù)據(jù)
*/
@Parameterized.Parameter(1)
public Integer a;
@Parameterized.Parameter(0)
public Integer b;
private Calculate calculate;
/**
* 數(shù)據(jù)源,必須是public static,且方法必須返回測試數(shù)據(jù)集合
*
* @return
*/
@Parameterized.Parameters
public static Collection data() {
return Arrays.asList(new Object[][]{
{0, 0},
{1, 1},
{2, 3},
{3, 7},
{10, 5},
});
}
@Before
public void beforeMethod() {
calculate = new Calculate();
}
@Test
public void testAdd() {
System.out.println(a + "+" + b + "=" + calculate.add(a, b));
}
}
2.1 識別測試用例
從上面參數(shù)化測試用例可以看出,參數(shù)化用例名默認(rèn)為 :caseName[index]的形式。如果想要準(zhǔn)確地識別生成的用例對應(yīng)哪條數(shù)據(jù)比較困難。實(shí)際@Parameters有個name屬性,可以指定參數(shù),如下所示。
- {index}: 代表當(dāng)前參數(shù)的索引;
- {0}, {1}, …: 代表第一個參數(shù),第二個參數(shù)等;
/**
* 數(shù)據(jù)源,必須是public static,且方法必須返回測試數(shù)據(jù)集合
* name指定用例名稱,默認(rèn)使用測試數(shù)據(jù)索引序號
*
* @return
*/
@Parameterized.Parameters(name = "{index}:a={0},b={1}")
public static Collection data() {
return Arrays.asList(new Object[][]{
{0, 0},
{1, 1},
{2, 3},
{3, 7},
{10, 5},
});
}
3 分組測試
3.1 測試suite
隨著測試類的不斷增加,如果組織和運(yùn)行一批測試類成為關(guān)鍵。junit提供了測試套件功能,通過將一組相關(guān)的測試類組織在一個測試套件內(nèi),使其可以一次執(zhí)行。測試套件執(zhí)行,使用單獨(dú)的執(zhí)行器Suite.class。
- @RunWith(Suite.class)注解的類為測試套件的入口類。
- @Suite.SuiteClasses放入相關(guān)測試類
/**
* 套件類,以suite執(zhí)行用例
*
* @author xingnana
* @create 2022/9/1
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({CalculateTest.class, CalculateAnotherTest.class})
public class JunitSuites {
}
public class CalculateTest {
@Test
public void testAdd() {
Calculate calculate = new Calculate();
Assert.assertEquals(6, calculate.add(2, 3));
}
}
public class CalculateAnotherTest {
@Test
public void testSubtract() {
Calculate calculate = new Calculate();
Assert.assertEquals(2, calculate.subtract(6, 4));
}
}
3.2 分組測試
測試套件Suite是測試類級別分組,粒度比較粗,那如何實(shí)現(xiàn)用例級別的分組呢?junit提供了@Cate
/**
* 分組測試
*
* @author: xingnana
* @date: 2022/9/9
*/
@RunWith(Categories.class)
@Categories.IncludeCategory({FastTests.class})
@Suite.SuiteClasses({ATest.class, BTest.class})
public class GroupTestSuite {
}
/**
* 測試類別Fast
*
* @author: xingnana
* @date: 2022/9/9
*/
public interface FastTests {
}
/**
* 測試類別Slow
*
* @author: xingnana
* @date: 2022/9/9
*/
public interface SlowTests {
}
public class ATest {
/**
* 給測試方法分類
*/
@Category(FastTests.class)
@Test
public void testA1(){
Assert.assertEquals("aa","bb");
}
@Test
public void TestA2(){
System.out.printf("打印");
}
}
@Category({SlowTests.class, FastTests.class})
public class BTest {
@Test
public void testB1() {
Assert.assertEquals("aa","bb");
}
@Test
public void TestB2() {
Assert.assertEquals("aa","aa");
}
}
4 junit5和junit4的對比
4.1 junit5介紹
junit5是Junit框架的一個大的更新,與以前版本的 JUnit 不同,JUnit 5由來自三個不同子項(xiàng)目的幾個不同模塊組成。
JUnit Platform:是JVM 上啟動測試框架的基礎(chǔ)。它定義了 TestEngine API,用于開發(fā)在平臺上運(yùn)行的測試框架。此外,該平臺還提供了一個 Console Launcher,用于從命令行啟動平臺,以及 JUnit Platform Suite Engine,用于在平臺上使用一個或多個測試引擎運(yùn)行自定義測試套件。
JUnit Jupiter:是用于編寫 JUnit5中的測試和擴(kuò)展的編程模型和擴(kuò)展模型的組合,是Junit5的核心。該子項(xiàng)目提供了一個 TestEngine,用于在平臺上運(yùn)行基于 Jupiter 的測試。
JUnit Vintage:提供了一個 TestEngine,用于在平臺上運(yùn)行基于 JUnit3和 JUnit4的測試。
(該圖取自該升級你的JUnit版本了——JUnit5基本介紹 - 知乎)
juni5與junit4的測試基本相同,但又有些區(qū)別,本文后半部分將對junit5和junit4的不同做一個介紹。
4.2 注解的區(qū)別
junit4 |
junit5 |
說明 |
@Test |
@Test |
注解測試用例 |
@BeforeClass @AfterClass |
@BeforeAll @AfterAll |
在測試類內(nèi)所有方法之前/之后執(zhí)行一次 |
@Before @After |
@BeforeEach @AfterEach |
在測試用例執(zhí)行之前/之后執(zhí)行一次 |
@Ignore |
@Disabled |
注解測試用例忽略不執(zhí)行 |
@Category |
@Tag |
測試用例分類 |
4.2.1 測試樣例
public class Junit5AnnotationTest {
/**
* @BeforeAll 注解的必須是static方法
*/
@BeforeAll
public static void beforeAll() {
System.out.println("@BeforeAll: 在該測試類內(nèi)所有方法之前執(zhí)行,只執(zhí)行一次");
}
@BeforeEach
public void beforeEachMethod() {
System.out.println("@BeforeEach: 在每個測試方法之前執(zhí)行一次");
}
@Test
public void testCaseA() {
System.out.println("@Test: 標(biāo)識測試方法testCaseA");
}
@Test
public void testCaseB() {
System.out.println("@Test: 標(biāo)識測試方法testCaseB");
}
/**
* 異常測試
*/
@Test
public void testCaseException() {
System.out.println("@Test: 標(biāo)識測試方法testCaseException, 異常測試");
Assertions.assertThrows(ArithmeticException.class, () -> {
System.out.println(1 / 0);
});
}
/**
* 超時測試
*/
@Test
public void testCaseTimeOut_A() {
System.out.println("testCaseTimeOut,超時");
// 若方法的超時時間超過timeout,則用例失敗,否則成功
Assertions.assertTimeout(Duration.ofMillis(2000), () -> Thread.sleep(3000));
}
/**
* 超時測試
* https://stackoverflow.com/questions/68483928/junit-5-test-not-failing-despite-timeout
* https://github.com/junit-team/junit5/issues/2087
*/
@Test
@Timeout(value = 2000, unit = TimeUnit.MILLISECONDS)
public void testCaseTimeOut_B() {
System.out.println("@Timeout超時");
while (true) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* @Disabled注解忽略不執(zhí)行的用例
*/
@Disabled
public void testCaseC() {
System.out.println("@Disabled: 標(biāo)識測試方法被忽略,不執(zhí)行");
}
@Test
public void testCaseD() {
Assertions.assertEquals(5, 4, "value not equal");
}
@AfterEach
public void afterEachMethod() {
System.out.println("@AfterEach: 在每個測試方法之后執(zhí)行一次");
}
/**
* @AfterAll 注解的必須是static方法
*/
@AfterAll
public static void afterAll() {
System.out.println("@AfterAll: 在該測試類中所有測試方法執(zhí)行完之后執(zhí)行,只執(zhí)行一次");
}
}
4.2.2 超時測試
public class TimeoutDemo {
@BeforeEach
@Timeout(5)
void setUp() {
// fails if execution time exceeds 5 seconds
}
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void failsIfExecutionTimeExceeds500Milliseconds() {
// fails if execution time exceeds 500 milliseconds
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// @Test
// @Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
// void failsIfExecutionTimeExceeds500MillisecondsInSeparateThread() {
// // fails if execution time exceeds 500 milliseconds, the test code is executed in a separate thread
// }
}
文章來源:http://www.zghlxwxcb.cn/news/detail-436110.html
遺留問題:
/**
*
* Junit5AnnotationTest中的超時測試用例 @Timeout不生效????
* https://stackoverflow.com/questions/68483928/junit-5-test-not-failing-despite-timeout
* https://github.com/junit-team/junit5/issues/2087
*/
@Test
@Timeout(value = 2000, unit = TimeUnit.MILLISECONDS)
public void testCaseTimeOut_B() {
System.out.println("@Timeout超時");
while (true) {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* TimeoutDemo類中的超時用例 @Timeout生效
* https://stackoverflow.com/questions/68483928/junit-5-test-not-failing-despite-timeout
* https://github.com/junit-team/junit5/issues/2087
*/
@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void failsIfExecutionTimeExceeds500Milliseconds() {
// fails if execution time exceeds 500 milliseconds
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
5 如何升級到j(luò)unit5
由于有些存量用例是使用junit4或3版本編寫的。JUnit Vintage可以支持在升級到j(luò)unit5,同時不修改原有用例的情況下運(yùn)行原有的用例文章來源地址http://www.zghlxwxcb.cn/news/detail-436110.html
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
到了這里,關(guān)于淺談java單元測試框架junit4/5的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!