Mockito入门

本文是我们名为“ 用Mockito进行测试 ”的学院课程的一部分。

在本课程中,您将深入了解Mockito的魔力。 您将了解有关“模拟”,“间谍”和“部分模拟”的信息,以及它们相应的Stubbing行为。 您还将看到使用测试双打和对象匹配器进行验证的过程。 最后,讨论了使用Mockito的测试驱动开发(TDD),以了解该库如何适合TDD的概念。 在这里查看

在本教程中,我们将研究Mockito Mocking Framework,并通过将其添加到类路径中来准备一个Eclipse项目来使用它。

1.为什么要模拟?

我们编写的所有代码都有一个相互依赖的网络,它可以调用其他几个类的方法,而这些类又可以调用其他方法。 确实,这就是面向对象编程的意图和力量。 通常在编写功能代码的同时,我们还将以自动化单元测试的形式编写测试代码。 我们使用这些单元测试来验证代码的行为,以确保其行为符合我们的预期。

当我们对代码进行单元测试时,我们希望对其进行隔离测试,并且希望对其进行快速测试。 为了进行单元测试,我们只关心在当前测试的类中验证我们自己的代码。 通常,我们还希望非常定期地执行单元测试,重构时以及在持续集成环境中工作时,每小时可能要执行多次。

这是我们所有相互依存成为问题的时候。 我们可能最终在另一个类中执行代码,该类具有导致单元测试失败的错误。 想象一下我们用来从数据库读取用户详细信息的类,如果要运行单元测试时没有数据库,会发生什么? 想象一下,一个调用多个远程Web服务的类,如果它们出现故障或需要很长时间响应怎么办? 我们的单元测试可能会由于我们的依赖关系而失败,而不是因为我们的代码行为出现问题。 这是不希望的。

除此之外,可能很难强制我们想要确保代码正确处理的特定事件或错误条件。 如果我们要测试某个反序列化对象的类会正确处理可能的ObjectStreamException怎么办? 如果我们要测试合作者的所有边界返回值怎么办? 确保将某些计算值正确传递给协作者该怎么办? 如果可能的话,复制我们的测试条件可能需要花费大量的代码并花费很长时间。

如果使用模拟,所有这些问题都会消失。 嘲笑就像是我们与之合作的类的替代品一样,它们取代了他们的位置,并按照我们告诉他们的表现去精确地表现。 嘲弄让我们假装我们真正的合作者在那儿,即使他们不在。 更重要的是,可以对模拟程序进行编程以返回我们想要的任何值,并确认将任何值传递给它们。 模拟程序立即执行,不需要任何外部资源。 假人会返回我们告诉他们的东西,抛出我们想让他们抛出的任何异常,并将按需一遍又一遍地执行这些操作。 他们让我们仅测试我们自己代码的行为,以确保我们的类能够正常工作,而不管其协作者的行为如何。

有几种可用于Java的模拟框架,每个框架都有自己的语法,自己的长处和缺点。 在本教程中,我们将使用Mockito框架,它是最流行的可用模拟框架之一。

2. Mockito框架简介

Mockito是一个Mocking框架,可以很容易地为要与您的被测类进行交互的类和接口创建模拟。 Mockito提供了一个非常简单的API,用于创建模拟并分配其行为。 它使您可以非常快速地指定预期的行为并验证与模拟的交互。

Mockito本质上具有两个阶段,其中一个或两个阶段都作为单元测试的一部分执行:

  • 存根
  • 验证

存根是指定模拟行为的过程。 这是我们告诉Mockito与模拟互动时想要发生的事情的方式。 存根使我们能够解决我们在第一部分中概述的一些问题–它使为测试创建所有可能的条件变得简单。 它让我们控制了模拟的响应,包括强迫它们返回我们想要的任何值,或者抛出我们想要的任何异常。 它使我们可以在不同条件下编写不同的行为。 存根使我们可以精确控制模拟将执行的操作。

验证是验证与我们的模拟互动的过程。 它使我们能够确定模拟的调用方式以及调用的次数。 它使我们可以查看模拟的参数,以确保它们符合预期。 验证使我们能够解决第一部分中提到的其他问题–它使我们确保将我们期望的值准确地传递给我们的合作者,并且不会发生意外情况。 验证使我们能够准确确定模拟发生了什么。

通过将这两个简单的阶段联系在一起,我们可以构建极其灵活和强大的单元测试,使用非常简单的Mockito API编码复杂的模拟行为和复杂的模拟交互验证。

Mockito确实有一些限制,包括

  • 你不能嘲笑期末班
  • 您不能模拟静态方法
  • 您不能模拟最终方法
  • 您不能模拟equals()或hashCode()

存根的快速示例

想象一下,您正在编写一个类,该类调用物理温度传感器的API。 您要调用double getDegreesC()方法并根据从传感器返回的值返回以下字符串之一:“ Hot”,“ Mild”,“ Cold”。 至少可以说,要使单元测试控制房间的环境温度以测试功能非常困难。 但是,如果我们使用Mockito来创建一个替代传感器的模拟物怎么办?

现在我们可以在单元测试中编写如下代码:

when(sensor.getDegreesC()).thenReturn(15.0);

这告诉Mockito,当模拟传感器收到对getDegreesC()的调用时,它应该然后返回值15.0。

快速验证示例

假设您有一个类进行一些计算,并负责在观察者完成计算后通知观察者。 您要确保在执行方法的过程中一次调用了观察者的notify()方法。 您可以在观察器中设置一些布尔值,然后从单元测试中进行检查,但这意味着更改某些生产代码,甚至您可能都不拥有这些代码。 Mockito怎么样,如果观察者是模拟的呢?

现在我们可以在单元测试中编写如下代码:

verify(observer).notify();

这告诉Mockito必须仅一次调用一次notify()方法,否则单元测试将失败。

3.混合一个Mockito

现在,我们已经了解了一些有关框架的知识,让我们在项目中使用它。

如果使用Maven,则将Mockito添加到项目中就像添加以下依赖项一样简单:

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
   	<version>1.9.5</version>
   	<scope>test</scope>
   </dependency>

如果您使用Gradle,则只需添加以下内容

dependencies {
    testCompile "org.mockito:mockito-all:1.9.5"
}

要将旧版Mockito添加到Eclipse项目的类路径中,请从Mockito下载页面中获取最新的jar(请取名为mmitito-all-1.9.5.jar)并将其下载到硬盘上。

右键单击您的eclipse项目,然后选择“属性”,然后在左窗格中选择“ Java Build Path”,然后在右侧选择“ Libraries”。

在“库”选项卡上,单击“添加外部Jar”按钮,然后导航到您先前下载的模拟所有jar。 选择罐子,它现在已添加到您的项目中并可供使用。

在撰写本文时,Mockito的最新版本是1.9.5,但是在将其添加到项目之前,应检查更新。

4.将Mockito与JUnit一起使用

要将Mockito集成到您的JUnit测试类中,可以使用提供的Test Runner MockitoJUnitRunner 。 只需使用以下注释您的测试课:

@RunWith(MockitoJUnitRunner.class)

这告诉Mockito在测试类中接受所有带注释的模拟,并对其进行初始化以进行模拟。 然后,您可以简单地使用@Mock注释任何实例变量,以将其用作模拟。 请注意,您应该导入org.mockito.Mock而不是org.mockito.mockitoannotations.Mock ,已弃用。

与所有示例一样,我们将创建一个新的Test类,并在其中使用Mockito模拟java.util.List

import java.util.List;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    
    @Mock
    private List<String> mockList;
    
}

@Mock注释告诉Mockito模拟mockList将被视为模拟,而@RunWith(MockitoJUnitRunner.class)告诉Mockito遍历MyTest所有带有@Mock注释的成员,并将其初始化以进行模拟。 您不必将任何新实例分配给嘲笑列表,这是由Mockito在后台完成的。 通过上面的简单代码,mockList准备好用作模拟了。

尝试添加以下导入:

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

然后是以下简单的测试用例:

@Test
    public void test() {
   	 String expected = "Hello, World!";
   	 when(mockList.get(0)).thenReturn(expected);

   	 String actual = mockList.get(0);
   	 assertEquals(expected, actual);
   	 System.out.println(actual);
    }

在这里,我们看到了我们的期望-我们有一个字符串"Hello, World!" 然后继续对模拟列表的List.get()方法进行存根, List.get()在请求列表的第一个元素时返回预期的String。

然后,我们调用mockList.get(0)来获取测试的实际值,并断言我们的实际值等于我们的预期值,然后将其输出到控制台以进行良好测量。

我们根本没有创建真实列表,也没有插入“ Hello,World!” 进入列表。 它只是一个模拟列表,它具有或了解的唯一功能是输入为0的get()方法。

尝试更改String actual = mockList.get(0);String actual = mockList.get(1); 并运行测试。 您将看到actual值现在为空。 原因是我们唯一保留的功能是使用输入0调用.get()– Mockito不知道如何使用输入1进行操作,因此它仅返回null。 实际上,我们调用List的任何其他方法都将返回null,而任何不返回任何值的方法将有效地充当no-op。 这是一个功能强大的控件,在几行代码中,我们创建了List的实现,该实现恰好在每次调用它时都可以实现我们想要的功能。

5. Mockito最佳做法

Mockito通常鼓励在单元测试和设计中采用标准的最佳实践,即:

  • Mockito没有模拟静态方法的规定,因为Mockito鼓励面向对象的设计和对过程代码的依赖注入。
  • Mockito没有提供模拟私有方法的规定,因为公共方法应该是黑盒,并且从测试私有方法的角度来看不存在。
  • Mockito打包并鼓励使用Hamcrest Matchers,这将在后续教程中介绍。
  • Mockito鼓励遵守Demeter法则,而不鼓励嘲笑链式方法。
  • 您不应存根或验证在不同线程之间共享的模拟。 但是,您可以调用共享模拟的方法。
  • 您无法验证模拟的toString()方法,原因是测试环境本身可能会调用它,因此无法进行验证。
  • 如果您的测试用例使用了“当时给定”表示法,则可以使用org.mockito.BDDMockito的存根方法,以便when(mock.method()).thenReturn(something)成为given(mock.method()).willReturn(something)因为它将以您的测试格式很好地阅读。
  • 可以在不使用Mockito批注的情况下使用Mockito,但是使用批注更加容易和整洁,这就是我们在这些教程中将要做的。
  • 如果您的测试要求您出于测试目的而修改类的特定方法的行为,则可以“监视”任何类,包括被测类。 Mockito明确建议仅在偶尔使用间谍时(例如,在处理遗留代码时受到限制)。 这将在以后的教程中介绍。
  • 如果对间谍方法的实际调用可能会产生错误条件,或者由于某些其他原因而无法调用,则Mockito建议使用do *系列方法进行存根。 这将在以后的教程中介绍。
  • Mockito将允许您使用参数匹配器代替实际参数,但有一个限制:如果一个参数使用匹配器,则所有参数都必须使用匹配器。 参数匹配器将在以后的教程中介绍,但应谨慎使用。
  • Mockito提供了verifyNoMoreInteractions()方法来验证特定的模拟不再有任何交互,但建议仅在适当的情况下谨慎使用。
  • Mockito提供了Answer接口以允许使用回调进行存根,但是建议不要使用它,并鼓励您使用thenReturn()doThrow()方法进行简单的存根。 我们将在以后的教程中介绍答案。
  • 如果使用ArgumentCaptor进行参数验证,则应仅在验证阶段而不是存根阶段使用它。 ArgumentCaptor将在以后的教程中介绍。
  • Mockito建议非常谨慎地使用Partial Mocks,主要是在处理遗留代码时。 设计良好的代码不应要求使用部分模拟。
  • Mockito提供了reset()方法,用于在测试方法的中间重置模拟,但是建议您不要使用它,因为它是一种代码味道,可能会使测试过长和复杂。

有更多功能和做法,但是这些是Mockito告诉您要注意的主要功能和做法。 在接下来的教程中,我们将涵盖以上所有内容,并进行更深入的介绍。

翻译自: https://www.javacodegeeks.com/2015/11/getting-started-with-mockito.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值