SpringBoot入门(十)单元测试
发布日期:2022-03-04 11:48:26 浏览次数:1 分类:技术文章

本文共 7926 字,大约阅读时间需要 26 分钟。

目录


第十章 单元测试JUnit5

10.1 JUnit5的变化

   SpringBoot2.2.0版本开始引入JUnit 5作为单元测试默认库。作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同,由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform:Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

  • JUnit Jupiter:JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。

  • JUnit Vintage:由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x、Junit3.x的测试引擎。

   注意:SpringBoot2.4以上版本移除了默认对Vintage的依赖(不能使用junit4的功能@Test)。如果需要兼容junit4需要自行引入

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

   现在版本的使用:

        引入依赖:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
</dependency>

@SpringBootTestclass Boot05WebAdminApplicationTests {
@Test
void contextLoads() {
}}

   以前的使用:@SpringBootTest + @RunWith(SpringTest.class)

   SpringBoot整合Junit以后:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,如@Autowired、@Transactional标注测试方法,测试完成后自动回滚

10.2 JUnit5常用注解

   JUnit5的注解与JUnit4的注解有所变化

  • @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一,不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest:表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest:表示方法可重复执行,下方会有详细介绍
  • @DisplayName:为测试类或者测试方法设置展示名称
  • @BeforeEach:表示在每个单元测试之前执行
  • @AfterEach:表示在每个单元测试之后执行
  • @BeforeAll:表示在所有单元测试之前执行
  • @AfterAll:表示在所有单元测试之后执行
  • @Tag:表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith:为测试类或测试方法提供扩展类引用
import org.junit.jupiter.api.*; //注意这里使用的是jupiter的Test注解!!/*@RunWith*//** * @BootstrapWith(SpringBootTestContextBootstrapper.class) * @ExtendWith(SpringExtension.class) */@SpringBootTest@DisplayName("junit5功能测试类")public class Junit5Test {
@Autowired
JdbcTemplate jdbcTemplate;
@DisplayName("测试displayname注解")
@Test
void testDisplayName() {
System.out.println(1);
System.out.println(jdbcTemplate);
}}
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始了...");
}
@AfterEach
void testAfterEach() {
System.out.println("测试结束了...");
}
@BeforeAll  //需要是静态方法
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
@AfterAll  //需要是静态方法
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
@Disabled  //禁用该测试方法
@DisplayName("测试方法2")
@Test
void test2() {
System.out.println(2);
}
/**
 * 规定方法超时时间。超出时间测试出异常
 *
 * @throws InterruptedException
 */
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(600);
}
@RepeatedTest(5)  //测试会自动运行5次
@Test
void test3() {
System.out.println(5);
}

10.3 断言assertions

   断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions静态方法。JUnit 5 内置的断言可以分成如下六个类别。

   断言机制的作用:

  • 检查业务逻辑返回的数据是否合理

  • 所有的测试运行结束以后,会有一个详细的测试报告,哪些方法成功,哪些方法失败

10.3.1 简单断言

   用来对单个值进行简单的验证(都是静态方法):

方法 说明

assertEquals

判断两个对象或两个原始类型是否相等

assertNotEquals

判断两个对象或两个原始类型是否不相等

assertSame

判断两个对象引用是否指向同一个对象

assertNotSame

判断两个对象引用是否指向不同的对象

assertTrue

判断给定的布尔值是否为true

assertFalse

判断给定的布尔值是否为false

assertNull

判断给定的对象引用是否为null

assertNotNull

判断给定的对象引用是否不为null

/**
 * 断言:前面断言失败,后面的代码都不会执行
 */
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions() {
int cal = cal(3, 2);
//相等
assertEquals(6, cal, "业务逻辑计算失败");
Object obj1 = new Object();
Object obj2 = new Object();
assertSame(obj1, obj2, "两个对象不一样");
}
//业务逻辑
int cal(int i, int j) {
return i + j;
}

10.3.2 数组断言

   通过assertArrayEquals方法来判断两个对象或原始类型的数组是否相等

@Test
@DisplayName("array assertion")
void array() {
//相等
assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不相等");
}

10.3.3 组合断言

   assertAll方法接受多个org.junit.jupiter.api.Executable函数式接口的实例作为要验证的断言,可以通过lambda表达式很容易的提供这些断言

@Test
@DisplayName("组合断言")
void all() {
/**
 * 所有断言全部需要成功
 */
assertAll("test",
() -> assertTrue(true && true, "结果不为true"),
() -> assertEquals(1, 2, "结果不是1"));
System.out.println("=====");
}

10.3.4 异常断言

   在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的,而JUnit5提供了一种新的断言方式Assertions.assertThrows(),配合函数式编程就可以进行使用。

@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}

10.3.5 超时断言

   Junit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间

@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

10.3.6 快速失败

   通过fail方法直接使得测试失败

@DisplayName("快速失败")
@Test
void testFail(){
//xxxxx
if(1 == 2){
fail("测试失败");
}
}

10.4 前置条件assumptions

   JUnit5中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@DisplayName("前置条件")public class AssumptionsTest { private final String environment = "DEV";  @Test @DisplayName("simple") public void simpleAssume() {
assumeTrue(Objects.equals(this.environment, "DEV"));
assumeFalse(() -> Objects.equals(this.environment, "PROD")); }  @Test @DisplayName("assume then do") public void assumeThenDo() {
assumingThat(
   Objects.equals(this.environment, "DEV"),
   () -> System.out.println("In DEV")
); }}

   assumeTrue和assumFalse确保给定的条件为true或false,不满足条件会使得测试执行终止。assumingThat的参数是表示条件的布尔值和对应的Executable接口的实现对象。只有条件满足时,Executable对象才会被执行;当条件不满足时,测试执行并不会终止。

10.5 嵌套测试

   JUnit5可以通过Java中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起,在内部类中可以使用@BeforeEach和@AfterEach注解,而且嵌套的层次没有限制。

@DisplayName("嵌套测试")class TestingAStackDemo {
Stack stack;
@Test
@DisplayName("new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
//嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行
assertNull(stack);//这里stack为null,不会先调用内部类中的BeforeEach方法创建stack对象
}
@Nested
@DisplayName("when new")
class WhenNew {//内部类
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());//判断栈是否为空,不含元素
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {//判断一定会抛出异常
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {//内部类中的内部类
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);//先插入一个元素
}
/**
 * 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行
 */
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());//不为空
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}}

10.6 参数化测试

   参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

   利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型、Class类型

  • @NullSource:表示为参数化测试提供一个null的入参

  • @EnumSource:表示为参数化测试提供一个枚举入参

  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
void testParameterized(int i){
System.out.println(i);
}
@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("stringProvider")
void testParameterized2(String i){
System.out.println(i);
}
static Stream  stringProvider() {
return Stream.of("apple", "banana","xxx");
}

10.7 迁移指南

   在进行迁移(将以前的版本迁移到JUnit5上)的时候需要注意如下的变化:

  • 注解在org.junit.jupiter.api包中,断言在org.junit.jupiter.api.Assertions类中,前置条件在org.junit.jupiter.api.Assumptions类中

  • 把@Before和@After替换成@BeforeEach和@AfterEach

  • 把@BeforeClass和@AfterClass替换成@BeforeAll和@AfterAll

  • 把@Ignore替换成@Disabled

  • 把@Category替换成@Tag

  • 把@RunWith、@Rule和@ClassRule替换成@ExtendWith

PS:根据尚硅谷课程整理,如有侵权,联系删除。

转载地址:https://blog.csdn.net/xxyneymar/article/details/122805077 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:SpringBoot入门(八)异常处理/Servlet组件注入/定制化
下一篇:SpringBoot入门(九)数据访问

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.36.148.248]2022年07月29日 20时58分46秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章