如何使用Mock进行单元测试

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

为什么要使用Mock?

     Mock 测试就是在测试过程中创建一个假的对象避免你为了测试一个方法却要自行构建整个 Bean 的依赖链。

举个例子

    类 A 需要调用类 B 和类 C而类 B 和类 C 又需要调用其他类如 D、E、F 等假设类 D 是一个外部服务那就会很难测因为你的返回结果会直接的 受外部服务影响导致你的单元测试可能今天会过、但明天就过不了了。

    而当我们引入 Mock 测试时就可以创建一个假的对象替换掉真实的 Bean B 和 C这样在调用B、C的方法时实际上就会去调用这个假的 Mock 对象的方法而我们就可以自己设定这个 Mock 对象的参数和期望结果让我们可以专注在测试当前的类 A而不会受到其他的外部服务影响这样测试效率就能提高很多。

     比如你现在想要测试一个方法是否是正常的但是这个方法中有很多调用数据库的代码那么我们就可以在每个调用数据库的地方打桩模拟一下访问完数据库之后的返回值这样我们就可以在测试的时候避免访问数据库了可以非常高效地完成我们的单元测试已达到验证我们写的方法到底对不对的目的。

导入依赖

     如果你不想自己动手构建一套Mock解决方案市面上也提供了很多现存的Mock方案。

     常用的有EasyMock、Mockito 、WireMock、JMockit、Mock、Moco。

     在这里是使用Mockito做为本次演示使用。

<dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.8.47</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

使用Mock模拟测试某个类中的某个方法是否可以成功执行

     如果我们想要测试某个类中的某个方法是否可以执行我们不用直接调用这个类的这个方法我们可以模拟一下这个类让模拟的对象调用这个方法然后再去检验一下这个模拟对象是否成功的调用到了这个方法只要这个模拟对象成功调用到了这个方法那么就说明我们真实类中的这个方法是可以被成功执行的。这就是使用mockito来进行某个类的单元测试如下图

 /**
     * 通过mock模拟一个对象然后检验这个对象的某个方法是否可以执行
     * */
    @Test
    public void test1(){
        //模拟创建一个List对象
        List mock = mock(List.class);
        //使用mock模拟出来的List对象,让这个对象添加一个元素1,(我们其实是为了验证List类中的add方法是否正常)
        mock.add(1);
        //验证模拟的对象List的add(1)方法是否执行如果正常发生了说明List类中的add(1)方法是正常的。这样我们的单元测试就算通过
        //verify()的作用主要是检验我们模拟出来的这个对象中的方法是否成功执行如果成功执行控制台什么信息都没有如果没有成功
        //执行控制台会报错误信息
        verify(mock).add(1);
    }

使用Mock模拟某个类的方法自己给这个方法返回我们指定的值

     我们在测试一个控制器中的方法的时候这个控制器中肯定是有一些方法是需要访问数据库的但是我们自己在进行单元测试的时候其实不必访问数据库我们只需要知道访问数据库之后得到的这个值是什么所以我们就可以使用Mock来模拟出访问数据库的方法返回的值下面的这个例子就是我们自己给某个类中的方法直接指定一个返回值如下图

 /**
     * 模拟对象中的某个方法给这个方法指定一个返回值那么我们再执行这个模拟对象的方法的时候返回的值就不再是真实
     * 对象返回的值而应该是我们自己设置的返回值。
     * <p>
     * 比如我们这里有一个Iterator迭代器原本调用迭代器对象的next()方法之后返回的值是集合中的下一个元素我们这里来模拟
     * 这个方法的返回值模拟的是第一次调用next()方法返回值是"hello",第二次调用next()方法返回值是"world",第三次以及往后调用
     * next()方法返回值是"abc"
     * <p>
     * 使用到了when(),thenReturn()方法第一个thenReturn()代表第一次执行iterator.next()方法的返回值是"hello"
     * 第二个thenReturn()代表第二次执行iterator.next()方法的返回值是"world",
     * 第三个thenReturn()代表第三次即以后执行iterator.next()方法的返回值都是"abc"
     * <p>
     * 还是用到了assertEquals(猜想值,变量)断言方法
     */
    @Test
    public void test2() {
        //使用mock模拟出一个Iterator类
        Iterator iterator = mock(Iterator.class);

        //自己设置迭代器对象方法next()的返回值
        when(iterator.next()).thenReturn("hello").thenReturn("world").thenReturn("abc");

        //使用mock模拟的iterator对象去看看iterator调用next()方法之后的返回值是否是我们想的那样
        String result = iterator.next() + " " + iterator.next() + " " + iterator.next() + " " + iterator.next();

        //使用断言验证猜想的结果是否正确
        assertEquals("hello world abc abc", result);
    }

使用Mock模拟某个方法调用后会抛出指定的异常

  /**
     * 使用Mock模拟对象规定某个方法要抛出一个异常
     * */
    @Test(expected = IOException.class)
    public void test3() throws IOException {

        OutputStream outputStream = mock(OutputStream.class);

        //我们自己规定当执行OutputStream对象的close()方法的时候会主动的抛出一个IOException异常
        doThrow(new IOException()).when(outputStream).close();

        outputStream.close();
    }

什么叫做打桩以及什么情况下需要打桩什么情况下不需要打桩

     打桩其实就是在真实代码的地方用一个模拟方法代替然后真实方法执行到这个地方的时候它的返回值是我们模拟的返回值。when().thenReturn(),用这句代码我们可以自己给某个方法设定返回值这就叫做打桩。

在什么时候需要打桩呢如果我们想要自测的方法有返回值我们需要打桩。

在现实开发中遇到的问题

     我们大部分在开发的生活都会遇到这种情况在一个开发中需要去调用外部的服务但是由于外部需要远程调用或者还未开发完毕这个时候为了能正常开发或者单元测试通过。我们需要进行mock这个服务。

例如以下案例

一个学生的信息需要调用其他外部服务但是由于外部服务还未开发好因此先使用Mock进行测试开发。


//邮件服务
public interface MailService {
    public String getMail();
}
//地址服务
public interface AddressService {
    public String getAddress();
}

//获取信息汇总
public class StudentManager {
    @Resource
    private MailService mailService;
    @Resource
    private AddressService addressService;

    public String getStudentInfo(){
       return mailService.getMail()+addressService.getAddress();
    }
}

最后测试

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class StudentManagerTest{

    @InjectMocks
    private StudentManager studentManager;

    @Mock
    private AddressService addressService;

    @Mock
    private MailService mailService;

    @Test
    public void testGetStudentInfo() {
        Mockito.when(addressService.getAddress()).thenReturn("杭州市西湖区");
        Mockito.when(mailService.getMail()).thenReturn("123456@163.com");
        String studentInfo = studentManager.getStudentInfo();
        Assert.assertEquals("123456@163.com杭州市西湖区",studentInfo);
    }
}

 

使用方法可以参考mockito中文文档

hehonghui/mockito-doc-zh: Mockito框架中文文档 (github.com)。

欢迎关注微信公众号Java的学习之路

里面资料非常全从java初级到高级都有视频电子书面试宝典简历模板经典案例源码分析程序员故事以及解决bug方法。。。。应有尽有可以推荐大家一起学习下

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6