JUnit 5和Mockito进行单元测试

news/2025/2/21 2:01:08

JUnit 5 基础

JUnit 5是最新的JUnit版本,它引入了许多新特性,包括更灵活的测试实例生命周期、参数化测试、更丰富的断言和假设等。

  • 基本注解

    • @Test:标记一个方法为测试方法。
    • @BeforeEach:在每个测试方法之前执行。
    • @AfterEach:在每个测试方法之后执行。
    • @BeforeAll:在所有测试方法之前执行一次(必须是静态方法)。
    • @AfterAll:在所有测试方法之后执行一次(必须是静态方法)。
    • @DisplayName:定义测试类或测试方法的自定义名称。
    • @Nested:允许将测试类分组到更小的测试类中。
    • @ParameterizedTest:进行参数化测试。
  • 断言Assertions类):

    • assertEquals(expected, actual):验证两个对象或值是否相等。
    • assertTrue(condition):验证条件是否为真。
    • assertFalse(condition):验证条件是否为假。
    • assertThrows(exception, executable):验证执行特定代码块时是否抛出预期异常。
    • assertAll(executables):组合多个断言,确保所有断言都必须通过。

Mockito 基础

Mockito是一个流行的Java mocking框架,用于在隔离环境中测试代码,通过模拟依赖来确保测试的独立性。

  • 基本注解

    • @Mock:创建一个模拟对象。
    • @InjectMocks:创建一个实例,其字段或构造器依赖将被@Mock注解的模拟对象自动注入。
    • @Spy:可以创建一个真实的对象,并在需要时对它的某些方法进行模拟。
    • @Captor:用于捕获方法调用的参数。
  • 常用方法

    • when(mock.method()).thenReturn(value):指定当调用模拟对象的某个方法时应返回的值。
    • verify(mock).method():验证模拟对象的某个方法是否被调用。
    • any(), eq(), anyInt() 等:在when()verify()方法中使用的参数匹配器。

示例

假设我们有一个PaymentService类,它依赖于PaymentProcessor接口:

public class PaymentService {
    private PaymentProcessor processor;

    public PaymentService(PaymentProcessor processor) {
        this.processor = processor;
    }

    public boolean process(double amount) {
        return processor.processPayment(amount);
    }
}

下面是如何使用JUnit 5和Mockito来测试PaymentService类:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
public class PaymentServiceTest {

    @Mock
    PaymentProcessor processor;

    @Test
    public void testProcessPayment() {
        // 设置
        PaymentService service = new PaymentService(processor);
        double amount = 100.0;
        when(processor.processPayment(amount)).thenReturn(true);

        // 执行
        boolean result = service.process(amount);

        // 验证
        assertTrue(result);
        verify(processor).processPayment(amount);
    }
}

在这个例子中,我们使用@Mock来创建PaymentProcessor的模拟对象,并使用when(...).thenReturn(...)来定义当调用processPayment方法时应返回的值。然后,我们执行process方法,并使用assertTrue来验证结果是否符合预期。最后,我们使用verify来确认processPayment方法是否被正确调用。

JUnit 5 进阶用法

参数化测试(Parameterized Tests)

参数化测试允许你使用不同的参数多次运行同一个测试。这对于需要验证多种输入条件的方法特别有用。

@ParameterizedTest
@ValueSource(strings = {"Hello", "JUnit"})
void withValueSource(String word) {
    assertNotNull(word);
}
动态测试(Dynamic Tests)

JUnit 5允许你动态生成测试,这些测试可以在运行时根据代码逻辑来决定。

@TestFactory
Collection<DynamicTest> dynamicTests() {
    return Arrays.asList(
        dynamicTest("Add test", () -> assertEquals(2, Math.addExact(1, 1))),
        dynamicTest("Multiply Test", () -> assertEquals(4, Math.multiplyExact(2, 2)))
    );
}
嵌套测试(Nested Tests)

使用@Nested注解,你可以将相关的测试组织在一起作为一个组在外层测试类中运行。

@Nested
class WhenNew {
    @Test
    void isEmpty() {
        assertEquals(0, new ArrayList<>().size());
    }
​
    @Nested
    class AfterAddingAnElement {
        @Test
        void isNotEmpty() {
            List<Object> list = new ArrayList<>();
            list.add(new Object());
​
            assertEquals(1, list.size());
        }
    }
}

Mockito 进阶用法

使用@Spy进行部分模拟

有时你可能需要模拟类的某些方法,而保持其他方法的实际行为。@Spy注解允许你这样做。

@Spy
List<String> spyList = new ArrayList<>();
​
@Test
void testSpy() {
    spyList.add("one");
    spyList.add("two");
​
    verify(spyList).add("one");
    verify(spyList).add("two");
​
    assertEquals(2, spyList.size()); // 实际调用方法
​
    // 修改方法行为
    doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size()); // 方法行为被改变
}
参数捕获(Argument Captors)

有时在验证方法调用时,你可能对方法调用的具体参数值感兴趣。@Captor注解和ArgumentCaptor类允许你捕获和检查这些值。

@Mock
List<String> mockList;
​
@Captor
ArgumentCaptor<String> argCaptor;
​
@Test
void argumentCaptorTest() {
    mockList.add("one");
    verify(mockList).add(argCaptor.capture());
​
    assertEquals("one", argCaptor.getValue());
}
连续调用的不同返回值

有时候,你可能需要一个方法在连续调用时返回不同的值。Mockito允许你通过thenReturn()方法链来实现这一点。

when(mockList.size()).thenReturn(0).thenReturn(1);
assertEquals(0, mockList.size());
assertEquals(1, mockList.size());
验证调用次数

验证一个方法被调用了特定次数。

mockList.add("once");
mockList.add("twice");
mockList.add("twice");
​
verify(mockList).add("once");
verify(mockList, times(2)).add("twice");
verify(mockList, never()).add("never happened");

JUnit 5

超时测试

JUnit 5允许你为测试设置超时时间,确保测试在给定时间内完成。如果超出指定时间,测试将失败。

@Test
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void timeoutTest() {
    // 模拟一个耗时的操作
    // 如果操作超过500毫秒,则测试失败
}
重复测试

如果你想对一个测试方法进行多次执行以确保其稳定性或寻找潜在的偶发问题,可以使用@RepeatedTest注解。

@RepeatedTest(5)
void repeatTest() {
    // 这个测试会运行5次
}
条件执行

JUnit 5提供了多种条件执行测试的方法,这些方法可以基于不同的条件来决定是否执行某个测试,例如操作系统类型、环境变量或Java版本。

@Test
@EnabledOnOs(OS.WINDOWS)
void onlyOnWindows() {
    // 仅在Windows操作系统上运行
}
​
@Test
@EnabledIfSystemProperty(named = "user.name", matches = "yourUserName")
void onlyForSpecificUser() {
    // 仅当系统用户名匹配时运行
}

Mockito

模拟静态方法(需要Mockito 3.4.0及以上版本)

从Mockito 3.4.0开始,你可以使用mockStatic来模拟静态方法。这是通过try-with-resources语句来实现的,以确保静态mock在使用后被正确关闭。

try (MockedStatic<UtilityClass> mockedStatic = mockStatic(UtilityClass.class)) {
    mockedStatic.when(UtilityClass::someStaticMethod).thenReturn("mocked response");
    assertEquals("mocked response", UtilityClass.someStaticMethod());
    // 静态方法被模拟期间的行为
}
// 在这个块之外,静态方法恢复原有行为
模拟final方法和类

Mockito 2.x开始支持模拟final方法和类。为了启用这个功能,你需要在src/test/resources/mockito-extensions目录下创建一个名为org.mockito.plugins.MockMaker的文件,并在文件中添加一行内容:

mock-maker-inline

这样配置后,Mockito就可以模拟final类和方法了。

使用BDDMockito进行行为驱动开发

BDDMockito提供了一种基于行为驱动开发(BDD)的语法来编写Mockito测试,使得测试更加可读。

@Test
void bddStyleTest() {
    // 给定
    BDDMockito.given(mockList.size()).willReturn(2);
​
    // 当
    int size = mockList.size();
​
    // 那么
    BDDMockito.then(mockList).should().size();
    assertEquals(2, size);
}

@Mock 、@InjectMocks的原理

@Mock

  • 原理@Mock注解告诉Mockito框架为标注的字段生成一个模拟对象。这个模拟对象是动态生成的代理对象,它拦截对任何非final方法的调用,并允许测试者通过Mockito的API来配置这些调用的行为(例如返回特定的值或抛出异常)。
  • 如何工作:当测试初始化时(例如,通过使用MockitoAnnotations.initMocks(this)方法或JUnit 5的@ExtendWith(MockitoExtension.class)),Mockito会扫描测试类中所有使用@Mock注解的字段,并为它们创建模拟对象。这些模拟对象默认不执行任何实际的代码逻辑,它们的行为完全由测试者通过Mockito的API来控制。

@InjectMocks

  • 原理@InjectMocks注解用于自动将@Mock(或@Spy)注解创建的模拟对象注入到被注解的字段中。Mockito会尝试通过构造器注入、属性注入或setter方法注入的方式,将模拟对象注入到@InjectMocks标注的实例中。
  • 如何工作
    1. 构造器注入:Mockito首先尝试使用包含最多参数的构造器来创建实例。如果构造器的参数能够与已声明的模拟对象匹配,这些模拟对象将被用作构造器参数。
    2. 属性注入:如果构造器注入不适用或不成功,Mockito会尝试直接设置实例中与模拟对象类型相匹配的属性。
    3. Setter注入:最后,如果属性注入不成功,Mockito会尝试通过调用匹配的setter方法来注入模拟对象。

http://www.niftyadmin.cn/n/5389361.html

相关文章

基于事件触发机制的孤岛微电网二次电压与频率协同控制MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 本模型质量非常高&#xff0c;运行效果完美。本模型为4机并联孤岛系统&#xff0c;在下垂控制的基础上加入二次控制&#xff0c;二次电压与频率协同控制策略利用事件触发的方法来减少控制器的更新次数。该方法…

数据结构day4

实现创建单向循环链表、创建结点、判空、输出、头插、按位置插入、尾删、按位置删除 loop_list.c #include "loop_list.h" loop_p create_head() {loop_p L(loop_p)malloc(sizeof(loop_list));if(LNULL){printf("空间申请失败\n");return NULL;}L->le…

STM32F4XX - uart设置

初始化一个波特率为115200的串口。下面函数参数为115200. 代码如下&#xff1a; void uart1_init(u32 bound) {GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIO…

【洛谷题解】P1008 [NOIP1998 普及组] 三连击

题目链接&#xff1a;[NOIP1998 普及组] 三连击 - 洛谷 题目难度&#xff1a;普及- 涉及知识点&#xff1a;构成比例 题意&#xff1a; 输出样例&#xff1a;192 384 576 219 438 657 273 546 819 327 654 981 分析…

应用回归分析:泊松回归

泊松回归是一种广泛用于计数数据的回归分析方法。它适用于响应变量是非负整数的情况&#xff0c;特别是当这些计数呈现出明显的离散分布时。泊松回归通过泊松分布的概率分布函数来建模计数数据&#xff0c;使其成为处理计数数据的自然选择。本文将介绍泊松回归的基本概念、应用…

【Java程序设计】【C00284】基于Springboot的校园疫情防控管理系统(有论文)

基于Springboot的校园疫情防控管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的校园疫情防控系统 本系统分为系统功能模块、管理员功能模块以及学生功能模块。 系统功能模块&#xff1a;在系统首页可以查…

Apache Maven简介

Maven 简介 Apache Maven 是一个用于项目构建、依赖管理和项目信息管理的强大工具。它基于项目对象模型(Project Object Model,POM)进行构建,通过描述项目的结构和依赖关系来管理项目的构建过程。 以下是 Apache Maven 的一些关键原理和工作流程: 项目对象模型(POM)…

C# OpenCvSharp 利用白平衡技术进行图像修复

目录 效果 灰度世界(GrayworldWB)-白平衡算法 完美反射(SimpleWB)-白平衡算法 基于学习的(LearningBasedWB)-白平衡算法 代码 下载 C# OpenCvSharp 利用白平衡技术进行图像修复 OpenCV xphoto模块中提供了三种不同的白平衡算法&#xff0c;分别是&#xff1a;灰度世界(G…