JUnit 5和Mockito进行单元测试

1. JUnit 5 基础

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

1.1 基本注解

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

1.2 断言Assertions类)

Assertions 类是JUnit 5中用于断言的核心类,提供了一系列静态方法来验证测试条件是否满足预期。

  1. assertEquals(expected, actual):验证两个对象是否相等。如果不等,测试失败。
  2. assertTrue(boolean condition):验证条件是否为真。如果为假,测试失败。
  3. assertFalse(boolean condition):验证条件是否为假。如果为真,测试失败。
  4. assertNull(Object object):验证对象是否为null。如果不为null,测试失败。
  5. assertNotNull(Object object):验证对象是否不为null。如果为null,测试失败。
  6. assertThrows(Class<T> expectedType, Executable executable):验证执行executable是否抛出了expectedType类型的异常。如果没有抛出或抛出其他类型的异常,测试失败。
  7. assertAll(Executable... executables):同时执行多个断言,如果所有断言都成功,则测试通过;如果任何一个断言失败,所有失败的断言都会被报告。
  8. assertSame(expected, actual):验证两个对象是否为同一个对象(使用==比较)。如果不是,测试失败。
  9. assertNotSame(unexpected, actual):验证两个对象是否不是同一个对象(使用==比较)。如果是,测试失败。
  10. assertTimeout(Duration timeout, Executable executable):验证执行executable是否在给定的时间内完成。如果执行超时,测试失败。

1.3  Assumptions 类

Assumptions 类提供了基于某些条件判断是否执行测试的能力。如果假设失败(即条件不满足),当前测试会被跳过,而不是失败。以下是一些常用的Assumptions方法:

  1. assumeTrue(boolean assumption):如果假设为真,则继续执行测试;如果假设为假,测试被跳过。
  2. assumeFalse(boolean assumption):如果假设为假,则继续执行测试;如果假设为真,测试被跳过。
  3. assumingThat(boolean assumption, Executable executable):如果假设为真,则执行给定的executable(可以是一个测试方法);无论假设结果如何,测试都会继续执行,但executable只在假设为真时执行。

2.Mockito 基础

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

  • 基本注解

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

  1. mock(Class<T> classToMock):创建一个类的模拟对象。这是创建模拟对象的基础。

  2. when(T methodCall):当你想模拟一个方法调用的返回值时使用。与thenReturn一起使用,可以指定一个方法调用应该返回什么值。

  3. thenReturn(T value):与when方法一起使用,用于指定方法调用的返回值。

  4. doReturn(Object toBeReturned):一个替代thenReturn的方法,用在当你需要模拟void方法或在spy对象上进行模拟时。

  5. verify(T mock):用于验证某个模拟对象的某个方法是否被调用,以及调用的次数。

  6. any():在设定模拟行为(如when)或验证(如verify)时,用于表示任何类型和值的参数。

  7. eq(T value):用于指定方法调用时期望的具体参数值。

  8. doNothing():用于模拟void方法时,指定该方法不执行任何操作。

  9. doThrow(Throwable... toBeThrown):用于模拟方法调用时抛出异常。

  10. spy(T object):创建一个真实对象的“间谍”或“spy”。这允许你在真实对象上“监视”方法调用,同时还能够覆盖某些方法的行为。

  11. ArgumentCaptor<T>:用于捕获方法调用时传递的参数,以便后续进行断言。

  12. times(int wantedNumberOfInvocations):与verify方法一起使用,用于指定某个方法被调用的具体次数。

  13. never():与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 {@MockPaymentProcessor processor;@Testpublic 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方法是否被正确调用。

3.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 {@Testvoid isEmpty() {assertEquals(0, new ArrayList<>().size());}
​@Nestedclass AfterAddingAnElement {@Testvoid isNotEmpty() {List<Object> list = new ArrayList<>();list.add(new Object());
​assertEquals(1, list.size());}}
}
 超时测试

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() {// 仅当系统用户名匹配时运行
}

 

4.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");

模拟静态方法(需要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);
}

5. @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.mzph.cn/news/705889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【深入理解设计模式】装饰者设计模式

装饰者设计模式 装饰者设计模式&#xff08;Decorator Design Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向现有对象添加新功能而不改变其结构。这种模式通常用于需要动态地为对象添加功能或行为的情况&#xff0c;而且这些功能可以独立于对象本身来进行扩展…

【计算机网络】1 因特网概述

一.网络、互联网和因特网 1.网络&#xff08;network&#xff09;&#xff0c;由若干结点&#xff08;node&#xff09;和连接这些结点的链路&#xff08;link&#xff09;组成。 2.多个网络还可以通过路由器互联起来&#xff0c;这样就构成了一个覆盖范围更大的网络&#xf…

【卡码网】完全背包问题 52. 携带研究材料——代码随想录算法训练营Day44

题目链接&#xff1a;题目页面 题目描述 题目描述 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会&#xff0c;以展示自己的最新研究成果。他需要带一些研究材料&#xff0c;但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等&…

终于明白kmp算法

在刷代码随想录的时候&#xff0c;遇到了leetcode这道经典题目 28. 实现 strStr() 力扣题目链接(opens new window) 实现 strStr() 函数。 给定一个 haystack 字符串和一个 needle 字符串&#xff0c;在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如…

Selenium IDE插件录制网页,解放双手

1、 国内下载地址 https://www.crx4chrome.com/crx/77585/ &#xff0c;这个网络正常基本可以下载&#xff0c;目前最新版本是3.17.2。 点击Crx4Chrome下载。下载后的文件名称是&#xff1a;mooikfkahbdckldjjndioackbalphokd-3.17.2-Crx4Chrome.com.crx。 2、 安装 直接打开…

webpack 如何实现模块懒加载

首先在打包的时候使用 splitwebpackplugin 进行分割 在实际引用中&#xff0c;webpack 实现模块的懒加载是通过动态导入&#xff08;dynamic import &#xff09;来实现的。动态导入是 es6 的一项功能&#xff0c;允许在运行时异步加载模块&#xff0c;从而实现按需加载。 使…

CMS垃圾回收器

CMS垃圾回收 CMS GC的官方名称为“Mostly Concurrenct Mark and Sweep Garbage Collector”&#xff08;最大-并发-标记-清除-垃圾收集器&#xff09;。 作用范围&#xff1a; 老年代 算法&#xff1a; 并发标记清除算法。 启用参数&#xff1a;-XX:UseConMarkSweepGC 默认回收…

探索创造无限可能——Autodesk AutoCAD 2022(CAD 2022)系统要求

随着科技的不断进步和发展&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;已经成为现代设计行业中不可或缺的一部分。在众多CAD软件中&#xff0c;Autodesk AutoCAD 2022&#xff08;CAD 2022&#xff09;无疑是最受欢迎和广泛应用的一款软件。作为一款全球领先的CAD软…

打造多平台游戏,Pygame让梦想照进现实

Pygame是一个流行的Python库&#xff0c;用于开发2D游戏。尽管它主要用于桌面游戏&#xff0c;但通过一些额外的工具和技巧&#xff0c;你也可以使用Pygame来打造多平台游戏&#xff0c;比如支持Windows、Linux、macOS、Android和iOS等平台。 下面是一个简单的Pygame游戏示例&…

sql 行列互换

在SQL中进行行列互换可以使用PIVOT函数。下面是一个示例查询及其对应的结果&#xff1a; 创建测试表格 CREATE TABLE test_table (id INT PRIMARY KEY,name VARCHAR(50),category VARCHAR(50) );向测试表格插入数据 INSERT INTO test_table VALUES (1, A, Category A); INSE…

某电力铁塔安全监测预警系统案例分享

项目概述 电力铁塔是承载电力供应的重要设施&#xff0c;它的安全性需要得到可靠的保障。但是铁塔一般安装在户外&#xff0c;分布广泛&#xff0c;且有很多安装在偏远地区&#xff0c;容易受到自然、人力的影响和破环。因此需要使用辅助的方法实时监控铁塔的安全状态&#xff…

ABC342 题解

ABC342 题解 A Description 给定一个串 s s s&#xff0c;求与 s s s 中其它字符不同的唯一字符的编号。 Solution 直接把 s s s 存到 t t t 里排序&#xff08;确实&#xff0c;sort(t.begin(), t.end()) 就可以&#xff09;&#xff0c;首尾特判再到 s s s 里查一下…

计算机设计大赛 深度学习大数据物流平台 python

文章目录 0 前言1 课题背景2 物流大数据平台的架构与设计3 智能车货匹配推荐算法的实现**1\. 问题陈述****2\. 算法模型**3\. 模型构建总览 **4 司机标签体系的搭建及算法****1\. 冷启动**2\. LSTM多标签模型算法 5 货运价格预测6 总结7 部分核心代码8 最后 0 前言 &#x1f5…

office word保存pdf高质量设置

1 采用第三方pdf功能生成 分辨率越大质量越好

MySQL集群 双主架构(配置命令)

CSDN 成就一亿技术人&#xff01; 今天刚开学第一天给大家分享一期&#xff1a;MySQL集群双主的配置需求和命令 CSDN 成就一亿技术人&#xff01; 神秘泣男子主页&#xff1a;作者首页 <———— MySQL专栏 &#xff1a;MySQL数据库专栏<———— MySQL双主是一…

学习笔记丨Shell

Usage of shell script References: Learn Shell, Shell 教程 | 菜鸟教程 The first line of shell script file begins with #!, followed by the full path where the shell interpreter is located. For example, #!/bin/bashTo find out the currently active shell and i…

python语言常见面试题:描述Python中的字典(Dictionary)和集合(Set)之间的区别。

Python中的字典&#xff08;Dictionary&#xff09;和集合&#xff08;Set&#xff09;是两种非常有用的数据结构&#xff0c;它们之间有一些明显的区别。 字典&#xff08;Dictionary&#xff09; 字典是一种无序的键值对集合。在字典中&#xff0c;每个键&#xff08;key&a…

Oracle 基础表管理(Heap-Organized Table Management)

表是数据库中负责数据存储的对象&#xff0c;在RDBMS中&#xff0c;数据以行、列的形式存储在表中。Oracle中表有很多种类型&#xff0c;最基础且应用最常用的类型就是堆表&#xff08;Heap-Organized Table&#xff09;&#xff0c;本文列举了Oracle堆表的常用管理操作。 一、…

Cpython和Jpython区别

Cpython和Jpython是Python语言的两种不同实现方式&#xff0c;它们之间存在一些关键的区别。 实现语言&#xff1a;Cpython是用C语言实现的&#xff0c;而Jpython则是用Java语言实现的。这意味着Cpython的源代码是用C语言编写的&#xff0c;而Jpython的源代码是用Java语言编写的…

pytorch --反向传播和优化器

1. 反向传播 计算当前张量的梯度 Tensor.backward(gradientNone, retain_graphNone, create_graphFalse, inputsNone)计算当前张量相对于图中叶子节点的梯度。 使用反向传播&#xff0c;每个节点的梯度&#xff0c;根据梯度进行参数优化&#xff0c;最后使得损失最小化 代码…