发布时间:2025-12-10 11:42:32 浏览次数:15
目录
一、什么是 JUnit
二、JUnit5 相关技术
1.注解
1.1 @Test
1.2 @Disabled
1.3 @BeforeAll、@AfterAll
1.4 @BeforeEach、@AfterEach
2.参数化
2.1 单参数
2.2 CSV 获取参数
2.3 方法获取参数
2.4 多参数
3.测试用例的执行顺序
3.1 顺序执行:@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
3.2 随机执行:@TestMethodOrder(MethodOrderer.class)
4.断言
5.测试套件
5.1 通过 class 运行测试用例
5.2 通过包运行测试用例
🌈本节课我们来学习单元测试框架 Junit,这里的单元测试指的是对最小的软件设计单元(模块)进行验证,在UI自动化测试里面,我们的单元测试主要针对UI界面的功能进行自动化测试。
JUnit是一个Java单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
注意:Junit 测试也是程序员测试,即所谓的白盒测试,它需要程序员知道被测试的代码如何完成功能,以及完成什么样的功能
它包括以下特性:
使用 Junit 能让我们快速的完成单元测试。
🌈通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
通常我们使用 JUnit5 版本
注解(也可以称为 元数据)为在代码中添加信息提供了一种形式化的方法,使得在代码中任一时刻可以非常方便的使用这些数据;注解类型定义了一种新的特殊接口类型,在接口关键期 interface 之前加@符号,即用@interface即可区分注解与普通接口声明。目前大部分框架都是通过使用注解简化代码提高编码效率
JUnit提供了非常强大的注解功能,通过 @Test 注解修饰到方法上,该方法就变为了一个测试方法,执行当前类时,会自动的执行该类下所有带 @Test 注解的用例
使用这些JUnit提供的注解时,需要在pom.xml文件中进行配置(记得配置完要进行刷新):maven中央仓库
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.2</version><scope>test</scope></dependency>
<dependencies><!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.9.2</version></dependency></dependencies>在 main 文件中,我们需要导入 Test 包:
我们写一个测试用例:
import org.junit.jupiter.api.Test;public class JUnitDemo1 {@Testvoid Test01() {System.out.println("这是 JunitDemo1 里的 Test01");}@Testvoid Test02() {System.out.println("这是 JunitDemo1 里的 Test02");}}❗❗需要注意的是写的代码几个运行按钮:
其中“对号”表示测试通过;“ !”表示测试错误
Disabled 表示 忽略
import org.junit.jupiter.api.Disabled;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;public class JUnitDemo1 {@Testvoid Test01() {System.out.println("这是 JunitDemo1 里的 Test01");}@Testvoid Test02() {System.out.println("这是 JunitDemo1 里的 Test02");}@Disabledvoid Test03() {WebDriver webDriver = new ChromeDriver();webDriver.get("https://www.baidu.com");webDriver.findElement(By.cssSelector("#kw")).sendKeys("521");webDriver.findElement(By.cssSelector("#su")).click();}}
此时我们看到这个代码就不执行 Test03
@BeforeAll:当前的方法需要在当前类下所有用例执行之前执行一次,且被该注解修饰的方法必须为静态方法
@AfterAll:当前的方法需要在当前类下所有用例执行之后执行一次,且被该注解修饰的方法必须为静态方法
例如在 UI 自动化中:通常情况下,创建文件、打开网页放在BeforeAll 里边;关闭浏览器放在AfterAll 里边
import org.junit.jupiter.api.AfterAll;import org.junit.jupiter.api.BeforeAll;import org.junit.jupiter.api.Disabled;import org.junit.jupiter.api.Test;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;public class JUnitDemo1 {@Testvoid Test01() {System.out.println("这是 JunitDemo1 里的 Test01");}@Testvoid Test02() {System.out.println("这是 JunitDemo1 里的 Test02");}@Disabledvoid Test03() {WebDriver webDriver = new ChromeDriver();webDriver.get("https://www.baidu.com");webDriver.findElement(By.cssSelector("#kw")).sendKeys("521");webDriver.findElement(By.cssSelector("#su")).click();}@BeforeAllstatic void SetUp() {System.out.println("这是我们 BeforeAll 里边的语句");}@AfterAllstatic void TearDown() {System.out.println("这是我们 AfterAll 里边的语句");}}
@BeforeEach:当前的方法需要在每个用例执行之前都执行一次
@AfterEach:当前的方法需要在每个用例执行之后都执行一次
import org.junit.jupiter.api.*;import org.openqa.selenium.By;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;public class JUnitDemo1 {@Testvoid Test01() {System.out.println("这是 JunitDemo1 里的 Test01");}@Testvoid Test02() {System.out.println("这是 JunitDemo1 里的 Test02");}@Disabledvoid Test03() {WebDriver webDriver = new ChromeDriver();webDriver.get("https://www.baidu.com");webDriver.findElement(By.cssSelector("#kw")).sendKeys("521");webDriver.findElement(By.cssSelector("#su")).click();}@BeforeAllstatic void SetUp() {System.out.println("这是我们 BeforeAll 里边的语句");}@AfterAllstatic void TearDown() {System.out.println("这是我们 AfterAll 里边的语句");}@BeforeEachvoid BeforeEachTest() {System.out.println("这是 BeforeEach 里边的语句");}@AfterEachvoid AfterEachTest() {System.out.println("这是 AfterEach 里边的语句");}}
参数化就是尽可能的通过一个用例,多组参数来模拟用户的行为;在使用参数化注解之前需要先用 @parameterizedTest 声明该方法为参数化方法,然后再通过注解提供数据来源
使用 @parameterizedTest 需要在 pom.xml 文件中进行配置(记得配置完要进行刷新):
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-params</artifactId><version>5.9.2</version></dependency>
单参数:@ValueSource(数据类型方法={参数1,参数2…})
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.ValueSource;public class JUnitDemo2 {//单参数:@ValueSource(数据类型方法={参数1,参数2…})@ParameterizedTest@ValueSource(ints = {1, 2, 3})void Test01(int num) {System.out.println(num);}@ParameterizedTest@ValueSource(strings = {"1", "2", "3"})void Test02(String number) {System.out.println(number);}}假设上述参数有很多,在注解处手动编写数据源就有些不方便,我们这时就可以借助第三方csv文件来读取数据源
CSV 获取参数:@CsvFileSource(resources = "____.csv")
这个时候我们需要在 main 文件下 resources 中创建一个 test.csv 文件(.csv 和 参数必须相同):
张三1,李四1,王五1张三2,李四2,王五2代码编写:
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.CsvFileSource;//参数化public class JUnitDemo2 {@ParameterizedTest@CsvFileSource(resources = "test.csv")void Test03(String name) {System.out.println(name);}}此时我们就把所有的内容都打印出来了;
@CsvFileSource、@ValueSource 只能传递同种类型的参数,那么我们想要传多种参数,那么可以用方法获取参数
方法获取参数:@MethodSource("Generator")
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.Arguments;import org.junit.jupiter.params.provider.MethodSource;import java.util.stream.Stream;public class JUnitDemo2 {public static Stream<Arguments> Generator() {return Stream.of(Arguments.arguments(1, "张三"),Arguments.arguments(2, "李四"),Arguments.arguments(3, "王五"));}@ParameterizedTest@MethodSource("Generator")void Test04(int num, String name) {System.out.println(num + ":" + name);}}多参数:@CsvSource({“数据组合1”,“数据组合2”…}),每个双引号是一组参数(测试用例)
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.*;import java.util.stream.Stream;public class JUnitDemo2 {//多参数:@CsvSource({“数据组合1”,“数据组合2”…})@ParameterizedTest@CsvSource({"1, 张三", "2, 李四", "3, 王五"})void manyTest(int num, String name) {System.out.println("num:" + num + ", name:" + name);}}我们看以下代码:
此时我们看到打印的依次是 A、B、C
此时我们看到打印的还是 A、B、C
所以 测试用例的执行顺序并不会按照我们编写代码的顺序来执行
如果在实际测试中,我们需要完成连贯的多个步骤的测试,是需要规定测试用例执行的顺序的,可以通过 @order 注解来实现排序
顺序执行:@TestMethodOrder(MethodOrderer.OrderAnnotation.class),然后再每次执行的时候添加 @order(执行顺序)
import org.junit.jupiter.api.MethodOrderer;import org.junit.jupiter.api.Order;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.TestMethodOrder;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class JUnitDemo1 {@Order(2)@Testvoid test03() {System.out.println("C");}@Order(3)@Testvoid test01() {System.out.println("A");}@Order(1)@Testvoid test02() {System.out.println("B");}}这个时候我们通过 @Order 来设置执行顺序,按照这个思路,我们得到的结果为 B -> C -> A
随机执行:@TestMethodOrder(MethodOrderer.class),也就是我们看到的一开始的执行顺序
import org.junit.jupiter.api.MethodOrderer;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.TestMethodOrder;@TestMethodOrder(MethodOrderer.class)public class JUnitDemo2 {@Testvoid test03() {System.out.println("C");}@Testvoid test01() {System.out.println("A");}@Testvoid test02() {System.out.println("B");}}因此,如果不写 @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 则是随即执行
检查测试方法的期望结果值和真实返回值,通过 Assertions 类实现
在这里我们可以看到 预期和结果 是否相同
当我们一个类中有多个测试用例时,我们不可能挨个去运行,那样将会很耗费时间,这时我们就需要 测试套件 来指定类或者指定包名来运行类下或者包下的所有测试用例。
如果要使用测试套件,首先我们需要先创建一个类,通过 @Suite 注解标识该类为测试套件类(而不是测试类)
使用测试套件,需要引入依赖:
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite --><dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite</artifactId><version>1.9.1</version></dependency>还需要引入依赖(使用 suite 需要引入 引擎engine 依赖):
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.9.1</version></dependency>使用 @SelectClasses({指定类, 指定类, 指定类})
JUnitDemo1:
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class JUnitDemo1 {@Order(2)@Testvoid test03() {System.out.println("C");}@Order(3)@Testvoid test01() {System.out.println("A");}@Order(1)@Testvoid test02() {System.out.println("B");}}JUnitDemo4:
public class JUnitDemo4 {public static Stream<Arguments> Generator() {return Stream.of(Arguments.arguments(1, "张三"),Arguments.arguments(2, "李四"),Arguments.arguments(3, "王五"));}@ParameterizedTest@MethodSource("Generator")void Test04(int num, String name) {System.out.println(num + ":" + name);}}RunSuite:
@Suite@SelectClasses({JUnitDemo4.class, JUnitDemo1.class})public class RunSuite {}此时就是先执行 JUnitDemo4,再执行 JUnitDemo1
@SelectPackages(value = {"包1", "包2","..."})
JUnit1 包下 Test1:
public class Test1 {@Testvoid test1() {System.out.println("JUnit1 package Test1 test1");}}JUnit2 包下 Test2:
public class Test2 {@Testvoid test2() {System.out.println("JUnit2 package Test2 test2");}}RunSuite2:
@Suite@SelectPackages(value = {"JUnit1", "JUnit2"})public class RunSuite2 {}此时先执行 JUnit1 包下 Test1 ,再执行 JUnit2 包下 Test2