Junit5 简单使用总结
作为一款测试框架,一般我们需要从以下几个方面去考虑
- TestCase : 测试用例的管理
- Assertions : 用例断言的管理
- Test Execution: 测试执行,以何种顺序执行
- Test Fixtures : 测试装置,测试用例运行的前后动作,用来管理测试用例的执行
- Test Suites: 测试套,控制用例批量运行
- Test Runner: 测试用例的运行器
- Test Result Report: 测试报告
Junit5 架构
junit5 大体由3个模块组成
- Junit platform 其主要作用在JVM 上启动测试框架,支持通过命令行Gradle maven 和Api 来运行平台
- Junit Jupiter :核心功能 用于编写测试用例
- Junit vintage : 承载兼容老版本的用例运行
工程环境搭建 参考官网在pom.xml 文件中引入依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- Only needed to run tests in a version of IntelliJ IDEA that bundles older versions -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.8.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!-- Maven 运行的依赖插件 -->
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
用例写作之常用注解
@Test | 标记在方法上,表示测试用例 |
---|---|
@BeforeEach | 表示被注解的方法,会在每个测试用例执行之前运行,@BeforeEach 注解支持继承 |
@AfterEach | 表示被注解的方法,会在每个测试用例执行之后运行,@AfterEach 注解支持继承 |
@BeforeAll | 表示在所有的测试方法运行之前执行, @BeforeAll 注解可以被继承 |
@AfterAll | 表示所有的测试方法运行之后执行 @BeforeAll 注解可以被继承 |
@DisplayName | 声明测试类或者测试方法显示的名称,此注解不会被继承 |
@Tag | 用于类或者方法上,做标签,后续可以根据标签来选择用例的执行,此注解用于类上可以被继承,用于方法上不可以被继承 |
@Disabled | 用于类或者方法上 ,禁用测试类 |
@Nested | 嵌套类的使用,但必须要求内部类是非静态内部类 |
@ParameterizedTest | 参数化 |
@RepeatedTest | 用例重复执行,可以用来测试并发场景 |
@order | 指定用例的执行顺序,传入数字即可 |
@TestMethodOrder | 指定用例以何种顺序执行,传入指定的MethodOrder 就可以自定义实现顺序https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order-methods |
@DisplayName("测试计算器")
@TestMethodOrder(MethodOrderer.MethodName.class)
public class CalculatorTest {
@BeforeAll
public static void beforeAll(){
System.out.println("Calculator test Before All");
}
@BeforeEach
public void beforeEach(){
System.out.println("Calculator test Before Each");
}
@Test
@DisplayName("测试加法正常运算")
public void testAdd(){
int result = Calculator.add(2,4);
System.out.println(result);
Assertions.assertEquals(result,6);
}
@Test
@DisplayName("测试减法运算")
@Disabled
public void testSubtraction(){
int result = Calculator.subtraction(10,6);
System.out.println("减法运算结果是:" + result);
Assertions.assertEquals(result,4);
}
@Test
@DisplayName("测试乘法运算")
@Order(2)
public void testMultiplication(){
int result = Calculator.multiplication(2,4);
System.out.println("乘法运算结果是:" + result);
Assertions.assertEquals(result,8);
}
@Test
@DisplayName("测试除法运算")
@Order(3)
public void testDivision(){
int result = Calculator.division(10,3);
System.out.println("除法运算结果是:" + result);
Assertions.assertEquals(result,3);
}
@AfterEach
public void afterEach(){
System.out.println("Calculator test After each");
}
@AfterAll
public static void afterAll(){
System.out.println("Calculator test After All");
}
}
@Nested 嵌套测试
public class NestedDemoTest {
@Test
@DisplayName("外层测试")
public void testOuter(){
System.out.println("outer....");
}
@DisplayName("内层测试")
@Nested
class Inner01{
@Test
@DisplayName("inner01 test")
public void test(){
System.out.println("inner01");
}
@Nested
@DisplayName("inner01 嵌套")
class InInner{
@Test
public void test(){
System.out.println("inner01 嵌套inner 01");
}
}
}
@Nested
@DisplayName("inner02")
class Inner02{
@Test
@DisplayName("inner02 test")
public void test(){
System.out.println("inner02");
}
}
}
@Nested
用例,然后再执行第二层嵌套的用例: 外层->倒叙嵌套->第二层嵌套
junit5 断言体系
- Assertions.assertEquals() 判断两个值是否相等
- Assertions.assertNotEquals() 判断两个值是否不相等
- Assertions.assertNull() 判断是否为空
- Assertions.asserNotNull() 判断是否不为空
- Assertions.assertSame() 判断两个对象的内存地址引用值是否相同
- Assertions.assertNotSame() 判断两个对象的内存地址引用值是否不相等
- assertTrue(Condition condition) 断言值为true
- asserFalse(Condition condition) 断言值为false
Junit5 软断言的支持,软断言是指第一步校验不过,还可以继续向下校验
Assertions.assertAll(Executable… excutable) Executable 是一个函数式接口,可以通过lambda 表达式进行传值, …代表可变参数列表
Assertions.assertAll(() -> Assertions.assertEquals(result,5));
多线程并发测试
在resources 目录下新建junit 配置文件 junit-platform.properties https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution
# 是否允许并行测试 true/false
junit.jupiter.execution.parallel.enabled=true
# 是否允许测试方法并行执行,same_thread 同一线程执行, concurrent 多线程并发执行
junit.jupiter.execution.parallel.mode.default=concurrent
# 是否允许测试类之间的并发执行
junit.jupiter.execution.parallel.mode.classes.default = same_thread
# 配置并行策略fixed 或者dynamic 使用fixed 需要自行配置多线程并发执行数
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 2
在测试用例上用@RepeatTest 注解来实现并发允许
@RepeatedTest(10)
public void testCount() throws InterruptedException {
int result = Calculator.count(1);
System.out.println(result);
}
测试套件的运行以及指定测试类
参考: https://junit.org/junit5/docs/current/user-guide/#testkit-engine
在pom.xml 文件中添加依赖
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-engine</artifactId>
<scope>test</scope>
</dependency>
在测试类中添加@Suit 注解创建测试套
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;
@Suite
@SuiteDisplayName("批量测试套")
@IncludeClassNamePatterns("*Test")
@IncludeTags({})
@SelectClasses({})
@ExcludeClassNamePatterns("")
@ExcludePackages({})
@ExcludeTags({})
@SelectPackages({"com.huawei.test"})
public class SuitDemo {
}
@Suite | 创建测试套件 |
---|---|
@SuiteDisplayName | 测试套命名 |
@SelectClass() | 选择加入测试套的用例 |
@SelectPackage() | 选择包下面的所有用例 |
@IncludeClassNamePatterns("*Test") | 选择运行类名包含Test |
@ExcludeClassNamePatterns("") | 排除类名符合某种规则 |
@ExcludePackages({}) | 排除选择的包 |
@ExcludeTags({}) | 排除被Tag 标记的测试用例 |
命令行 利用mvn test 和Maven Surefire plugin 插件做测试用例的执行管理
- mvn test 执行全量的用例
- mvn -Dtest=${TestClass} test 指定测试用例运行 如 mvn -DTest =SuitDemo test
- mvn -Dtest= T e s t C l a s s 1 , {TestClass1}, TestClass1,{TestClass2} test 指定多个测试类运行
- mvn -Dtest=${*TestClass} 通过匹配的方式指定测试类的运行
- mvn -Dtest=${TestClass}#${TestMethod} 指定测试方法运行
用例参数化
public class ParamDemoTest {
@ParameterizedTest
@ValueSource(ints={1,3})
public void testCount(int x) throws InterruptedException {
int result = Calculator.count(x);
System.out.println(result);
}
@ParameterizedTest
@CsvSource(value = {"zhangsan,22,男","lisi,33,女"})
public void testCsv(String arg1 ,String arg2,String arg3){
System.out.println("arg1:" + arg1);
System.out.println("arg2:" + arg2);
System.out.println("arg3:" + arg3);
}
@ParameterizedTest
@CsvFileSource(resources = "/data/user.csv")
public void testCsvFile(String arg1 ,String arg2){
System.out.println("arg1:" + arg1);
System.out.println("arg2:" + arg2);
}
/**
* @MethodSource 指定数据源的来源方法,方法的返回值可以时Array List Stream 等可迭代的对象
*/
@ParameterizedTest
@MethodSource(value = {"getData"})
public void testMethod(String arg){
System.out.println(arg);
}
@ParameterizedTest
@MethodSource(value = {"getMap"})
public void testMethod1(Map.Entry entry){
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
public void testGetYaml(){
}
static Iterator<Map.Entry<String, String>> getMap(){
HashMap<String,String> map = new HashMap<>();
map.put("name","zhangsan");
map.put("sex","男");
return map.entrySet().iterator();
}
static Stream<String> getData(){
return Stream.of("zhangsan","lisi");
}
@Test
public void testYaml() throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
// TypeReference typeReference = new TypeReference() {
// }
TypeReference typeReference = new TypeReference<List<YamlData>>() {
};
List<YamlData> yamlData = objectMapper.readValue(this.getClass().getResourceAsStream("/data/testdata.yaml"),typeReference);
yamlData.forEach((data) ->{
System.out.println(data.username + data.age);
});
}
}
利用Jackson + yaml 实现参数化步骤:
参考链接:https://www.baeldung.com/jackson-yaml
-
pom.xml 文件中添加Jackson 的依赖包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency> -
添加jackson 支持yaml的依赖包
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.8</version>
</dependency> -
编写yaml
-
username: zhangsan
age: 20
computers:
- brand: 华为
price: 19999
- brand: 小米
price: 9999.9
action:
eat: 大鱼大肉
sleep: 10小时
map:
id: 001
step: 向后走
-
username: lisi
age: 30
computers:
- brand: mac
price: 29999
action:
eat: 青菜萝卜
sleep: 10小时
map:
id: 002
step: 向前走
//yaml 以缩进代表层级关系 不允许tab 以两个空格代表缩进 -
定义对应格式的实体类,注意如果属性是私有属性提供响应get/set 方法
@JsonIgnoreProperties(ignoreUnknown = true)
public class YamlData {
private String username;
private int age;
private List<Computer> computers;
private Action action;
private HashMap<String,String> map;
//get/set 方法省略....
} -
编写相应的测试类
@Test
public void testYaml() throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
// TypeReference typeReference = new TypeReference() {
// }
TypeReference typeReference = new TypeReference<List<YamlData>>() {
};
List<YamlData> yamlData = objectMapper.readValue(this.getClass().getResourceAsStream("/data/testdata.yaml"),typeReference);
yamlData.forEach((data) ->{
System.out.println(data.username + data.age);
});
}
}