「测试」Mock之Mockito认识之旅

一.前言

  关于这篇文章的起源,是第三次思沃大讲堂的作业的题目中,有这样一段话

把前两问做的类集成起来,写一个集成的单元测试,写一个集成测试。

问题来了,集成的单元测试和集成测试有什么区别呢?
  集成测试(Integration Testing):是在单元测试的基础上,将所有模块按照概要设计要求组装成为一个子系统或者系统,进行集成测试。一些模块虽然能够单独工作,但并不能保证连接起来也能正常的工作,程序在某些局部反映不出来的问题,在全局上很可能暴漏出来,因此集成测试十分必要。
  集成的单元测试:按字面意思的理解,就是对该集成类进行单元测试。单元测试就是对已经实现的软件最小单元进行测试以保证构成软件系统的各个单元的质量。这么说来,在此处集成的单元测试和集成测试并无区别吗?
  No~ 区别还是有的,集成的单元测试,首先是个单元测试,然后是对集成类进行单元测试,也就是说只测试该类的逻辑,而不用关注他所依赖的类是否正确实现。如何不关注依赖类的是否正确实现呢?这就是接下来我要介绍的Mock啦,这得感谢我的Buddy,让我对Mock有了直白的认识,然后才关注Mock,从而进一步了解。


二.为什么需要mock

  我们在做测试的时候,往往会发现我们要测试的类或方法会引用很多外部依赖的对象,而我们没法控制这些外部依赖的对象,为了解决这个问题,我们需要用到Mock来模拟这些外部依赖的对象,从而控制它们。举个例子,service调用dao,即service依赖dao,我们可以用mock来模拟真实的dao调用,从而达到测试service的目的。
  模拟对象(Mock Object)可以取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能,模拟对象背后的目的就是创建一个轻量级的,可以控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能。

mock对象使用范畴
1.真实对象具有不可确定的行为,产生不可预测的效果。
2.真实对象很难被创建的。
3.真实对象的某些行为很难被触发。
4.真实对象实际上还不存在的。


三.常见的mock框架
  • jmock:通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得测试变得简单。缺点:在执行前记录期望行为,显得很繁琐。
  • Mockito:Mockito通过在执行后校验哪些函数已经被调用,消除了对期望行为的需要,API非常简洁。缺点:对于静态函数、构造函数、私有函数等还是无能为力。
  • powermock:PowerMock是在Mockito的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 实现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。缺点:会对字节码篡改,即测试时的字节码与平时编译出来的字节码是不一样的,而很多统计单元测试覆盖率的插件是以字节码来统计的,所以PowerMock编写的测试程序不能被统计进覆盖率。

推荐Mockito和powermock


四.Mockito简单介绍

一般使用Mockito需要执行以下步骤:
1.模拟并替换测试代码中外部依赖。
2.执行测试代码。
3.验证测试代码是否被正确的执行


Mock执行步骤.png

使用注解(@Mock、@InjectMocks等)的话,必须实例化mock对象,有两种方式实例化mock对象:

  • @RunWith(MockitoJUnitRunner.class)
  • MockitoAnnotations.initMocks(this)

当我们需要配置某个方法的返回值时,Mockito提供了链式的API供我们方便的调用:

  • when(mockObject.someMethod()).thenReturn(...)
    用来定义当条件满足时函数的返回值。
  • when(mockObject.someMethod()).thenReturn(...).thenReturn(...)
    用来定义多个返回值的情况。
  • doReturn(...).when(mockObject.someMethod())
  • when(mockObject.someMethod()).thenThrow(new Runtime
    Exception())
    执行某方法时抛出异常。
  • doThrow(new RuntimeException()).when(mockObject.someMethod())
  • 对void方法进行预期设定
    1.doNothing().when(mock.someMethod())
    2.doThrow(new RuntimeException()).when(mock.someMethod())
    3.doNothing().doThrow(new RuntimeException()).when(mock.someMethod())
  • Mockito会自动记录自己的交互行为,可以用verify(…).methodXxx(…)语法来验证Xxx()方法是否按照预期进行了调用
    1.验证调用次数:verify(mockObject,times(n)).someMethod(argument);//n为被调用的次数
    2.验证超时:verify(mockObject, timeout(100)).someMethod();
    3.既验证调用次数,又验证是否超时:verify(mockObject, timeout(100).times(1)).someMethod();

需注意:

  • 对于final、static方法,Mockito 无法对其 when(…).thenReturn(…) 操作。

五.Mockito使用实例

以第三次思沃大讲堂的作业为例。
问题描述:游戏开始后,系统会随机给出一个四位,每位都不重复的数字作为答案。由用户输入自己猜测的 四个数字。 系统会将两个数字进行对比,并给形出xAxB的提示, 比如”2A1B”。 如果数字猜对而且位置也对,就是一个A。 如果数字猜对但位置不对,就是一个B。 例如:系统给出”1234”,用户输入”1234” 返回”4A0B” 系统给出”1234”,用户输入”4321” 返回”0A4B”。
CompareNumber类:实现比较。只有一个函数,该函数接受两个参数,一个是答案,一个是用户输 入的四位数。返回值是xAxB的字符串 。
AnswerGenerator类:生成随机的四位无重复位数字。只有一个函数,返回一个四位,每位都不重复随机数。
Guess类:只有一个函数,只有一个参数。把前两问做的类集成起来。
GuessUnitTest类:对Guess类写单元测试(即文首所说的集成的单元测试)。

public class Guess {
    private CompareNumber compareNumber = new CompareNumber();
    private AnswerGenerator answerGenerator = new AnswerGenerator();
    private int answer=answerGenerator.generatorFourDigits();

    public String guessTheDigit(int guessDigit){
        String result = compareNumber.compareToAnswer(answer,guessDigit);
        return result;
    }
}
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class GuessUnitTest {

    @InjectMocks
    private Guess guess = new Guess();

    @Mock
    private CompareNumber mockCompareNumber = new CompareNumber();

    @Mock
    private AnswerGenerator mockAnswerGenerator = new AnswerGenerator();

    @Before
    public void init() throws NoSuchFieldException, IllegalAccessException {
        /*返回guess已声明字段answer*/
        Field f = guess.getClass().getDeclaredField("answer");
        /*值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查*/
        f.setAccessible(true);
        /*将指f对象表示的字段answer设置为指定的值,即1234。*/
        f.set(guess, 1234);
    }

    @Test
    public void test_4A0B() {
        when(mockCompareNumber.compareToAnswer(1234, 1234)).thenReturn("4A0B");
        String result = guess.guessTheDigit(1234);
        assertTrue("4A0B".equals(result));
    }

    @Test
    public void test_0A4B() {
        when(mockCompareNumber.compareToAnswer(1234, 4321)).thenReturn("0A4B");
        String result = guess.guessTheDigit(4321);
        assertTrue("0A4B".equals(result));
    }

    @Test
    public void test_2A2B() {
        when(mockCompareNumber.compareToAnswer(1234, 1432)).thenReturn("2A2B");
        String result = guess.guessTheDigit(1432);
        assertTrue("2A2B".equals(result));
    }
}

说明
@Mock:创建一个mock对象(模拟对象)。
@InjectMock:创建一个实例,@Mock注解创建的模拟对象将被注入到该实例中。
@Before:Junit注解,在每个测试执行之前必须执行的代码。
@Test:Junit注解,标明是一个测试方法。

Junit4常用注解

  • @Before:初始化方法,在任何一个测试执行之前必须执行的代码。
  • @After:释放资源,在任何测试执行之后需要进行的收尾工作。
  • @Test:测试方法,表明这是一个测试方法。在Junit中将会自动被执行。
  • @Ignore:忽略的测试方法,含义为“某些方法尚未完成,暂不参与此次测试”。测试结果提示你有几个测试被忽略,而不是失败。
  • @BeforeClass:针对所有测试,在所有测试方法执行前执行一次。
  • @AfterClass:针对所有测试,在所有测试方法执行结束后执行一次。

在Junit4中,单元测试用例的执行顺序:


单元测试用例的执行顺序.png

每个测试方法的执行顺序:


测试方法的执行顺序.png

六.Mockito学习资料

Mockito官网:http://site.mockito.org/
Mockito官方文档:https://static.javadoc.io/org.mockito/mockito-core/2.13.0/org/mockito/Mockito.html
Mockito中文文档:http://blog.csdn.net/bboyfeiyu/article/details/52127551
Mockito Github:https://github.com/mockito/mockito

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容