文章目录
阅读前请先收藏,一文搞懂junit及spring集成,从此不再模凌两可。
一、 junit4
1.1 基本用法
- 引入pom
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
- @Test
使用@Test注解测试方法。但测试方法必须是 public void。方法名一般为testXXX,通常需要见名知起义。
- @BeforeClass和@AfterClass
@BeforeClass:会在测试类测试方法执行之前执行一次;
@AfterClass:会在测试内测试方法均执行完成后执行一次;
注意,@BeforeClass和@AfterClass注解的方法必须是static方法。
- @Before和@After
@Before:会在每个测试方法执行之前执行一次;
@After:会在每个测试方法执行之后执行一次;
- @Parameters
使用@Parameters注解数据源方法。
- @Ignore
使用@Ignore忽略测试方法,被该注解标识的测试方法会被忽略不执行。
Demo
public class JunitAnnotationTest {
/**
* @BeforeClass 注解的必须是static方法
*/
@BeforeClass
public static void beforeClass() {
System.out.println("@BeforeClass: 在该测试类内所有方法之前执行,只执行一次");
}
@Before
public void beforeMethod() {
System.out.println("@Before: 在每个测试方法之前执行一次");
}
@Test
public void testCaseA() {
System.out.println("@Test: 标识测试方法testCaseA");
}
@Test
public void testCaseB() {
System.out.println("@Test: 标识测试方法testCaseB");
}
/**
* 异常测试
*/
@Test(expected = ArithmeticException.class)
public void testCaseException() {
System.out.println("@Test: 标识测试方法testCaseException, 异常测试");
System.out.println(1 / 0);
}
/**
* 超时测试
*/
@Test(timeout = 1000)
public void testCaseTimeOut() throws InterruptedException {
System.out.println("@Test: 标识测试方法testCaseTimeOut,超时");
// 若方法的超时时间超过timeout,则用例失败,否则成功
Thread.sleep(1000);
}
@Ignore
public void testCaseC() {
System.out.println("@Ignore: 标识测试方法被忽略,不执行");
}
@After
public void afterMethod() {
System.out.println("@After: 在每个测试方法之后执行一次");
}
/**
* @AfterClass 注解的必须是static方法
*/
@AfterClass
public static void afterClass() {
System.out.println("@AfterClass: 在该测试类中所有测试方法执行完之后执行,只执行一次");
}
}
1.2 进阶用法
- 按指定顺序执行
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Demo3_OrderTest {
@Test
public void order1(){
System.out.println("order1");
}
@Test
public void order2(){
System.out.println("order2");
}
@Test
public void order3(){
System.out.println("order3");
}
}
- 参数化用法
参数化测试指的是通过传入不同的测试数据,从而可以多次运行同一个用例。junit使用@Parameters注解数据源方法。编写参数化测试的步骤是:
- 使用@Parameters注解测试数据源方法;
- 声明实例变量用于接收测试数据,并使用@Parameter注解。若测试方法需要两个入参,则需要声明两个实例变量分别接收。除了通过注解@Parameter接收测试数据,也可以通过定义构造函数用于给实例变量赋值实现测试数据绑定到实例变量。
- 定义测试方法,使用实例变量。注意必须public修饰
测试类执行器使用Parameterized,即在测试类增加注解@RunWith(Parameterized.class)。
@RunWith(Parameterized.class)
public class ParameterizedTest {
/**
* 必须是public且用@Parameterized.Parameter注解,括号内的为某行的第几个测试数据
*/
@Parameterized.Parameter(1)
public Integer a;
@Parameterized.Parameter(0)
public Integer b;
private Calculate calculate;
/**
* 数据源,必须是public static,且方法必须返回测试数据集合
*/
@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));
}
}
识别测试用例
从上面参数化测试用例可以看出,参数化用例名默认为 :caseName[index]的形式。如果想要准确地识别生成的用例对应哪条数据比较困难。实际@Parameters有个name属性,可以指定参数,如下所示。
{index}: 代表当前参数在参数集合中的索引;
{0}, {1}, …: 代表第一个参数,第二个参数等;
/**
* 数据源,必须是public static,且方法必须返回测试数据集合
* name指定用例名称,默认使用测试数据索引序号
*/
@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},
});
}
- 套件测试
随着测试类的不断增加,如果组织和运行一批测试类成为关键。junit提供了测试套件功能,通过将一组相关的测试类组织在一个测试套件内,使其可以一次执行。测试套件执行,使用单独的执行器Suite.class。
集成多个TestCase类
@RunWith(Suite.class)注解的类为测试套件的入口类。
@Suite.SuiteClasses放入相关测试类
/**
* 套件类,以suite执行用例
*/
@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));
}
}
- 分组测试
方法级别的分组用@Category
- Category注解,可以标志在方法上,也可以标注在类上(所有执行方法和类Category保持一致)
- 一个执行方法可以归属于多个Category
- @RunWith(Categories.class) 也是一种套件执行器,必须和Suite.SuiteClasses一起使用
/**
* 分组测试
*/
@RunWith(Categories.class)
@Categories.IncludeCategory({FastTests.class})
@Suite.SuiteClasses({ATest.class, BTest.class})
public class GroupTestSuite {
}
/**
* 测试类别Fast
*/
public interface FastTests {
}
/**
* 测试类别Slow
*/
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");
}
}
1.3 集成spring
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
public abstract class AbstractTestCase {}
如果spring是xml配置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:appcontext-*.xml")
public abstract class AbstractTestCase {}
特别地集成spring和Parameters参数化测试用法:
@RunWith(Parameterized.class)
@ContextConfiguration(locations = {
"classpath*:config/spring/local/appcontext-*.xml"})
public class UserCouponCacheManager extends AbstractTestSurefire {
private TestContextManager testContextManager;
@Autowired
private SomeBean someBean;
@Parameterized.Parameter(0)
public Long uid;
@Parameterized.Parameter(1)
public Long couponId;
@Parameterized.Parameters(name = "{index}:uid={0},couponId={1}")
public static Collection data() {
return Arrays.asList(new Object[][]{
{1111L,111L},
{2222L,222L}
});
}
@Test
public void testBean(){
someBean.invokeWithParams(uid,couponId);
}
@Before
public void setup() throws Exception {
this.testContextManager = new TestContextManager(getClass());
this.testContextManager.prepareTestInstance(this);
}
特别地,其他需要同时使用RunWith(SpringJUnit4ClassRunner.class)和其他的RunWith执行器时,也可以这样使用。
1.4 集成springboot
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppBootStrap.class)
public abstract class AbstractOnlyJunit4TestCase {}
注意:springboot-test版本小于2.2时,只能和junit4一起使用不支持junit5
二、 junit5
2.1 基本介绍
junit5是Junit框架的一个大的更新,与以前版本的 JUnit 不同,JUnit 5由来自三个不同子项目的几个不同模块组成。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pd0bXn1L-1688991852160)(pics/junit5架构.png)]
JUnit Platform:是JVM 上启动测试框架的基础。它定义了 TestEngine API,用于开发在平台上运行的测试框架。此外,该平台还提供了一个 Console Launcher,用于从命令行启动平台,以及 JUnit Platform Suite Engine,用于在平台上使用一个或多个测试引擎运行自定义测试套件。
JUnit Jupiter:是用于编写 JUnit5中的测试和扩展的编程模型和扩展模型的组合,是Junit5的核心。该子项目提供了一个 TestEngine,用于在平台上运行基于 Jupiter 的测试。
JUnit Vintage:提供了一个 TestEngine,用于在平台上运行基于 JUnit3和 JUnit4的测试。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gie165Yv-1688991852161)(pics/junit5架构2.png)]
2.2 与junit4注解比较
juni4 | juit5 | 说明 |
---|---|---|
@org.junit.Test | org.junit.jupiter.api.Test | 注解测试用例 |
@BeforeClass | @BeforeAll | 在测试类内所有方法之前执行一次 |
@AfterClass | @AfterAll | 在测试类内所有方法之后执行一次 |
@Before | @BeforeEach | 在测试用例执行之前执行一次 |
@After | @AfterEach | 在测试用例执行之后执行一次 |
@Ignore | @Disabled | 注解测试用例忽略不执行 |
@Category | @Tag | 测试用例分类 |
无此特性 | @TestFactory | 动态测试用例生成工厂 |
无此特性 | @Nested | 嵌套测试 |
无此特性 | @ExtendWith | 注册定制扩展点,和junit4的@RunWith功能类似 |
2.3 基本使用
引入pom
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- 如果不需要用到junit4,可以不引入 -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
要想maven周期支持junit5,maven-surefire-plugin插件至少用2.22.0
或以上,jdk版本8及以上
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
<version>2.22.0</version>
</plugin>
- 基本使用
/**
* @BeforeAll 注解的必须是static方法
*/
@BeforeAll
public static void beforeAll() {
System.out.println("@BeforeAll: 在该测试类内所有方法之前执行,只执行一次");
}
@BeforeEach
public void beforeEachMethod() {
System.out.println("@BeforeEach: 在每个测试方法之前执行一次");
}
@Test
public void test1(){
System.out.println("test1");
}
@Test
public void test2(){
System.out.println("test2");
}
@AfterEach
public void afterEachMethod() {
System.out.println("@AfterEach: 在每个测试方法之后执行一次");
}
/**
* @AfterAll 注解的必须是static方法
*/
@AfterAll
public static void afterAll() {
System.out.println("@AfterAll: 在该测试类中所有测试方法执行完之后执行,只执行一次");
}
- 预期内异常|超时
/**
* 预期内的异常也算成功
*/
@Test(expected = RuntimeException.class)
public void expectedException(){
System.out.println("hello world! with expected exception");
throw new RuntimeException("expected exception error");
}
@Test
public void unexpectedException(){
System.out.println("hello world! with unexpected exception");
throw new RuntimeException("unexpected exception error");
}
/**
* 执行超时后直接异常
*/
@Test(timeout = 1000L)
public void timeOut() throws InterruptedException {
System.out.println("hello world! with timeOut");
Thread.sleep(2000L);
}
2.4 进阶使用
- 参数化测试
/**
* 注入一个基本类型或String/Class类型的数组
*/
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
public void valueSourceParameterTest(String str){
System.out.println("valueSourceParameterTest:"+str);
}
/**
* 注入枚举
*/
@ParameterizedTest
@EnumSource(Color.class)
public void enumSourceParameterTest(Color color){
System.out.println("enumSourceParameterTest:"+color);
}
/**
* 来自于一个static方法, 且方法返回值是stream
*/
@ParameterizedTest
@MethodSource("fromStream") //指定方法名
public void methodSourceParameterTest(String str){
System.out.println("methodSourceParameterTest:"+str);
}
/**
* 来自于一个csv文件,每行字段对应参数列表
*/
@ParameterizedTest
@CsvFileSource(resources = "/test.csv") //指定csv文件位置
public void csvFileParameterTest(String name,Integer age){
System.out.println("csvFileParameterTest: name="+name+",age="+age);
}
public static Stream<String> fromStream() {
return Stream.of("apple", "banana");
}
public enum Color{
RED,BLACK,BLUE;
}
- 套件
基于包
@Suite
@SelectPackages("com.wsl.my.junit.junit5.suite")
基于class
@Suite
@SelectClasses({AddTest.class,Demo1_TestUseTest.class})
- 分组tag
定义tag,可以基于方法也可以基于类
public class ATest {
@Tag("FastTests")
@Test
public void testA1(){
System.out.println("testA1");
}
@Test
public void testA2(){
System.out.println("testA2");
}
}
@Tag("SlowTests")
@Tag("FastTests")
public class BTest {
@Test
public void testB1() {
System.out.println("testB1");
}
@Test
public void testB2() {
System.out.println("testB2");
}
}
根据IncludeTags执行
@IncludeTags("FastTests")
@Suite
@SelectClasses({ATest.class, BTest.class})
public class Demo8_Tag1Test {}
根据IncludeTags和ExcludeTags执行
@IncludeTags("FastTests")
@ExcludeTags("SlowTests")
@Suite
@SelectClasses({ATest.class, BTest.class})
public class Demo8_Tag2Test {}
- 按指定顺序执行
方法1:按注解的顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class NestTest1{
@Test
@Order(1)
public void order1(){
System.out.println("OrderAnnotation order1");
}
@Test
@Order(2)
public void order2(){
System.out.println("OrderAnnotation order2");
}
}
方法2: 按displayName顺序
@TestMethodOrder(MethodOrderer.DisplayName.class)
class NestTest2{
@Test
@DisplayName("name2")
public void order1(){
System.out.println("DisplayName order1");
}
@Test
@DisplayName("name3")
public void order2(){
System.out.println("DisplayName order2");
}
}
方法3: 按方法名顺序
@TestMethodOrder(MethodOrderer.MethodName.class)
class NestTest3{
@Test
public void order1(){
System.out.println("MethodName order1");
}
@Test
public void order2(){
System.out.println("MethodNameorder2");
}
}
方法4: 随机顺序
@TestMethodOrder(MethodOrderer.Random.class)
class NestTest4{
@Test
public void order1(){
System.out.println("Random order1");
}
@Test
public void order2(){
System.out.println("Random order2");
}
}
- TestClass的执行顺序
src/test/resources/junit-platform.properties
中指定执行顺序
junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation
支持的顺序有以下,其中OrderAnnotation需要在class上标注@Order
ClassOrderer.ClassName
ClassOrderer.DisplayName
ClassOrderer.OrderAnnotation
ClassOrderer.Random
- 条件测试
系统变量满足条件执行/不执行,可以完全匹配或正则
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
// ...
}
@Test
@DisabledIfSystemProperty(named = "ci-server", matches = "true")
void notOnCiServer() {
// ...
}
根据方法返回值来决定执行/不执行
@Test
@EnabledIf("customCondition")
void enabled() {
// ...
}
@Test
@DisabledIf("customCondition")
void disabled() {
// ...
}
boolean customCondition() {
return true;
}
2.5 集成spring
仅支持junit5,不支持junit4
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public abstract class AbstractOnlyJunit5TestCase {}
支持junit5,同时兼容junit4
@ExtendWith(SpringExtension.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public abstract class AbstractMixJunitTestCase {}
注意:若仅需支持junit5,可以将junit相关包排掉,用junit5的API
<dependency>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</dependency>
2.6 集成springboot
仅支持junit5
@SpringBootTest(classes = AppBootStrap.class)
public abstract class AbstractOnlyJunit5TestCase {}
支持junit5,同时兼容junit4
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppBootStrap.class)
public abstract class AbstractMixJunitTestCase {}
注意:若仅需支持junit5,可以将junit相关包排掉,用junit5的API
<dependency>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</dependency>
<!-- 排除掉junit4的执行引擎 -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</dependency>