JUnit5实战

霍格沃兹测试开发学社

ceshiren.com

大纲

  • Xunit
  • 基本注解
  • 常用注解
  • 测试方法的执行顺序
  • 测试方法的参数化
  • Suite套件
  • TestInfo

XUnit体系结构

Test Case 测试用例
Assertions 断言
Test Execution 测试执行,以何种顺序执行
Test Fixture 测试装置,用来管理测试用例的执行
Test Suites 测试套件,用来编排测试用例
Test Runner 测试的运行器
Test Result Formatter 测试结果,具备相同的格式,可被整合

Junit5学习路线

Junit5 实战

  • 创建maven项目

  • 添加相关依赖到pom文件

  • 编写相关测试用例

pom依赖

    <properties>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <maven.compiler.version>3.10.1</maven.compiler.version>
        <maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
        <!-- 使用 Java 11 语言特性 ( -source 11 ) 并且还希望编译后的类与 JVM 11 ( -target 11 )兼容,
        可以添加以下两个属性,它们是默认属性插件参数的名称 -->
        <java.version>11</java.version>
        <!-- 对应junit Jupiter的版本号;放在这里就不需要在每个依赖里面写版本号,导致对应版本号会冲突 -->
        <junit.jupiter.version>5.9.1</junit.jupiter.version>
        <!-- log日志 -->
        <slf4j.version>2.0.3</slf4j.version>
        <logback.version>1.4.3</logback.version>
        <!--allure报告-->
        <allure.version>2.19.0</allure.version>
        <aspectj.version>1.9.5</aspectj.version>
        <allure.maven.version>2.11.2</allure.maven.version>
        <allure.cmd.download.url>
            https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline
        </allure.cmd.download.url>
    </properties>
    <!--    物料清单 (BOM)-->
    <dependencyManagement>
        <dependencies>
            <!--当使用 Gradle 或 Maven 引用多个 JUnit 工件时,此物料清单 POM 可用于简化依赖项管理。不再需要在添加依赖时设置版本-->
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit.jupiter.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--junit5-->
        <!-- 创建 Junit5 测试用例的 API-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <!--对应添加的依赖的作用范围-->
            <scope>test</scope>
        </dependency>
        <!-- 兼容 JUnit4 版本的测试用例-->
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <!--suite套件依赖 -->
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- log日志 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>compile</scope>
        </dependency>

        <!--        allure报告-->
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-junit5</artifactId>
            <version>${allure.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.version}</version>
                <configuration>
                    <!-- 设置jre版本为 11 -->
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <!-- 设置编码为 UTF-8 -->
                    <encoding>${maven.compiler.encoding}</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <configuration>
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                    <includes>
                        <include>**/suite/*Test</include>
                    </includes>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>${junit.jupiter.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                        <version>${junit.jupiter.version}</version>
                    </dependency>


                </dependencies>
            </plugin>
            <plugin>
                <groupId>io.qameta.allure</groupId>
                <artifactId>allure-maven</artifactId>
                <version>${allure.maven.version}</version>
                <configuration>
                    <reportVersion>${allure.version}</reportVersion>
                    <allureDownloadUrl>${allure.cmd.download.url}/${allure.version}/allure-commandline-${allure.version}.zip</allureDownloadUrl>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

日志 logback.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
        <pattern>%date %-5level %logger{36}.%M\(%line\) -- %msg%n</pattern>
    </encoder>

    <!-- name指定<appender>的名称    class指定<appender>的全限定名  ConsoleAppender的作用是将日志输出到控制台-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--输出时间格式 %-5level:级别从左显示5个字符宽度-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss}  %highlight%-5level %magenta([%thread]) %yellow(%logger{40}.%M\(%class{0}.java:%line\)) -- %msg%throwable%n</pattern>
        </encoder>
    </appender>
    <!-- 通过 "byModel" 将时间格式化成 "yyyyMMdd" 的形式插入到 logger 的上下文中这个值对后续的配置也适用-->
    <timestamp key="byModel" datePattern="yyyyMMdd" />

    <appender name="FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">

        <file>${byModel}.log</file>

        <!-- 配置日志所生成的目录以及生成文件名的规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${log.zip.path}/%d{yyyy-MM-dd}.%i.log.zip</FileNamePattern>
            <!-- 日志总保存量为1GB -->
            <totalSizeCap>1024MB</totalSizeCap>
            <!-- 如果按天来回滚,则最大保存时间为365天,365天之前的都将被清理掉 -->
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!--文件达到 最大128MB时会被压缩和切割 -->
                <maxFileSize>128MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>

        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{40}.%M\(%class{0}.java:%line\) -- %msg%n</pattern>

        </encoder>

    </appender>

    <logger name="top.testeru" level="DEBUG" />
    <logger name="ch.qos.logback" level="INFO" />
    <logger name="org" level="WARN" />

    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

实战练习

  • 产品:新型计算器

    • 1.多个数值连续加法运算

    • 2.两个值相减

    • 3.100连续减

    • 4.多个数值平均值

    • 5.多个字段拼接

场景需求

  • 被测系统数值相关计算,传递的数据类型为:整型

  • 取值范围:[-99 , 99]

  • 字段拼接传入的参数必须是字符串

业务代码

public class Calculator {
    //获得具有所需名称的记录器
    static final Logger logger = getLogger(lookup().lookupClass());

    public static int result = 0;

    //用例名
    String name;
    //唯一ID标识
    String id;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Calculator(String name) {
        this.name = name;
        logger.info("创建 {} ", name);
    }


    public void initId(){
        id = UUID.randomUUID().toString();
        logger.info("生成ID:{} 并绑定", id);

    }
    public void destroyId() {
        if (id == null) {
            throw new IllegalArgumentException(name + " 没有初始化对应ID");
        }
        logger.info("ID: {} 释放", id);
        id = null;
    }


    public void close() {
        logger.info("关闭 {} ", name);
    }


    //连续添加
    public int sum(int... numbers) {

        if(Arrays.stream(numbers).anyMatch(u -> u > 99) | Arrays.stream(numbers).anyMatch(u -> u < -99)){
            logger.warn("请输入范围内的整数");
            throw new IllegalArgumentException("请输入范围内的整数!");

        }else {
            return IntStream.of(numbers).sum();
        }

    }


    //从100进行减法
    public int subtract(int... numbers) {
        if(Arrays.stream(numbers).allMatch(u -> u > 99) | Arrays.stream(numbers).allMatch(u -> u < -99)){
            logger.warn("请输入范围内的整数");
            throw new IllegalArgumentException("请输入范围内的整数!");
        }else {
            return IntStream.of(numbers).reduce(100, (a, b) -> a-b);
        }
    }

    public int subtract(int x,int y) {
        if(x>99 | x<-99 | y>99 | y<-99){
            logger.warn("请输入范围内的整数");
            return 0;
        }else {
            return x-y;

        }
    }


    //平均值 average
    public double average(int... numbers) {
        if(Arrays.stream(numbers).allMatch(u -> u > 99) | Arrays.stream(numbers).allMatch(u -> u < -99)){
            logger.warn("请输入范围内的整数");
            return 0;
        }else {
            return IntStream.of(numbers).average().getAsDouble();
        }
    }


    //连续拼接
    public String concatStr(String... words) {
        return String.join(" ", words);

    }

}

实战练习

  • 题目:

    • 根据需求编写对应的测试用例:加法,减法,string拼接

    • 第一步:打开计算器

    • 在每个测试方法之前生成运行的唯一ID标识

    • 在每个测试方法之前log打印:开始进行计算

    • 在测试方法得到结果后log打印:计算结果:result

    • 在每个测试方法之后进行销毁ID操作

    • 在调用完所有测试用例后执行关闭计算器app操作

实战练习

  • 注意⚠️:

    • 每个测试用例都要添加断言,验证结果

    • 灵活使用测试装置

    • 边界值要进行验证

项目编写规则

  • 对应前置操作放在基础BaseTest测试类中

  • 对应业务逻辑测试代码在业务测试类中

运行顺序

  • 如果在 业务逻辑测试类 和 Base测试类 中都有以下注解的方法,且方法名不同
    • @BeforeAll
    • @BeforeEach
    • @AfterEach
    • @AfterAll
  • 对应执行顺序???

运行顺序

  • B extends A

    • B为业务代码

    • A为BaseTest

总结知识点

  • 基本的方法注解运行顺序

  • 继承关系下的运行顺序

  • 断言

  • 异常情况断言

实战练习2

  • 题目:

    • 使用参数化实现测试数据的动态传递

    • 添加自定义显示名

    • 自定义执行顺序

测试方法的执行顺序

  • 配置文件声明一次,全部测试类通用

  • 配置文件路径:

    • src/test/resources/junit-platform.properties
  • 配置文件声明内容:

    junit.jupiter.testmethod.order.default = org.junit.jupiter.api.MethodOrderer$OrderAnnotation
    

实战练习3

  • 题目:

    • 一个功能一个测试类

      • 数值相关计算的在nums包

      • 字符串相关的计算在strs包

    • 添加标签

    • 使用套件执行

      • 套件自定义显示名

TestInfo

  • 有关当前测试信息注入到 以下方法中
    • @Test
    • @RepeatedTest
    • @ParameterizedTest
    • @TestFactory
    • @BeforeEach
    • @AfterEach
    • @BeforeAll
    • @AfterAll

总结

  • 对应注解运行顺序

  • 类上和方法上共同的注解

    • Displayname Tag Disable
  • 参数化stream流

  • 顺序执行注解order

  • testInfo