添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

最近开始使用Mockito进行单元测试,本篇文章是理解Mockito并对其常见测试方式进行归纳总结。

Mocito体系

从使用角度,可以将Mockito分为插桩以及验证,使用者只需要关心模块提供的能力,不需要太过于深入了解。

  • ArgumentMatcher:参数匹配工具,比如Mokito.eq(“屈定”)类似语句会创建该匹配
  • OngoingStubbing:mock入口类,用于打桩,应对 when() thenXXX形式
  • Stubber:mock入口类,用于打桩,应对doXXX when()形式
  • VerificationMode:验证器,用于验证mock对象行为,常见实现有times()
  • InOrder:验证器的一种,用于验证执行顺序
  • ArgumentCaptor:用于被mock对象参数捕获
  • Mockito原理

    想要在开发中随心所欲的使用Mockito达到单测目的,了解Mockito原理是必须的。当我们在用Mockito时,经常写出以下类似代码,从逻辑上可以分为四部分:定义Mock对象,定义方法返回值,调用 单测方法 (这里直接调用mock方法,方便阐述原理),验证业务结果。那么每一步骤对于Mockito分别做了什么呢?

    1
    2
    3
    4
    5
    6
    7
    UserService mockService = Mockito.mock(UserService.class);

    Mockito.when(mockService.queryUser(Mockito.eq("xxx")))
    .thenReturn(new User("屈定"));

    User user = mockService.queryUser("xxx");
    Assertions.assertEquals("屈定", user.getOwner());

    简单来看,我们可以猜想到所谓的Mock测试技术原理应是预先定义好该方法返回值,使用AOP技术拦截对应的方法执行,当拦截直接返回对应的值,从而达到Mock效果,如下图所示:

    问题回到Mockito,可以提出以下三个问题为思考的切入点:

  • Mockito是如何创建AOP对象的
  • Mockito是如何预先定义方法返回值
  • Mockito是如何拦截方法执行并返回给定mock值的
  • Mockito是如何创建AOP对象的

    Mockito AOP对象的创建,对应代码的第一行 Mockito.mock(UserService.class) ,贴一下相关代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Mockito.mock(UserService.class)
    public static <T> T mock(Class<T> classToMock, MockSettings mockSettings) {
    return MOCKITO_CORE.mock(classToMock, mockSettings);
    }

    # MOCKITO_CORE.mock(xxxx)
    public <T> T mock(Class<T> typeToMock, MockSettings settings) {
    if (!MockSettingsImpl.class.isInstance(settings)) {
    throw new IllegalArgumentException("Unexpected implementation of '" + settings.getClass().getCanonicalName() + "'\n" + "At the moment, you cannot provide your own implementations of that class.");
    }
    MockSettingsImpl impl = MockSettingsImpl.class.cast(settings);
    MockCreationSettings<T> creationSettings = impl.build(typeToMock);
    T mock = createMock(creationSettings);
    mockingProgress().mockingStarted(mock, creationSettings);
    return mock;
    }

    上述代码中, Mockito 会使用全局静态变量 MOCKITO_CORE 创建代理对象,核心逻辑都在 MockitoCore 中,我们不必关心很细节,所以仍然按照带着问题的方式去探索。

    **1.**创建中的 MockSettings 可以做什么?

    MockSettings 可以针对一个mock对象创建做额外的配置,比如使用指定构造函数进行初始化,设置mock的一些调用监听器,以及mock拦截后默认返回值,一般创建时不指定,系统默认为 new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS)

    **2.**代理对象创建使用的是什么技术?对应拦截器实现是什么?

    对应细节都在 createMock(...) 方法中,这里就不贴代码,直接说结论。Mockito内部有一套插件机制,其中生成代理类对应 MockMaker 扩展点,默认实现为 org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker ,即用ByteBuddy技术生成对应代理类, ByteBuddy 博主了解的不是很多,但根据 SubclassBytecodeGenerator 处的源码可以发现其本质是使用 继承 动态创建需要代理类的子类,然后复写对应方法达到拦截目的,因此也说明了Mockito不支持final类,不支持static,private等方法mock的原因,不过这个算不上缺点,private方法外部根本不用关心,因此无需考虑mock,static方法作为全局使用的工具类型方法,如果也需要mock那么说明存在坏代码的味道,最好的做法是重构,而不是想方设法的mock。另外Mockito提供了 InlineByteBuddyMockMaker 实现类,该类利用 Instrumentation API 特性实现了静态方法,私有方法,final方法等拦截,更加强大,该特性还在试验中,感兴趣的可以尝试。

    ByteBuddy 代理类默认拦截器为 org.mockito.internal.creation.bytebuddy.MockMethodInterceptor 类。该类面向ByteBuddy提供的调用入口,内部会将参数包装后,给到真正的Mock拦截器 org.mockito.invocation.MockHandler ,进而决定返回或者插桩定义。 MockHandler 的实现可以理解为下图结构,

    **3.**Mockito如何保证线程安全

    在测试中会开启多线程测试,而Mockito又是一个静态调用形式,内部MockitoCore是全局共享变量,如果没有一定处理措施,必然会导致并发冲突。Mockito的解决方案是使用ThreadLocal,其提供 MockingProgress 类存储当前mock进度信息,提供 ThreadSafeMockingProgress 使其与ThreadLocal进行绑定,线程安全和mock本身原理关系不是很大,这里不多做分析,具体细节感兴趣的可以去了解下。

    Mockito是如何预先定义方法返回值

    常用的预定义方法返回值主要有两种形式 when() thenXXX doXXX when ,很多同学不理解这两个的区别,事实不然,两者的存在都很有必要,按照上述拦截后执行逻辑,分别分析两者的不同点。

    **1.**when() thenXXX是如何预定义值的

    还是以上述为例 Mockito.when(mockService.queryUser(Mockito.eq("xxx"))).thenReturn(new User("屈定")); ,Java是顺序执行代码的,那么针对该部分代码执行顺序可以拆解为以下几个步骤,下面分别分析:

  • Mockito.eq(“xxx”)
  •