当前位置 : 首页 » 文章分类 :  开发  »  JUnit

JUnit

JUnit 笔记


单测真的很有用

Jpa 持久化层写了一些条件拼接,类似下面这种:

public Page<UserDO> query(QueryRequest req) {
    BooleanBuilder builder = new BooleanBuilder();
    if (Objects.nonNull(req.getId())) {
        builder.and(qUserDO.id.eq(req.getId()));
    }
    if (StringUtils.isNotBlank(req.getUserName())) {
        builder.and(qUserDO.userName.like(String.format("%%%s%%", req.getUserName())));
    }
    if (StringUtils.isNotBlank(req.getUserNickName())) {
        builder.and(qUserDO.userNickName.like(String.format("%%%s%%", req.getUserName())));
    }
    Pageable pageable = PageUtils.toPageable(req.getPageRequest());
    return findAll(builder, pageable);
}

写单测时发现根据昵称查询返回的数据个数 Assertion 失败,检查代码发现 query() 方法中检查的是 req.getUserNickName(), 但拼接的是 req.getUserName(), 应该是当时手抖写错了,如果没有单测真的很难发现这种bug


Assert Exception 断言异常

JUnit5 Jupiter 断言异常(assertThrows())

JUnit 5 Jupiter 提供了一个 assertThrows() 来断言异常

public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
    return AssertThrows.assertThrows(expectedType, executable);
}

当期望的异常被抛出时,assertThrows()返回该异常,之后我们可以继续断言异常的message等信息。
例如

@Test
public void whenExceptionThrown_thenAssertionSucceeds() {
    Exception e = Assertions.assertThrows(IllegalArgumentException.class, () -> service.delete(idsArg));
    log.info(e.getMessage());
    Assertions.assertTrue(e.getMessage().contains("无法删除ids"));
}

注意只有 expectedType 或其子类被抛出时断言才满足。假如抛出的不是期望的异常,assertThrows() 本身就会抛出异常,例如
期望 NumberFormatException 异常,其实闭包方法抛出的是 IllegalArgumentException 异常

org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <java.lang.NumberFormatException> but was: <java.lang.IllegalArgumentException>

JUnit4断言异常(@Test(expected))

直接在 @Test 注解内增加 expected 参数指定期望的异常,当期望的异常或其子类异常被抛出时单测成功,期望的异常未抛出时单测失败。

@Test(expected = NullPointerException.class)
public void whenExceptionThrown_thenExpectationSatisfied() {
    String test = null;
    test.length();
}

Assert an Exception is Thrown in JUnit 4 and 5
https://www.baeldung.com/junit-assert-exception


Spring Boot Junit无法执行问题汇总

测试方法必须是 public 的,private 测试方法无法执行
在测试方法上要有 @Test 注解
测试方法不能用 static 静态修饰
测试方法不能有返回值,带返回值的测试方法无法执行
测试方法不能有参数


指定方法执行顺序

默认情况下,JUnit 单测类中各个 @Test 单测方法的执行顺序是 确定但不可预测的(deterministic but not predictable)

JUnit 4.11 @FixMethodOrder

从 junit 4.11 开始, Junit提供一个 @FixMethodOrder 注解(annotation)来控制测试方法的执行顺序。

@FixMethodOrder 注解的参数可选值有下面3个:
MethodSorters.JVM 按照JVM得到的方法顺序,并不一定是方法在代码中定义的顺序,这与JVM的实现有关
MethodSorters.DEFAULT(默认的顺序) 以确定但不可预期的顺序执行
MethodSorters.NAME_ASCENDING 按方法名字母顺序执行

注意:在junit5中这个注解又被删除了
https://github.com/junit-team/junit5/issues/13

JUnit 5 @Order

按照 Order 编号从小到大执行

@Test
@Order(1)
public void firstTest() {
    output.append("a");
}

@Test
@Order(2)
public void secondTest() {
    output.append("b");
}

JUnit5

JUnit 5 User Guide
https://junit.org/junit5/docs/current/user-guide/

官方提供的 Example Projects
junit-team / junit5-samples
https://github.com/junit-team/junit5-samples/tree/r5.6.1/junit5-jupiter-starter-maven

junit-vintage-engine 和 junit-jupiter-engine

junit-vintage-engine 是 JUnit 4 中使用的测试引擎。
junit-jupiter-engine 是 JUnit 5 中使用的测试引擎。

Spring Boot 2.4 之前,如果想使用 JUnit 5 的话,需要将 JUnit 4 的 vintage 引擎从 spring-boot-starter-test 中删除

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

从 Spring Boot 2.4 开始,spring-boot-starter-test 中不再包含 junit-vintage-engine,所以也不再需要将 vintage 从 spring-boot-starter-test 中删除。
但如果想继续使用 JUnit 4,需要单独引入 junit-vintage-engine

<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>

@ExtendWith 代替 @RunWith

JUnit5中不再使用 @RunWith(SpringRunner.class) 替换为 @ExtendWith(SpringExtension.class)

JUnit4中:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
    @Test
    public void contextLoads() {
    }
}

JUnit5中:

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class ApplicationTests {
    @Test
    public void contextLoads() {
    }
}

@BeforeAll 以及 @AfterAll

@BeforeEach 替换 @Before
@BeforeAll 替换 @BeforeClass
@AfterEach 替换 @After
@AfterAll 替换 @AfterClass

@BeforeAll method must be static

org.junit.platform.commons.JUnitException: @BeforeAll method ‘public void com.masikkk.persistence.BaseJpaTest.setUp()’ must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).

JUnit5 中, @BeforeAll 注解的前置方法必须是 static 的(因为是类级别的),或者增加 @TestInstance(Lifecycle.PER_CLASS) 注解。

测试实例生命周期

JUnit5 中默认测试实例生命周期是方法级的,即 PER_METHOD
修改 junit.jupiter.testinstance.lifecycle.default 参数为 TestInstance.Lifecycle 类中定义的常亮即可。
例如 -Djunit.jupiter.testinstance.lifecycle.default=per_class

2.10.1. Changing the Default Test Instance Lifecycle
https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle-changing-default

Assertions 代替 Assert


普通Java项目改为Maven项目并使用JUnit5

项目上点右键 -> Add Framework Support -> 勾选 Maven,则会自动添加一个 pom.xml 配置,之后就可以使用 mavne 引入需要的依赖了。
maven 默认使用 src/main/java 和 src/test/java 作为源码目录(Source Folders),如果不想使用默认的源码目录,可以自己在 Project 中配置
进入 File -> Project Structure -> Modules

默认是如下图这样的

默认Maven项目的源码目录

比如我的 algorithms 项目,只是独立的算法题代码,转为 maven 项目只是为了方便引入 junit 测试框架,就直接把根目录 . 设为 Test Source Folders ,方便直接使用 junit,改为下图这样


上一篇 LeetCode.289.Game of Life 生命游戏

下一篇 LeetCode.1111.Maximum Nesting Depth of Two Valid Parentheses Strings 有效括号的嵌套深度

阅读
评论
1.4k
阅读预计6分钟
创建日期 2020-04-02
修改日期 2021-05-24
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论