Java单元测试框架(二)——JUnit5

简介: Java单元测试框架(二)——JUnit5

1.原理图


image.png


2. 基本架构


被测代码

package com.jerry;
public class Calculator{
    private static int result; 
    public void add(int n) {
        result = result + n;
    }
    public void substract(int n) {
        result = result - n;  
    }
    public void multiply(int n) {
        result = result * n;
    } 
    public void divide(int n){
        try {
            result = result / n;
        }catch(ArithmeticException ex){
            System.out.println(ex);
            throw new ArithmeticException("The n not allowed to 0!!")
        }
}
}


最基本的测试代码

public class CalculatorTest {
    private static Calculator calculator = new Calculator();
    @BeforeEach
    public void setUp() throws Exception {
        calculator.clear();
    }
    @Test
    @DisplayName("测试加法")
    public void testAdd() {
        calculator.add(2);
        calculator.add(3);
        assertEquals(5, calculator.getResult());
    }
    @Test
    @DisplayName("测试减法")
    public void testSubstract() {
        calculator.add(5);
calculator.substract(3);
        Assertions.assertEquals(2, calculator.getResult());
    }
    @Test
    @DisplayName("测试乘法")
    public void testMultiply() {
        calculator.add(3);
        calculator.multiply(2);
        Assertions.assertEquals(6, calculator.getResult());
    }
    @Test
    @DisplayName("测试除法")
    public void testDivide() {
        calculator.add(9);
        calculator.divide(3);
        Assertions.assertEquals(3, calculator.getResult());
    }


标签@DisplayName可以在测试结果的时候显示其内容。

 

image.png


3.JUnit5的修饰符


修饰符

含义

@DisplayName

为测试类或者测试方法设置展示名称

@BeforeAll

表示在所有单元测试之前执行

@AfterAll

表示在所有单元测试之后执行

@BeforeEach

表示在每个单元测试之前执行

@AfterEach

表示在每个单元测试之后执行

@Disabled

表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

@Timeout 

表示测试方法运行如果超过了指定时间将会返回错误,类似于JUnit4中的(timeout=XXX) JUnit 5.5.2以后

@RepeatedTest

表示方法可重复执行

@ParameterizedTest 

表示方法是参数化测试,类似于JUnit4中的@RunWith(Parameterized.class)

@Tag

表示单元测试类别,类似于JUnit4中的@Categories

@ExtendWith

为测试类或测试方法提供扩展类引用


描述装饰符的程序

package com.jerry;
import static org.junit.jupiter.api.Assertions.assertAll;
import org.junit.jupiter.api.*;
public class MyFirstJunit5Test {
  @BeforeAll
    @DisplayName("每条用例开始时执行")
  public static void startALL(){
    System.out.println("BeforeAll");
    }
  @AfterAll
    @DisplayName("每条用例开始时执行")
  public static void endAll(){
    System.out.println("AfterAll");
    }
  @BeforeEach
    @DisplayName("每条用例开始时执行")
    void start(){
    System.out.println("BeforeEach");
    }
    @AfterEach
    @DisplayName("每条用例结束时执行")
    void end(){
      System.out.println("AfterEach");
    }
    @Test
    void myFirstTest() {
      System.out.println("myFirstTest");
      Assertions.assertEquals(2, 1 + 1);
    }
    @Test
    @DisplayName("描述测试用例")
    void testWithDisplayName() {
      System.out.println("testWithDisplayName");
    }
    @Test
    @Disabled("这条用例暂时跑不过,忽略!")
    void myFailTest(){
      System.out.println("Disabled Testcase");
      Assertions.assertEquals(1,2);
    }
    @Test
    @DisplayName("运行一组断言")
    public void assertAllCase() {
      System.out.println("运行一组断言");
        assertAll("groupAssert",
                () -> Assertions.assertEquals(2, 1 + 1),
                () -> Assertions.assertTrue(1 > 0)
        );
    }
    @Test
    @DisplayName("依赖注入1")
    public void testInfo(final TestInfo testInfo) {
        System.out.println(testInfo.getDisplayName());
    }
    @Test
    @DisplayName("依赖注入2")
    public void testReporter(final TestReporter testReporter) {
        testReporter.publishEntry("name", "Alex");
    }
}


运行结果(缩进为了看起来方便,我自己设置的)

BeforeAll
     BeforeEach
        依赖注入1
    AfterEach
    BeforeEach
        TestIdentifier [依赖注入2]
         ReportEntry [timestamp = 2020-08-21T11:38:49.361976500, name = 'Alex’]
     AfterEach
    BeforeEach
        myFirstTest
    AfterEach
    BeforeEach
         testWithDisplayName
    AfterEach
    BeforeEach
       运行一组断言
    AfterEach
AfterAll


注意:@BeforeAll、 @AfterALL注解方法必须是静态方法,否则会抛出运行时错误。


4. JUnit5 新加断言


断言方法

断言描述

assertTimeoutPreemptively

超时断言

assertThrows

异常断言


5. 异常断言


被测的程序

public void divide(int n){
      try {
        result = result / n;
      }catch(ArithmeticException ex){
        System.out.println(ex);
        throw new ArithmeticException("The n not allowed to 0!!");
      }
    }


测试程序

@Test
    @DisplayName("测试除0异常")
    public void testDivideByZero() {
        calculator.add(9);
        Throwable exception = Assertions.assertThrows(ArithmeticException.class, () -> calculator.divide(0));
        Assertions.assertEquals("The n not allowed to 0!!",exception.getMessage());
  }


6. 超时断言


@Test
    public void squareRoot()  {
       Assertions.assertTimeoutPreemptively(Duration.of(2, ChronoUnit.SECONDS), () -> calculator.squareRoot(4))
}


判断程序是否应该在两秒钟执行完毕。类似于JUnit4中的(timeout=XXX)。


7. 参数化测试


7.1单参数

@ParameterizedTest
@ValueSource(ints = {1,2,3,4})
@DisplayName("参数化测试_单参数")
public void parameterizedTest(int param) {
    calculator.add(param);
    calculator.add(-1 * param);
    Assertions.assertEquals(0, calculator.getResult());
}


标签:

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参


ValueSource类型:

  • String values: @ValueSource(strings = {"foo", "bar", "baz"})
  • Double values: @ValueSource(doubles = {1.5D, 2.2D, 3.0D})
  • Long values: @ValueSource(longs = {2L, 4L, 8L})
  • Integer values: @ValueSource(ints = {2, 4, 8})


7.2 Enum参数

import java.util.concurrent.TimeUnit;
@ParameterizedTest
@EnumSource(value = TimeUnit.class, names = {"SECONDS", "MINUTES"})
@DisplayName("参数化测试_Enum参数")
void testTimeUnitJustSecondsAndMinutes(TimeUnit unit) {
Assertions.assertTrue(EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES).contains(unit));
Assertions.assertFalse(EnumSet
          .of(TimeUnit.DAYS, TimeUnit.HOURS, TimeUnit.MILLISECONDS, TimeUnit.NANOSECONDS,
                TimeUnit.MICROSECONDS).contains(unit));
}


7.3参数化测试——方法参数(多参数)

注意:这个方法返回必须是个流
    @ParameterizedTest
    @MethodSource("paramGenerator")
    @DisplayName("参数化测试_ 方法参数(多参数)")
    void MethodParameForSquareRoot(int param, int result){    
        calculator.squareRoot(param);
        Assertions.assertEquals(result, calculator.getResult());
    }
    static Stream<Arguments> paramGenerator(){
        return Stream.of(Arguments.of(4,2), Arguments.of(9,3), Arguments.of(16,4));
    }


7.4.参数化测试——CVS文件参数  

@ParameterizedTest
    @CsvFileSource(resources = "data.csv")  //指定csv文件位置
    @DisplayName("参数化测试-csv文件")
    public void parameterizedCVSFile(int param, int result) {
        calculator.squareRoot(param);
        Assertions.assertEquals(result, calculator.getResult());
    }


其中,data.csv为:

4  2
9  3
16  4


8.内嵌测试类


一般一个产品类对应一个测试类,但是使用JUnit,可以实现类的嵌套。


package com.jerry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
public class NestedTestDemo {
    @Test
    @DisplayName("Nested")
    void isInstantiatedWithNew() {
        System.out.println("最一层--内嵌单元测试");
    }
    @Nested
    @DisplayName("Nested2")
    class Nested2 {
        @BeforeEach
        void Nested2_init() {
            System.out.println("Nested2_init");
        }
        @Test
        void Nested2_test() {
            System.out.println("第二层-内嵌单元测试");
        }
        @Nested
        @DisplayName("Nested3")
        class Nested3 {
            @BeforeEach
            void Nested3_init() {
                System.out.println("Nested3_init");
            }
            @Test
            void Nested3_test() {
                System.out.println("第三层-内嵌单元测试");
            }
        }
    }
}


结果输出:

第一层--内嵌单元测试
Nested2_init
第二层-内嵌单元测试
Nested2_init
Nested3_init
第三层-内嵌单元测试

image.png


9. 重复测试


@RepeatedTest(5) //表示重复执行5次
@DisplayName("重复减法")
public void testSubstractManyTimes() {
    calculator.add(5);
    calculator.substract(3);
    Assertions.assertEquals(2, calculator.getResult());
}


这个测试用例将被执行5次,为什么设计这个方法,我个人没有理解。


10. 动态测试


TestFactory
@DisplayName("动态测试")
Iterator<DynamicTest> dynamicTests() {
    return Arrays.asList(
        dynamicTest("第1个动态测试", () ->{calculator.squareRoot(4);Assertions.assertEquals(2, calculator.getResult());}),
        dynamicTest("第2个动态测试", () ->{calculator.squareRoot(9);Assertions.assertEquals(3, calculator.getResult());}),
        dynamicTest("第3个动态测试", () ->{calculator.squareRoot(16);Assertions.assertEquals(4, calculator.getResult());}),
        dynamicTest("第4个动态测试", () ->{calculator.squareRoot(25);Assertions.assertEquals(5, calculator.getResult());})
    ).iterator();
 }


动态测试用得比较少,这个功能的原因我个人也不太理解。


11分组断言assertAll


@Test
@DisplayName("开根号分组断言")
public void testGroup() {
    int[] parem = {4, 9, 16, 25, 36};
    int[] result ={2, 3, 4, 5, 6};
    Assertions.assertAll("parem,result",
        () -> {calculator.squareRoot(parem[0]);Assertions.assertEquals(result[0], calculator.getResult());},
        () -> {calculator.squareRoot(parem[1]);Assertions.assertEquals(result[1], calculator.getResult());},
        () -> {calculator.squareRoot(parem[2]);Assertions.assertEquals(result[2], calculator.getResult());},
        () -> {calculator.squareRoot(parem[3]);Assertions.assertEquals(result[3], calculator.getResult());},
        () -> {calculator.squareRoot(parem[4]);Assertions.assertEquals(result[4], calculator.getResult());}
   );


分组断言中任一个断言的失败,都会将以 MultipleFailuresError 错误进行抛出提示。另外可以看出,使用分组断言也可以实现参数化

目录
相关文章
|
3天前
|
Java 测试技术 数据库
【JAVA基础篇教学】第十七篇:Java单元测试
【JAVA基础篇教学】第十七篇:Java单元测试
|
14小时前
|
监控 数据可视化 IDE
python自动化测试实战 —— 单元测试框架
python自动化测试实战 —— 单元测试框架
8 2
|
3天前
|
存储 安全 Java
Java一分钟之-集合框架进阶:Set接口与HashSet
【5月更文挑战第10天】本文介绍了Java集合框架中的`Set`接口和`HashSet`类。`Set`接口继承自`Collection`,特征是不允许重复元素,顺序不确定。`HashSet`是`Set`的实现,基于哈希表,提供快速添加、删除和查找操作,但无序且非线程安全。文章讨论了`HashSet`的特性、常见问题(如元素比较规则、非唯一性和线程安全性)以及如何避免这些问题,并提供了代码示例展示基本操作和自定义对象的使用。理解这些概念和注意事项能提升代码效率和可维护性。
9 0
|
3天前
|
存储 安全 算法
Java一分钟之-Java集合框架入门:List接口与ArrayList
【5月更文挑战第10天】本文介绍了Java集合框架中的`List`接口和`ArrayList`实现类。`List`是有序集合,支持元素重复并能按索引访问。核心方法包括添加、删除、获取和设置元素。`ArrayList`基于动态数组,提供高效随机访问和自动扩容,但非线程安全。文章讨论了三个常见问题:索引越界、遍历时修改集合和并发修改,并给出避免策略。通过示例代码展示了基本操作和安全遍历删除。理解并正确使用`List`和`ArrayList`能提升程序效率和稳定性。
7 0
|
5天前
|
测试技术
测试基础 Junit单元测试框架
测试基础 Junit单元测试框架
11 2
测试基础 Junit单元测试框架
|
5天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
24 0
|
5天前
|
存储 安全 Java
深入理解Java集合框架
深入理解Java集合框架
10 0
|
13天前
|
Java 测试技术 Maven
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
|
13天前
|
Java API 数据安全/隐私保护
【亮剑】如何在Java项目中结合Spring框架实现邮件发送功能
【4月更文挑战第30天】本文介绍了如何在Java项目中结合Spring框架实现邮件发送功能。首先,需在`pom.xml`添加Spring和JavaMail依赖。然后,在`applicationContext.xml`配置邮件发送器,包括SMTP服务器信息。接着,创建一个使用依赖注入的`EmailService`类,通过`JavaMailSender`发送邮件。最后,调用`EmailService`的`sendSimpleEmail`方法即可发送邮件。最佳实践包括:使用配置管理敏感信息,利用`MimeMessage`构造复杂邮件,异常处理和日志记录,以及在大量发送时考虑使用邮件队列。
|
16天前
|
IDE 测试技术 持续交付
【专栏】利用Python自动化测试与单元测试框架提升代码质量与效率
【4月更文挑战第27天】本文探讨了Python自动化测试与单元测试框架在提升代码质量与效率中的作用。Selenium、Appium用于Web和移动应用自动化测试,pytest提供强大、易扩展的测试支持。unittest是Python标准的单元测试框架,支持结构化测试用例和丰富的断言。实践中,应制定测试计划,编写高质量测试用例,实行持续集成与测试,并充分利用测试报告。这些工具和策略能有效保障代码质量和提升开发效率。