单元测试工具-Junit

一. 认识Junit

JUnit 是一个用于编写和运行 Java 程序的开源框架,它支持自动化单元测试,可以帮助开发人员测试代码的正确性和健壮性。

JUnit 提供了一组注解、断言和测试运行器,可以方便地编写和运行单元测试。

在进行自动化测试时,比如使用 Selenium 自动化测试框架编写一些测试用例脚本,那么可能就需要执行很多测试用例的,此时我们就需要一个工具来管理这些测试用例,而 Junit 就是一个很好的管理工具。

二. Junit中常用的注解

比较常用的是 JUnit5 版本,要使用需要引入以下依赖:

<!-- 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>

其中的常用的注解有以下几个:

注解说明
@Test标识单元测试方法。
@BeforeEach在每个测试方法之前执行。
@AfterEach在每个测试方法之后执行。
@BeforeAll在所有测试方法之前执行,只会执行一次。
@AfterAll在所有测试方法之后执行,只会执行一次。
@Disabled标识禁用的测试类或测试方法。

1. @Test

@Test注解标识在方法上面,表示当前的方法是一个单元测试方法,通过 @Test 标识的单元测试方法,不需要在main中,也可以直接执行。

演示代码:

import org.junit.jupiter.api.Test;

public class JunitTest {
    @Test
    void Test01() {
        System.out.println("这是JunitTest里面的Test01");
    }

    @Test
    void Test02() {
        System.out.println("这是JunitTest里面的Test02");
    }
}

需要注意的是此时编译器中的几个运行按钮:

img

执行类中全部单元测试方法结果:

img

其中“ √ ”表示测试通过,如果是出现“ ! / X ”就表示测试错误。

2. @Disabled

当使用@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 JunitTest {
    @Test
    void Test01() {
        System.out.println("这是JunitTest里面的Test01");
    }

    @Test
    void Test02() {
        System.out.println("这是JunitTest里面的Test02");
    }
    @Disabled
    void Test03() {
        WebDriver webDriver = new ChromeDriver();
        webDriver.get("https://www.baidu.com");
        webDriver.findElement(By.cssSelector("#s-top-left > a:nth-child(6)"));
    }
}

执行所有测试用例结果:

Test03未被执行。

img

3. @BeforeAll & @AfterAll

@BeforeAll:当前的方法需要在当前类下所有用例执行之前执行一次(只会执行一次),且被该注解修饰的方法必须为静态方法,通常可以将类似创建驱动,打开网页的操作放在此注解下。

@AfterAll:当前的方法需要在当前类下所有用例执行之后执行一次(只会执行一次),且被该注解修饰的方法必须为静态方法,通常可以将类似关闭浏览器的操作放在此注解下。

演示代码:

@BeforeAll
static void SetUp() {
    System.out.println("这是BeforeAll里面的语句");
}
@AfterAll
static void TearDown() {
    System.out.println("这是AfterAll的语句");
}

执行结果:

img

4. @BeforeEach & @AfterEach

@BeforeEach:当前的方法需要在每个用例执行之前都执行一次。

@AfterEach:当前的方法需要在每个用例执行之后都执行一次。

演示代码:

@BeforeEach
void BeforeEachTest() {
    System.out.println("这是BeforeEach里面的语句");
}
@AfterEach
void AfterEachTest() {
    System.out.println("这是AfterEach里面的语句");
}

执行结果:

img

三. @ParameterizedTest参数化

参数化就是尽可能的通过一个用例,多组参数来模拟用户的行为,在使用参数化注解之前需要先用@parameterizedTest声明该方法为参数化方法,然后再通过注解提供参数的来源,要注意 @ParameterizedTest 和 @Test 不能同时使用。

使用 @parameterizedTest 需要引入以下依赖:

<!-- 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>

1. 单参数

单参数,指的是传递的参数类型是单种形式的。

主要使用注解@ValueSource(数据类型方法={参数1,参数2…}),数据类型方法就是在类型名称后面加上s

演示代码:

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void Test04(int num) {
    System.out.println(num);
}

将类中的 Before 和 After 标识的方法先都注释掉,只执行 Test04,方便观察结果。

img

传入 String 类型也是可以的。

@ParameterizedTest
@ValueSource(strings = {"1", "2", "3"})
void Test05(String number) {
    System.out.println(number);
}

执行结果:

img

2. 多参数

多参数,指的是传递的参数可以是多种类型的。

2.1. CSV 获取参数

首先可以使用@CsvSource({“数据组合1”,“数据组合2”…})注解**,**每个双引号是一组参数,可以传入多种类型的参数。

演示代码:

@ParameterizedTest
@CsvSource({"1, 张三", "2, 李四", "3, 王五"})
void Test06(int num, String name) {
    System.out.println("num:" + num + ", name:" + name);
}

执行结果:

img

假设上述参数有很多,在注解处手动编写数据源就有些不方便,我们这时就可以借助第三方.csv文件来读取数据源。

CSV 获取参数使用@CsvFileSource(resources = "____.csv")注解

此时我们可以在resources中创建一个test01.csv文件(.csv 和 参数必须相同),添加如下内容:

1, 张三
2, 李四
3, 王五
4, 赵六

演示代码:

@ParameterizedTest
@CsvFileSource(resources = "test01.csv")
void Test07(int num, String name) {
    System.out.println("num:" + num + ", name:" + name);
}

执行结果:

img

2.2. 方法获取参数

方法获取参数,通过@MethodSource("方法名")注解来实现,需要构造与方法名对应的方法 ,在对应的方法中,返回相应类型的参数。

演示代码:

public static Stream<Arguments> Generator() {
    return Stream.of(Arguments.arguments(1, "张三"),
            Arguments.arguments(2, "李四"),
            Arguments.arguments(3, "王五")
    );
}
@ParameterizedTest
@MethodSource("Generator")
void Test08(int num, String name) {
    System.out.println(num + ":" + name);
}

执行结果:

img

四. @Order控制测试用例的执行顺序

要注意,Junit 每个类中的单元测试的方法的执行顺序并不是从上往下的,它默认的执行顺序是不确定的,是根据 Junit 的运行机制来的,这个就不做介绍了。

如果在实际测试中,我们需要完成连贯的多个步骤的测试,是需要规定测试用例执行的顺序的,就可以通过给测试方法添加@Order注解并指定一个整数值,可以提供一致和可预测的执行顺序。

但使用@Order注解前,需要先使用@TestMethodOrder类注解并设置参数为MethodOrderer.OrderAnnotation.class说明当前类下所有的用例需要按照@Order注解来控制测试方法的执行顺序。

当设置@TestMethodOrder类注解的参数为MethodOrderer.Random.class就恢复默认的执行顺序了,@Order注解就不生效了(跟不写效果一样@TestMethodOrder)。

演示代码:

import org.junit.jupiter.api.*;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
//@TestMethodOrder(MethodOrderer.Random.class)
public class JunitTest01 {
    @Order(2)
    @Test
    void C() {
        System.out.println("C测试用例");
    }

    @Order(3)
    @Test
    void B() {
        System.out.println("B测试用例");
    }

    @Order(1)
    @Test
    void D() {
        System.out.println("D测试用例");
    }

    @Order(4)
    @Test
    void A() {
        System.out.println("A测试用例");
    }
}

执行结果:

img

五. 断言

JUnit 5 中,断言方法位于 org.junit.jupiter.api.Assertions 类中。

通过Assertions类实现的断言可以在测试中验证预期结果是否与实际结果相符,如果断言失败,则测试将被标记为失败(!,会报错),并提供有关错误的详细信息,这有助于快速定位和修复问题。

断言方法说明
assertEquals(expected, actual)验证两个对象是否相等。可以用于比较基本数据类型、对象和数组。
assertTrue(condition)验证条件是否为真。如果条件为真,则测试通过;否则,测试失败。
assertFalse(condition)验证条件是否为假。如果条件为假,则测试通过;否则,测试失败。
assertNull(actual)验证对象是否为 null。如果对象为 null,则测试通过;否则,测试失败。
assertNotNull(actual)验证对象是否不为 null。如果对象不为 null,则测试通过;否则,测试失败。
assertSame(expected, actual)验证两个对象引用是否相同。即判断两个对象是否指向同一个内存地址。
assertNotSame(unexpected, actual)验证两个对象引用是否不相同。
assertArrayEquals(expectedArray, actualArray)验证两个数组是否相等。用于比较数组的元素是否相等。
assertThrows(expectedType, executable)验证代码块是否抛出了特定类型的异常。
assertTimeout(duration, executable)验证代码块是否在指定的时间内执行完成,超过指定时间则测试失败。

演示代码:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class JunitTest02 {
    @Test
    public void test() {
        String str = "helloworld";
        assertEquals("helloworld", str);
        assertTrue(1 == 1);
        assertFalse(2 < 1);
        assertNotNull(str);
        assertNull(str);
    }
}

执行结果:

报错了,单元测试执行预期结果与实际结果不相符,str并不为null

img

六. 测试套件

当有多个测试类时,挨个去手动运行,会很繁琐耗时,我们可以使用测试套件来指定多个类或者指定多个包来运行类下或者包下的所有测试用例。

要使用测试套件,首先我们需要先创建一个类,通过@Suite注解标识该类为测试套件类。

使用 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>

使用测试套件,也需要引入依赖项:

<!-- 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>
 

1. 通过Class运行测试用例

使用@SelectClasses({指定类, 指定类, 指定类})类注解,通过参数中指定对应的类,然后对类中的测试用例进行执行,按照从前往后的顺序执行。

演示代码:

import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({JunitTest02.class, JunitTest.class, JunitTest01.class})
public class RunSuite {
}

执行结果:

img

2. 通过包运行测试用例

使用@SelectPackages(value = {"包1", "包2","..."})类注解,通过参数中指定对应的包,然后对包中的测试用例进行执行,但是扫描的包不可以和当前的类在同一个包下面。

演示代码:

Test01 包下 test001。

package Test01;

import org.junit.jupiter.api.Test;

public class Test1 {
    @Test
    void test001() {
        System.out.println("Test1 pacage test001");
    }
}

Test02 包下 test002。

package Test02;

import org.junit.jupiter.api.Test;

public class Test2 {
    @Test
    void test002() {
        System.out.println("Test2 pacage test002");
    }
}

RunSuite2。

import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectPackages(value = {"Test01", "Test02"})
public class RunSuite2 {
}

执行结果:

img