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=KaTeX parse error: Expected 'EOF', got '#' at position 12: {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);
});
}
}
Junit + allure 测试报告展示
allure 环境准备
官方参考连接:https://github.com/allure-examples
参考连接:https://blog.csdn.net/huggh/article/details/90905845
Allure 安装下载地址:
https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/
- 下载完成之后解压,加bin目录加载到环境变量path中
- 本地电脑需要具备java 环境
Allure 命令
Allure --help 帮助
Allure --version 查看版本信息
Allure serve 生成在线版本的测试
allure generate -o 输出目录
@Feature | 模块名称 |
---|
@Story | 用例名称 | @DisplyaName | 用例标题 | @Issue | 缺陷地址 | @Step | 操作步骤 | @Severity | 用例等级,blocker、critical、normal、minor、trivial | @link | 定义连接 | Allure.addAttachment | 附件 | | | | |
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<aspectj.version>1.9.1</aspectj.version>
<allure.version>2.13.6</allure.version>
<junit5.version>5.7.0</junit5.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit5.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit5.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit5.version}</version>
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>${allure.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<testFailureIgnore>false</testFailureIgnore>
<argLine>
-Dfile.encoding=UTF-8
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
<systemPropertyVariables>
<allure.results.directory>${project.build.directory}/allure-results</allure.results.directory>
<junit.jupiter.extensions.autodetection.enabled>true</junit.jupiter.extensions.autodetection.enabled>
<junit.jupiter.execution.parallel.enabled>true</junit.jupiter.execution.parallel.enabled>
<junit.jupiter.execution.parallel.config.strategy>dynamic</junit.jupiter.execution.parallel.config.strategy>
</systemPropertyVariables>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.10.0</version>
<configuration>
<reportVersion>${allure.version}</reportVersion>
<resultsDirectory>${project.build.directory}/allure-results</resultsDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
@Attachment
@Step("进行截图")
public byte[] makeScreenShot() {
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
}
@Attachment(value = "pic", type = "image/jpg",fileExtension = "jpg")
public byte[] makeScreenShot() throws IOException {
InputStream resourceAsStream = this.getClass().getResourceAsStream("/image/1.jpg");
byte[] bytes = new byte[1024 * 1024];
return Arrays.copyOfRange(bytes,0,resourceAsStream.read(bytes));
}
@Attachment
public String makeAttach() {
return "yeah, 2 is 2";
}
//
Allure.addAttachment("demo picture","image/jpg",this.getClass().getResourceAsStream("/image/1.jpg"),"jpg");
|