2MUCH

JUnit

2023-04-15


学习教程:https://www.w3cschool.cn/junit/

JUnit 是一个 Java 编程语言的单元测试框架。Junit也就是所谓的白盒测试,能快速完成单元测试(Unit Test,又称为模块测试)。

特点

简单的示例

创建一个Demo类

public class Demo {  
  
    public static int add(int a, int b) {  
        return a+b;  
    }  
  
}

创建一个相应的Test类

import org.junit.Test;  
  
import static org.junit.Assert.assertEquals;  
  
public class DemoTest {  
  
    @Test  
    public void testAdd() {  
        assertEquals(5,Demo.add(1,4));  
    }  
  
  
}

创建Test Runner 类

import org.junit.runner.JUnitCore;  
import org.junit.runner.Result;  
import org.junit.runner.notification.Failure;  
  
public class TestRunner {  
    public static void main(String[] args) {  
        Result result = JUnitCore.runClasses(DemoTest.class);  
        for (Failure failure: result.getFailures()) {  
            System.out.println(failure.toString());  
        }  
        System.out.println(result.wasSuccessful());  
    }  
}

运行TestRunner的结果:

// 验证通过
true

// 故意模拟验证失败的情况,执行结果:
testAdd(DemoTest): expected:<3> but was:<5>
false

基本用法

Test类:

常用注解

JUnitCore

在测试运行器中,使用JUnitCore类来执行。对于只有一次的测试运行,可以使用静态方法 runClasses(Class[])

Junit API

JUnit 中的最重要的程序包是 junit.framework 它包含了所有的核心类。

Assert

import org.junit.Assert; 包含检验各种条件是否符合的方法,若不符合则抛错

TestCase

import junit.framework.TestCase; 定义了运行多重测试的固定装置

import org.junit.Test;  
import junit.framework.TestCase;  
  
public class DemoTest extends TestCase{  
    @Test  
    public void testTestCase() {  
        System.out.println("No of Test Case = "+ this.countTestCases());  
        System.out.println("Test Case Name = "+ this.getName());  
        this.setName("newName");  
        System.out.println("Test Case Name = "+ this.getName());  
    }  
}

执行结果:

No of Test Case = 1
Test Case Name = testTestCase
Test Case Name = newName
true

TestResult

import junit.framework.TestResult; 集合了执行测试样例的所有结果

TestSuite

import junit.framework.TestSuite; 表示测试的集合

测试套件

测试套件是一种将多个测试类组合在一起执行的方式,以便对整个应用程序或系统进行更全面的测试。测试套件类似于一个集合,它将多个测试类组合在一起,每个测试类都是一个测试套件的一部分。测试套件会依次运行其中包含的所有测试类,并输出整个测试过程的结果。

如何使用

需要编写一个特殊的测试类来定义测试套件,这个测试类需要使用@RunWith(Suite.class)注解来运行测试套件。在测试类中,你需要使用@Suite.SuiteClasses注解来指定包含在测试套件中的测试类。 例如:

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({
   TestEmployeeDetails.class,
   TestEmployeeSalary.class
})

public class EmployeeTestSuite {
  // 这个类没有测试方法,只是用来指定包含在测试套件中的测试类
}

其中,EmployeeTestSuite是一个定义测试套件的测试类,包含两个测试类TestEmployeeDetailsTestEmployeeSalary。如果你运行EmployeeTestSuite,JUnit将依次运行TestEmployeeDetailsTestEmployeeSalary中的所有测试方法,并输出测试结果。

注意:一定要保证测试套件之间没有循环包含关系,否则会有死循环!

忽略测试

@Ignore : 可以修饰测试类或测试方法,使得此测试类或测试方法不会被执行到 注意:JUnit5 中@Igonre不起作用,一个替换方案是使用@Disabled

时间测试

一般用于测试某方法是否执行时间过长,设定timeout参数用于指定该测试方法的最大执行时间,单位为毫秒。如果测试时执行超过该时长,则会中止测试方法并标记为失败。例子如下:

@Test(timeout = 1000)
public void testTimeout() throws InterruptedException { // 等待 2 秒钟,超时时间为 1 秒钟 
Thread.sleep(2000); 
}

异常测试

设定expected参数,用于验证待测试的方法抛的异常是否符合预期。例子如下:

@Test(expected = ArithmeticException.class) 
public void testPrintMessage() { 
	...
}

参数化测试

使用不同的值多次测试同一个方法,简化测试过程。构造特点:

例子如下:

@RunWith(Parameterized.class)
public class CalculatorTest {
    private int num1;
    private int num2;
    private int expectedResult;
    private Calculator calculator;

    @Parameterized.Parameters
    public static Collection<Object[]> input() {
        return Arrays.asList(new Object[][]{
                {2, 3, 5},
                {5, 7, 12},
                {9, 4, 13},
                {8, 1, 9},
                {6, 2, 8}
        });
    }

    public CalculatorTest(int num1, int num2, int expectedResult) {
        this.num1 = num1;
        this.num2 = num2;
        this.expectedResult = expectedResult;
    }

    @Before
    public void setUp() {
        calculator = new Calculator();
    }

    @Test
    public void testAdd() {
        int result = calculator.add(num1, num2);
        assertEquals(expectedResult, result);
    }
}

实际项目中的应用

比如在一个Springboot+mybatisPlus的项目中,要如何利用JUnit测试在ServiceImpl中实现的各种方法呢?这里我直接给出ChatGPT给我的回答吧:

假设有一个名为UserServiceImpl的实现类,实现了UserService接口,该接口定义了数据库表user的增删改查方法,我们可以为其编写JUnit测试方法。

这里我们为UserServiceImpl中的每一个方法编写了一个对应的测试方法,其中使用了JUnit提供的Assertions断言方法,比如Assertions.assertTrue()Assertions.assertNotNull(),用来判断测试结果是否正确。注意,在测试方法上要加上@Test注解,以标识这是一个JUnit测试方法,同时需要使用@SpringBootTest注解来启动Spring容器,以便能够注入UserService实例。

package com.example.demo.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceImplTest {
    @Autowired
    private UserService userService;

    @Test
    public void testSaveUser() {
        User user = new User();
        user.setName("test");
        user.setAge(18);
        boolean result = userService.save(user);
        Assertions.assertTrue(result);
    }

    @Test
    public void testUpdateUser() {
        User user = userService.getById(1L);
        user.setAge(20);
        boolean result = userService.updateById(user);
        Assertions.assertTrue(result);
    }

    @Test
    public void testDeleteUser() {
        boolean result = userService.removeById(1L);
        Assertions.assertTrue(result);
    }

    @Test
    public void testGetUserById() {
        User user = userService.getById(2L);
        Assertions.assertNotNull(user);
    }

    @Test
    public void testGetUserPage() {
        Page<User> page = new Page<>(1, 10);
        Page<User> userPage = userService.page(page, null);
        Assertions.assertNotNull(userPage);
    }
}

测试如何不影响真实的表数据

当我们使用JUnit测试时,例如插入数据,必然会往表中插入脏数据。那么,如何在不影响真实表数据的情况下,完成测试呢?

测试时如何指定id

当我们使用JUnit测试删除或更新操作时,往往需要指定操作id。这里也给出两种常用做法:

进阶使用