Java测试框架Mockito源码分析

Mockito源码浅析——核心逻辑 一、Mockito源码逻辑简介 Mockito是一个Mock框架,让你用简单干净的API写漂亮的测试代码。Mockito通过ByteBuddy动态字节码生成技术生成mock类型(你要模拟类的代理类),默认通过objenesis框架生成mock类型的对象实例,mock实例对象的所有方法都会调用MockHandlerImpl对象实例的handle方法。 该文章主要从三个部分进行讲解:mock代理对象创建,mock对象方法的打桩,mock对象方法的入参验证。 二、mock代理... 阅读详情

1.Mockito简介

测试驱动的开发(Test Driven Design, TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,一个很普遍的问题是,要测试的类会有很多依赖,这些依赖的类/对象/资源又会有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。
所幸,我们有一个应对这个问题的办法:Mock。简单地说就是对测试的类所依赖的其他类和对象,进行mock - 构建它们的一个假的对象,定义这些假对象上的行为,然后提供给被测试对象使用。被测试对象像使用真的对象一样使用它们。用这种方式,我们可以把测试的目标限定于被测试对象本身,就如同在被测试对象周围做了一个划断,形成了一个尽量小的被测试目标。Mock的框架有很多,最为知名的一个是Mockito,这是一个开源项目,使用广泛。官网:http://site.mockito.org/。

2.Mockito框架设计

首先我们要知道,Mock对象这件事情,本质上是一个Proxy模式的应用。Proxy模式说的是,在一个真实对象前面,提供一个proxy对象,所有对真实对象的调用,都先经过proxy对象,然后由proxy对象根据情况,决定相应的处理,它可以直接做一个自己的处理,也可以再调用真实对象对应的方法。示例:

代码中的注释描述了代码的逻辑:先创建mock对象,然后设置mock对象上的方法get,指定当get方法被调用,并且参数为0的时候,返回”one”;然后,调用被测试方法(被测试方法会调用mock对象的get方法);最后进行验证。逻辑很好理解,但是初次看到这个代码的人,会觉得有点儿奇怪,总感觉这个代码跟一般的代码不太一样。让我们仔细想想看,下面这个代码:

// 设置mock对象的行为 - 当调用其get方法获取第0个元素时,返回”one”
Mockito.when(mockedList.get(0)).thenReturn(“one”);

public class MockDemo {
    // 创建mock对象
    List<String> mockedList = Mockito.mock(List.class);

    @Before
    public void setUp(){
        // 设置mock对象的行为 - 当调用其get方法获取第0个元素时,返回"one"
        Mockito.when(mockedList.get(0)).thenReturn("one");
    }

    @Test
    public void mockDemoTest(){
        // 使用mock对象 - 会返回前面设置好的值"one",即便列表实际上是空的
        String str = mockedList.get(0);

        Assert.assertTrue("one".equals(str));
        Assert.assertTrue(mockedList.size() == 0);
    }
}

如果按照一般代码的思路去理解,是要做这么一件事:调用mockedList.get方法,传入0作为参数,然后得到其返回值(一个object),然后再把这个返回值传给when方法,然后针对when方法的返回值,调用thenReturn。好像有点不通?mockedList.get(0)的结果,语义上是mockedList的一个元素,这个元素传给when是表示什么意思?所以,我们不能按照寻常的思路去理解这段代码。实际上这段代码要做的是描述这么一件事情:当mockedList的get方法被调用,并且参数的值是0的时候,返回”one”。很不寻常,对吗?如果用平常的面向对象的思想来设计API来做同样的事情,估计结果是这样的:

Mockito.returnValueWhen(“one”, mockedList, “get”, 0);
第一个参数描述要返回的结果,第二个参数指定mock对象,第三个参数指定mock方法,后面的参数指定mock方法的参数值。这样的代码,更符合我们看一般代码时候的思路。

但是,把上面的代码跟Mockito的代码进行比较,我们会发现,我们的代码有几个问题:
1.不够直观
2.对重构不友好
第二点尤其重要。想象一下,如果我们要做重构,把get方法改名叫fetch方法,那我们要把”get”字符串替换成”fetch”,而字符串替换没有编译器的支持,需要手工去做,或者查找替换,很容易出错。而Mockito使用的是方法调用,对方法的改名,可以用编译器支持的重构来进行,更加方便可靠。

3.实现分析

明确了Mockito的方案更好之后,我们来看看Mockito的方案是如何实现的。首先我们要知道,Mock对象这件事情,本质上是一个Proxy模式的应用。Proxy模式说的是,在一个真实对象前面,提供一个proxy对象,所有对真实对象的调用,都先经过proxy对象,然后由proxy对象根据情况,决定相应的处理,它可以直接做一个自己的处理,也可以再调用真实对象对应的方法。Proxy对象对调用者来说,可以是透明的,也可以是不透明的。

Java本身提供了构建Proxy对象的API:Java Dynamic Proxy API,而Mockito是用Cglib来实现的。
下面看下运行时期Cglib生成的Mock代理对象的.class文件是怎么样的

public class List$$EnhancerByMockitoWithCGLIB$$d85c0201 implements List, Factory {
    ........
    private static final Method CGLIB$get$9$Method;
    ........

    public final boolean removeAll(Collection var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if(this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if(var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$removeAll$26$Method, new Object[]{var1}, CGLIB$removeAll$26$Proxy);
            return var2 == null?false:((Boolean)var2).booleanValue();
        } else {
            return super.removeAll(var1);
        }
    }

    final boolean CGLIB$retainAll$27(Collection var1) {
        return super.retainAll(var1);
    }
    ..........
    case -208030418:
            if(var10000.equals("get(I)Ljava/lang/Object;")) {
                return CGLIB$get$9$Proxy;
            }
            break;
    .........
}

可以看到Mokito利用Cglib为List的所有方法都做了Mock实现,但是我们只对get方法做了Stub,所以只用关注这些代码

CGLIB$get$9$Proxy = MethodProxy.create(var1, var0, "(I)Ljava/lang/Object;", "get", "CGLIB$get$9");
case -208030418:
            if(var10000.equals("get(I)Ljava/lang/Object;")) {
                return CGLIB$get$9$Proxy;
            }
            break;

看到第一句是不是和我上面说的面向对象的写法很像
这里写图片描述
下面我们来看看,到底如何实现文章开头的示例中的API。如果我们仔细分析,就会发现,示例代码最难理解的部分是建立Mock对象(proxy对象),并配置好mock方法(指定其在什么情况下返回什么值)。只要设置好了这些信息,后续的验证是比较容易理解的,因为所有的方法调用都经过了proxy对象,proxy对象可以记录所有调用的信息,供验证的时候去检查。下面我们重点关注stub配置的部分,也就是我们前面提到过的这一句代码:

// 设置mock对象的行为 - 当调用其get方法获取第0个元素时,返回"one"
Mockito.when(mockedList.get(0)).thenReturn("one");

当when方法被调用的时候,它实际上是没有办法获取到mockedList上调用的方法的名字(get),也没有办法获取到调用时候的参数(0),它只能获得mockedList.get方法调用后的返回值,而根本无法知道这个返回值是通过什么过程得到的。这就是普通的java代码。为了验证我们的想法,我们实际上可以把它重构成下面的样子,不改变它的功能:

  // 设置mock对象的行为 - 当调用其get方法获取第0个元素时,返回"one"
  String str = mockedList.get(0);
  Mockito.when(str).thenReturn("one");

这对Java开发者来说是常识,那么这个常识对Mockito是否还有效呢。我们把上面的代码放到Mockito测试中实际跑一遍,结果跟前面的写法是一样的,证明了常识依然有效。

有了上面的分析,我们基本上可以猜出来Mockito是使用什么方式来传递信息了 —— 不是用方法的返回值,而是用某种全局的变量。当get方法被调用的时候(调用的实际上是proxy对象的get方法),代码实际上保存了被调用的方法名(get),以及调用时候传递的参数(0),然后等到thenReturn方法被调用的时候,再把”one”保存起来,这样,就有了构建一个stub方法所需的所有信息,就可以构建一个stub方法了。

上面的设想是否正确呢?Mockito是开源项目,我们可以从代码当中验证我们的想法。下面是MockHandlerImpl.handle()方法的代码。代码来自Mockito在Github上的代码。

public Object handle(Invocation invocation) throws Throwable {
     if (invocationContainerImpl.hasAnswersForStubbing()) {
         ...
    }
     ...
     InvocationMatcher invocationMatcher = matchersBinder.bindMatchers(
            mockingProgress.getArgumentMatcherStorage(),
           invocation
    );
   mockingProgress.validateState();
    // if verificationMode is not null then someone is doing verify()
   if (verificationMode != null) {
   ...
      }
    // prepare invocation for stubbing   invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher);
  OngoingStubbingImpl<T> ongoingStubbing = 
  new OngoingStubbingImpl<T>(invocationContainerImpl);
mockingProgress.reportOngoingStubbing(ongoingStubbing);
   ...
}

注意第1行,第6-9行,可以看到方法调用的信息(invocation)对象被用来构造invocationMatcher对象,然后在第19-21行,invocationMatcher对象最终传递给了ongoingStubbing对象。完成了stub信息的保存。这里我们忽略了thenReturn部分的处理。有兴趣的同学可以自己看代码研究。

看到这里,我们可以得出结论,mockedList对象的get方法的实际处理函数是一个proxy对象的方法(最终调用MockHandlerImpl.handle方法),这个handle方法除了return返回值之外,还做了大量的处理,保存了stub方法的调用信息,以便之后可以构建stub。

4.总结

通过以上的分析我们可以看到,Mockito在设计时实际上有意地使用了方法的“副作用”,在返回值之外,还保存了方法调用的信息,进而在最后利用这些信息,构建出一个mock。而这些信息的保存,是对Mockito的用户完全透明的。“模式”告诉我们,在设计方法的时候,应该避免副作用,一个方法在被调用时候,除了return返回值之外,不应该产生其他的状态改变,尤其不应该有“意料之外”的改变。但Mockito完全违反了这个原则,Mockito的静态方法Mockito.anyString(), mockInstance.method(), Mockito.when(), thenReturn(),这些方法,在背后都有很大的“副作用” —— 保存了调用者的信息,然后利用这些信息去完成任务。这就是为什么Mockito的代码一开始会让人觉得奇怪的原因,因为我们平时不这样写代码。

然而,作为一个Mocking框架,这个“反模式”的应用实际上是一个好的设计。就像我们前面看到的,它带来了非常简单的API,以及编译安全,可重构等优良特性。违反直觉的方法调用,在明白其原理和一段时间的熟悉之后,也显得非常的自然了。设计的原则,终究是为设计目标服务的,原则在总结出来之后,不应该成为僵硬的教条,根据需求灵活地应用这些原则,才能达成好的设计。在这方面,Mockito堪称一个经典案例。

Java测试用例自动生成器项目实战(java-testcae-gen) Java测试用例自动生成器(java-testcae-gen)是一款面向现代Java应用开发的智能化测试辅助工具,致力于通过静态分析与规则引擎结合的方式,自动识别业务类中的可测方法并生成高覆盖率的单元测试代码。当前,传统手工编写测试用例存在效率低、维护难、覆盖率不稳定等问题,尤其在微服务与持续集成(CI/CD)普及的背景下,测试滞后已成为交付瓶颈。 阅读详情

相关推荐

深入理解Mockito:框架工作原理与实战示例

Mockito是Java编程语言中一个流行的单元测试框架,主要用于模拟对象的创建与行为,以此来编写测试用例。它允许开发人员在不依赖具体实现的情况下对代码进行单元测试,这在测试驱动开发(TDD)和行为驱动开发(BDD)中尤为重要。为了使Mock对象更贴近真实情况,可以使用Mockito的answer功能来自定义方法调用的返回值。// 根据参数逻辑返回不同的值});这段代码中,方法会根据传入的参数返回计算后的结果。

weixin_35391606的博客 501

反模式的经典 - Mockito设计解析

测试驱动的开发(Test Driven Design, TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,一个很普遍的问题是,要测试的类会有很多依赖,这些依赖的类/对象/资源又会有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。\\所幸,我们有一个应对这个问题的办法:Mock。简单地说就是对测试的类所依赖的其他类和对象,进行mock ...

cpongo5 310

第三节: 串口通信(用CubeMX学习STM32)

用CubeMX学习STM32, 串口通信学习, 通过轮询方式和中断方式两种方式进行通信演示。 STM32F407zgt6

Kevin_8_Lee的博客 8903

Mockito核心源码分析总结

Mockito核心源码分析总结 Mockito使用示例 在开始看mockito的源码之前,首先看一下下面两个mockito的使用示例。 简单使用示例 @Test public void mockitoTest(){ // 1.生成一个mock对象 List<String> mockedList = Mockito.mock(List.class); // 查看mock对象的类名: $java.util.List$$Enha

Blossom 1857

双足步行机器人的ZMP与CoP检测

静态步行与动态步行   机器人步态分为静态步行和动态步行。当机器人做静态步行运动时,身体的各个部分运动速度很小,机器人的整体稳定性较易控制。静态步行稳定性采用机器人的重心地面投影点(Center of Gravity,简称CoG)作为稳定性标准,这种判定方法适用于运动较为缓慢的情况。如果机器人采用的是一种高速运动的步行方式,则称之为动态步行。当机器人进行动态步行时,其重心的位置和加速度的大小时...

weixin_34112208的博客 5906

BLDC方波控制双闭环Simulink仿真模型

无刷直流电机方波控制双闭环Simulink仿真模型搭建详细教程,包括速度PI环、电流PI环、换向逻辑和受控电压源,仿真结果良好。

m0_57458432的博客 6869

5个Mockito高级特性,让你的单元测试效率提升10倍

# 5个Mockito高级特性,让你的单元测试效率提升10倍 你还在为单元测试中复杂的依赖模拟烦恼吗?还在为验证方法调用顺序而编写冗长代码吗?本文将揭示Mockito 5.x版本中5个强大却少有人知的高级特性,帮你轻松解决这些痛点。读完本文,你将能够: - 掌握静态方法与构造函数的无缝模拟 - 使用BDD风格API编写更易读的测试用例 - 利用参数捕获器精准验证方法入参 - 定制灵活的参数匹配...

gitblog_00621的博客 857

【RTOS】 Vxworks 653 第一章 —— 初识 vx653 3.x

。。不是很重要,自己搜吧鲁棒分区是一种确保共享计算资源中独立运行功能实现​​时空隔离​​的机制,其目标是为集成模块中的混合临界性应用提供与联邦式部署(federated implementation)同等级别的功能隔离保障。具体表现为:​​多关键性共存​​:支持不同安全等级应用(如飞控系统A级与娱乐系统D级)在同一模块中协同运行。​​抗干扰能力​​:防止未授权或意外的时空干扰(如内存越界、时间窗口冲突),确保应用间互不影响。​​。

qq_38183694的博客 1280

Mockito 初探

 一. 简介 1.背景         Mockito是一个流行的Mocking(模拟测试)框架,通过使用Mocking框架,可以尽可能使unit test独立的。unit test保持独立的好处不在这里讨论。     官方文档: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html    2....

sgq0085的专栏 529

RimWorld Mod开发避坑指南:从Defs命名规范到XML结构详解(附实战代码)

本文深入解析RimWorld Mod开发中Defs文件的编写规范与高级技巧,涵盖命名冲突预防、XML结构优化及高级功能实现。通过实战代码示例,帮助开发者避开常见陷阱,提升Mod兼容性与稳定性,特别适合RimWorld Mod开发者参考。

weixin_30790841的博客 450

Coze智能体网页集成实战:从零搭建免登录AI助手

本文详细介绍了如何利用Coze Chat SDK从零开始为网站集成免登录AI助手。通过获取访问令牌和智能体ID,仅需几行JavaScript代码即可将功能完整的对话窗口嵌入网页,实现开箱即用的智能交互。文章重点讲解了核心部署步骤、会话隔离方案以及UI深度定制方法,帮助开发者快速打造贴合品牌、提升用户体验的网页AI助手。

gold8的专栏 555

强大的Mockito测试框架

1自动生成Mock类 在需要Mock的属性上标记@Mock注解,然后@RunWith(MockitoJUnitRunner.class)或者在setUp()方法中显示调用MockitoAnnotations.initMocks(this);生成Mock类即可。 2自动注入Mock类到被测试类 只要在被测试类上标记@InjectMocks,Mockito就会自动将标记@Mock...

系统运维 719

华为OD技术面真题 - Python开发 - 1

汇总华为OD技术面八股Python八股,高频技术真题并提供详细解答。

qq_45776114的博客 1716

分析 JUnit 框架源代码

http://www.ibm.com/developerworks/cn/java/j-lo-junit-src/

心系旭阳 445

面经-2023-禾赛-FPGA工程师

面经-2023-禾赛-FPGA工程师

w0shishabi的博客 2969

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

面试简历模板,2024Android通用流行框架大全(1),Android面试题2024中高级

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。【Android思维脑图(技能树)】

2401_83977626的博客 1140

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Android单元测试框架源码分析(二)浅析Robolectric

上述

xiaoshixiu的专栏 2314

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

基于QwQ-32B大模型实现Java代码JUnit单元测试自动生成实战

单元测试是保障软件质量、提升代码可维护性的核心工程实践,其核心原理在于通过编写自动化测试用例来验证代码单元(如方法、类)在各种输入下的行为是否符合预期。在Java生态中,JUnit是事实上的标准单元测试框架,而JUnit 5提供了更强大的参数化测试、扩展模型等现代特性。随着大语言模型在代码理解与生成任务上展现出的强大能力,将AI技术应用于自动化测试生成已成为提升研发效能的重要方向。通过结合大模型的代码理解能力与JUnit 5的丰富特性,可以自动化生成覆盖正常路径、边界条件及异常场景的高质量测试用例,从而将开

weixin_30889885的博客 423

Android单元测试<上>

单元测试业界标准: MVP + Junit4 + Mockito + Hamcrest + Espresso + Dragger2 一、为何要做单元测试 App持续集成的时候需要一个集成测试保障其正确性(正确性) 页面较复杂的时候,我们是否可以先测试业务逻辑的正确性(边开发边测试) 项目较大,编译缓慢,测试业务逻辑部分(测试速度大幅提高) 对代码结构、代码健壮性、代码可维护度都有很大提高 不

yuanyang5917的专栏 958

JUnit源码分析

<br />from: http://www.searchtb.com/2010/10/junit%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.html

mypop的专栏 688

BTTV安卓版开发者工具使用:调试与测试最佳实践指南

BTTV安卓版是一款为Twitch安卓移动应用添加BetterTTV、FrankerFaceZ和7TV表情的增强模组。作为一名开发者,掌握高效的调试与测试技巧对于确保模组稳定性和功能完整性至关重要。本文将分享BTTV安卓版开发中的调试与测试最佳实践,帮助开发者快速定位问题并优化开发流程。 ## 📱 项目架构与构建流程概览 在开始调试之前,了解BTTV安卓版的架构至关重要。该项目采用**补丁式

gitblog_00570的博客 784

Mockito 原理与实战

Mockito 原理与实战

m0_50742275的博客 622

Mockito

打桩可以理解为 mock 对象规定一行的行为,使其按照我们的要求来执行具体的操作。在 Mockito 中,常用的打桩方法为。验证是校验待验证的对象是否发生过某些行为,Mockito 中验证的方法是:verify。方法来 mock静态方法的所属类,此方法返回一个具有作用域的模拟对象。Verify 配合 time() 方法,可以校验某些操作发生的次数。spy() 方法与 mock() 方法不同的是。实例:使用 mock 方法 mock 一个类。thenReturn() 代码示例。快速 mock 的方法,使用。

aaenen 的博客 1561

mockito_书评:Mockito Essentials

mockito Sujoy Acharya的Mockito Essentials副标题( Packt出版 ,2014年10月)是:“实用指南,可帮助您使用Mockito进行单元测试并开始运行。” Mockito Essentials中的前言和七章涵盖大约190个实质性页面。 前言 在序言中,作者Sujoy Acharya写道Mockito Essentials “是一个高级指南,将帮...

最佳 Java 编程 329

Mockito.when()

Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.(启用存根方法。 当你希望模拟在调用特定方法时返回特定值时使用它。) Simply put: "When the x method is called then ret...

xiehuanhuan1991的博客 2万+
上一篇: java socket基础-长连接与短连接
下一篇: 数据库表结构设计的一些总结
张焱飞
博客等级 码龄11年 8粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值