【Java技术专题】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)

简介: 【Java技术专题】「核心技术提升」最流行的Java模拟框架Mockito入门指南(Java单元测试)

前提介绍

官方网站

mockito.org

版本介绍

还在使用 Mockito 1.x?看看 Mockito 2 有哪些新功能!Mockito 3 没有引入任何破坏性的 API 变动,但现在需要 Java 8 而不是 Mockito 2 的 Java 6。 Mockito 4 删除了过时的 API。Mockito 5 将默认 mockmaker 改为 mockito-inline,现在需要 Java 11。一次只支持一个主要版本,而且不会向旧版本回传更改内容。

项目源码

github.com/mockito/moc…

开发指南

添加maven依赖

这将在Maven项目中添加Mockito核心库的依赖关系,并限定其范围为测试(<scope>test</scope>)。这样,您就可以在单元测试中使用Mockito框架来模拟对象和验证行为了。请注意,您需要根据您的实际需求调整版本号。

xml

复制代码

<!-- 添加 Mockito 依赖 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.12.4</version>
        <scope>test</scope>
    </dependency>

获得 Mockito 的推荐方法是使用自己喜欢的构建系统声明对 "mockito-core "库的依赖。使用 Gradle 可以做到这一点:

添加Gradle依赖

0

复制代码

repositories { mavenCentral() }
dependencies { testImplementation "org.mockito:mockito-core:3.+" }}

Maven 用户可以声明对 mockito-core 的依赖。Mockito 会将每次更改作为 -SNAPSHOT 版本发布到公共 Sonatype 资源库。进行手动依赖关系管理的用户可直接从 Maven Central 下载 jar。使用手动依赖关系管理的传统版本可使用 1.* "mockito-all" 发行版。该发行版在 Mockito 2.* 中已停用。

mockito需要junit配合使用

需要将JUnit和Mockito结合在一起使用来进行Java单元测试。在添加Mockito依赖之前,请确保您的项目中已经包含了JUnit的依赖。

xml

复制代码

<dependencies>
    <!-- 添加 JUnit 依赖 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!-- 添加 Mockito 依赖 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>3.12.4</version>
        <scope>test</scope>
    </dependency>
</dependencies>

在上面的配置中,我们添加了JUnit和Mockito的依赖关系,并将其范围都设置为测试(<scope>test</scope>)。这样,就可以使用JUnit来运行测试,并使用Mockito进行对象模拟和行为验证。请确保根据您的实际需求调整JUnit和Mockito的版本号。

导入静态资源

为了使测试类中的代码更简洁和易读,您可以通过静态导入来引入Mockito和JUnit的一些静态资源。这样可以减少在测试类中使用这些资源时的冗余代码

java

复制代码

import static org.mockito.Mockito.*;  
import static org.junit.Assert.*;

mockito的方法

验证互动

下面是对应相关的案例代码:

java

复制代码

@Test  
public void verify_behaviour(){  
    //模拟创建一个List对象
    List mock = mock(List.class);  
   // 或者使用 Mockito 4.10.0+ 时更简单
    List mock = mock();
   // 使用 mock 对象 - 它不会抛出任何 "意外交互 "异常
    mock.add(1);  
    mock.clear();  
    //验证add(1)和clear()行为是否发生
  // 选择性的、明确的、可读性高的验证
    verify(mock).add(1);
    verify(mock).clear();
}

详细分析:使用mock()模拟对象

举一个简单的案例去模型mock数据效果,mock可以模拟各种各样的对象,替代真正的对象做出希望的响应。

java

复制代码

//模拟LinkList的一个对象
LinkedList mockdedList = mock(LinkedList.class);
//此时条用get方法,会返回null,因为还没有对方法调用的返回值做模拟。
System.out.printlin(mockedList.get(99));
模拟方法调用的返回值
mock对象被调用时的返回值

当我们需要模拟获取第一个元素并返回字符串“first”时,可以使用Mockito进行桩模拟(stub)。桩模拟是指为特定的方法调用配置返回固定值的行为。在官方文档中,这种行为被称为stub(存根)。通过使用桩模拟,我们可以模拟本地对象以屏蔽对远程主机上对象的调用。

java

复制代码

when(mockedList.get(0).thenReturn("first"));
// 此时打印输出first
System.out.println(mockedList.get(0));
详细分析:模拟,方法调用抛出异常

java

复制代码

// 模拟获取第二个元素时,抛出RuntimeException
when(mockedList.get(1)).thenThrow(new RuntimeException);
// 此时抛出RuntimeException异常
System.out.println(mockedList.get(1));
// 没有返回值类型的方法也可以模拟异常抛出:
doThrow(new RuntimeException()).when(mockedList).clear();
详细分析:模拟调用方法时的参数匹配

anyInt()是Mockito中的一个匹配器,它用于匹配任何传入的int参数。这意味着无论传入的参数是什么值,都将返回"element"。

java

复制代码

when(mockedList.get(anyInt())).thenReturn("element");
// 此时打印是element
System.out.println(mockedList.get(99));

可以这样描述anyInt()的作用:它用于接受任意的int参数,并将其值忽略,返回固定的字符串"element"。

详细分析:模拟方法调用次数

JAVA

复制代码

// 调用add一次
mockedList.add("once");
// 验证add方法是否被调用了一次,两种写法效果一样
verify(mockedList)add("once");
verify(mockedList,times(1)).add("once");

可以使用Mockito中的atLeast(int i)atMost(int i)来验证方法被调用的最小和最大次数限制。

  • atLeast(int i)用于验证方法至少被调用了i
  • atMost(int i)用于验证方法最多被调用了i次。

可以精确地控制方法被调用的次数,并进行相应的验证。通过使用这些方法,可以更准确地断言方法的调用次数是否符合预期。

详细分析:验证被测试类是否正确工作,使用verify()

在默认情况下,对于所有未被桩模拟过的有返回值的方法,Mockito会返回相应的默认值。对于基本数据类型,如int,默认值是0;对于布尔类型,默认值是false;对于其他对象类型,默认值是null

mock对象会覆盖整个被mock的对象,因此没有stub的方法只能返回默认值。重复stub两次,则以第二次为准,如下将返回”second“。

java

复制代码

when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(0)).thenReturn("second");

下面这种形式表示第一次调用返回”first“,第二次调用返回”second“,可以写n多个

java

复制代码

when(mockedList.get(0)).thenReturn("first").thenReturn("second");

如果实际调用次数超过了stub过的次数,则会一直返回最后一次stub的值,如上例,第三次调用get(0),则返回 ”second“验证方法被调用特定的次数。

验证add方法被调用了两次

java

复制代码

verify(mockedList,times(2)).add("2");
验证add方法致至少被调用一次

java

复制代码

verify(mockedList.atLeastOnce()).add("2");
验证add方法至少被调用两次

java

复制代码

verify(mockedList,atLeast(2)).add("2");
验证add方法最大被调用5次

java

复制代码

verify(mockedList,atMost(5)).add("2");
验证add方法从未被调用

找到冗余的调用,使用never();

java

复制代码

verify(mockedList,never()).add("2");

模拟所期望的结果

为了模拟获取和检索数据的行为,我们可以使用Mockito来进行对象模拟。通过模拟数据获取和检索的过程,我们可以更轻松地编写和执行单元测试。

java

复制代码

// 你可以模拟具体的类,而不仅仅是接口
LinkedList mockedList = mock(LinkedList.class);
// 或者使用 Mockito 4.10.0+ 更简单
// LinkedList mockedList = mock();
// 在实际执行前出现存根
when(mockedList.get(0)).thenReturn("first");
// 下面将打印 "first
System.out.println(mockedList.get(0));
// 下面打印 "null",因为 get(999) 没有被存根化
System.out.println(mockedList.get(999));

模拟所期望的结果

java

复制代码

@Test  
public void when_thenReturn(){  
    //mock一个Iterator类  
    Iterator iterator = mock(Iterator.class);  
    //预设当iterator调用next()时第一次返回hello,第n次都返回world  
    when(iterator.next()).thenReturn("hello").thenReturn("world");
     //使用mock的对象  
    String result = iterator.next() + " " + iterator.next() + " " + iterator.next();  
    //验证结果  
    assertEquals("hello world world",result);  
 }

模拟方法体抛出异常

案例一

@Test(expected = IOException.class)注解中指定了期望的异常类型为IOException,用于断言在测试代码中是否抛出了该异常,通过指定expected注解和模拟对象的预设行为,我们可以断言在调用close()方法时是否会抛出IOException异常。

java

复制代码

@Test(expected = IOException.class)  
public void when_thenThrow() throws IOException {  
    OutputStream outputStream = mock(OutputStream.class);  
    OutputStreamWriter writer = new OutputStreamWriter(outputStream);  
    //预设当流关闭时抛出异常  
    doThrow(new IOException()).when(outputStream).close();  
    outputStream.close();  
}

使用doThrow()方法配置了当outputStreamclose()方法被调用时抛出IOException异常的预设行为。,调用outputStreamclose()方法,触发异常抛出。

案例二

@Test(expected = RuntimeException.class)注解中指定了期望的异常类型为RuntimeException,用于断言在测试代码中是否抛出了该异常。

java

复制代码

@Test(expected = RuntimeException.class)  
public void doThrow_when(){  
    List list = mock(List.class);  
    doThrow(new RuntimeException()).when(list).add(1);  
    list.add(1);  
}

使用doThrow()方法配置了当listadd(1)方法被调用时抛出RuntimeException异常的预设行为,调用listadd(1)方法,触发异常抛出。通过指定expected注解和模拟对象的预设行为,我们可以断言在调用add(1)方法时是否会抛出RuntimeException异常。

验证执行顺序

下面代码用于验证在特定顺序下方法的执行状况,使用inOrder.verify()方法按顺序验证了方法的调用。

java

复制代码

@Test  
public void verification_in_order(){
    List list = mock(List.class);
    List list2 = mock(List.class);
    list.add(1);
    list2.add("hello");
    list.add(2);
    list2.add("world");
    //将需要排序的mock对象放入InOrder
    InOrder inOrder = inOrder(list,list2);
    //下面的代码不能颠倒顺序,验证执行顺序  
    inOrder.verify(list).add(1);  
    inOrder.verify(list2).add("hello");  
    inOrder.verify(list).add(2);  
    inOrder.verify(list2).add("world");  
}

验证顺序

先验证list的add(1)方法被调用,然后验证list2的add("hello")方法被调用,接着验证list的add(2)方法被调用,最后验证list2的add("world")方法被调用。

通过使用InOrder对象,我们可以验证方法的执行顺序是否符合预期。如果按照预期顺序执行,则测试将成功通过。如果顺序不符合预期,测试将失败。

未完待续

敬请期待:【Java技术深入解析】「核心技术提升」最流行的Java模拟框架Mockito入门指南(进阶学习案例)。

相关文章
|
1天前
|
敏捷开发 测试技术 持续交付
深入理解自动化测试:框架与实践
【5月更文挑战第5天】 在现代软件开发周期中,自动化测试已成为确保产品质量和加速交付过程的关键环节。本文将深入探讨自动化测试的核心概念、框架选择以及实际实施过程中的最佳实践。通过分析各种自动化测试工具和技术的优缺点,我们旨在为读者提供一种系统化的方法来构建和维护有效的自动化测试环境。
|
1天前
|
机器学习/深度学习 人工智能 自然语言处理
自动化测试中AI驱动的决策框架设计与实现
【5月更文挑战第5天】 在软件测试领域,自动化测试已成为提升测试效率和质量的关键手段。然而,随着软件系统的复杂性增加,传统的自动化测试方法面临挑战,尤其在测试用例的生成、执行及结果分析等方面。本文提出一种基于人工智能(AI)的自动化测试决策框架,旨在通过智能化的算法优化测试过程,并提高异常检测的准确率。该框架结合机器学习和深度学习技术,能够自学习历史测试数据,预测高风险变更区域,自动生成针对性强的测试用例,并在测试执行过程中实时调整测试策略。此外,通过自然语言处理(NLP)技术,该框架还能对测试结果进行语义分析,进一步提供更深入的洞察。本研究不仅增强了自动化测试工具的智能性,也为软件质量保证提
|
5天前
|
数据管理 测试技术
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 随着软件开发的快速发展,自动化测试已经成为保证软件质量和提升开发效率的重要手段。本文将深入探讨自动化测试框架的核心概念,并以广泛应用的开源工具Selenium为例,解析其架构、原理及在实际项目中的运用。通过实例分析与性能评估,旨在为读者提供一套系统的自动化测试解决方案,并探讨其在复杂应用场景下的优化策略。
|
6天前
|
Java 测试技术 Maven
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
Spring Boot单元测试报错java.lang.IllegalStateException: Could not load TestContextBootstrapper [null]
|
6天前
|
敏捷开发 前端开发 JavaScript
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 在现代软件开发过程中,自动化测试已成为确保产品质量和加快市场投放的关键步骤。本文聚焦于流行的自动化测试框架——Selenium,探讨其架构、核心组件以及如何有效地利用Selenium进行Web应用测试。通过分析真实案例,我们将揭示Selenium在实际项目中的应用优势与面临的挑战,并提出优化策略。文章的目的在于帮助测试工程师深入理解Selenium,提升其在复杂项目中的运用效率。
|
6天前
|
前端开发 IDE 数据可视化
深入理解与应用自动化测试框架Selenium的最佳实践
【4月更文挑战第30天】 本文将深入剖析自动化测试框架Selenium的核心原理,并结合最佳实践案例,探讨如何有效提升测试覆盖率和效率。文中不仅涉及Selenium的架构解析,还将提供针对性的策略来优化测试脚本,确保测试流程的稳定性与可靠性。通过实例演示,读者可以掌握如何在不同测试场景中灵活运用Selenium,以及如何处理常见的技术挑战。
|
6天前
|
JavaScript 安全 编译器
【TypeScript 技术专栏】TypeScript 与 Jest 测试框架
【4月更文挑战第30天】本文探讨了TypeScript与Jest测试框架的结合在确保代码质量和稳定性上的重要性。Jest以其易用性、内置断言库、快照测试和代码覆盖率分析等特点,为TypeScript提供全面的测试支持。两者结合能实现类型安全的测试,提高开发效率,并涵盖各种测试场景,包括异步操作。通过实际案例分析,展示了如何有效利用这两个工具提升测试质量和开发效率,为项目成功奠定基础。
|
6天前
|
监控 JavaScript 前端开发
【TypeScript技术专栏】TypeScript的单元测试与集成测试
【4月更文挑战第30天】本文讨论了在TypeScript项目中实施单元测试和集成测试的重要性。单元测试专注于验证单个函数、类或模块的行为,而集成测试关注不同组件的协作。选用合适的测试框架(如Jest、Mocha),配置测试环境,编写测试用例,并利用模拟和存根进行隔离是关键。集成测试则涉及组件间的交互,需定义测试范围,设置测试数据并解决可能出现的集成问题。将这些测试整合到CI/CD流程中,能确保代码质量和快速响应变化。
|
6天前
|
设计模式 算法 安全
Java多线程编程实战:从入门到精通
【4月更文挑战第30天】本文介绍了Java多线程编程的基础,包括线程概念、创建线程(继承`Thread`或实现`Runnable`)、线程生命周期。还讨论了线程同步与锁(同步代码块、`ReentrantLock`)、线程间通信(等待/通知、并发集合)以及实战技巧,如使用线程池、线程安全设计模式和避免死锁。性能优化方面,建议减少锁粒度和使用非阻塞算法。理解这些概念和技术对于编写高效、可靠的多线程程序至关重要。
|
6天前
|
敏捷开发 监控 前端开发
深入理解与应用自动化测试框架:以Selenium为例
【4月更文挑战第30天】 在软件开发的快速迭代周期中,质量保证(QA)团队面临持续的压力,需确保产品在每次发布时都达到预期的质量标准。为了应对这一挑战,自动化测试成为了关键工具,它不仅提高了测试效率,还确保了测试的一致性和可重复性。本文将探讨自动化测试框架Selenium的核心组件、工作原理及其在实际测试中的应用。通过分析Selenium的优势和面临的常见问题,我们将讨论如何有效地集成Selenium到现有的测试流程中,以及如何克服常见的技术障碍。我们的目标是为读者提供一个清晰的指南,帮助他们理解和利用自动化测试框架来优化他们的软件测试实践。