单元测试

单元测试的目的是保证当前方法正常执行,对软件中的最小可测试单元进行检查和验证,不同于集成测试是将所有模块按照设计要求进行测试,所以工作中单测一般是对业务逻辑(service层)进行测试。

什么是Mock?

在单元测试中,我们往往想去独立地去测一个类中的某个方法,但是这个类可不是独立的,它会去调用一些其它类的方法、dao、service等,这也就导致了以下两个问题:
  • 外部服务可能无法在单元测试的环境中正常工作,因为它们可能需要访问数据库或者使用一些其它的外部系统。
  • 我们的测试关注点在于这个类的实现上,外部类的一些行为可能会影响到我们对本类的测试,那也就失去了我们进行单测的意义。
 
这个时候Mockito和PowerMock诞生了,这两种工具都可以用一个虚拟的对象来替换那些外部的、不容易构造的对象,便于我们进行单元测试。两者的不同点在于Mockito对于大多数的标准单测case都很适用,而PowerMock可以去解决一些更难的问题(比如静态方法、私有方法等)
 
Mock生成的是一个代理对象,默认情况下,执行对象的所有的方法都返回该方法的返回类型的默认值,不会真正去执行该对象的方法。既然这样,那我们在测试中如何使用这个mock出来的对象,来执行方法进行测试呢?这就需要使用到Mockito的Stub(测试桩)来设置Mock对象方法的返回值了。
 

Mockito

Mockito是一个java测试框架,它提供mock的创建,验证,打桩。在测试中,可能会依赖外部资源,比如数据库等,mockito可以模拟这些数据,进行测试。
 
比如一个类A,有B、C、D等多个复杂类的成员变量。如果我们测试时候通过new A()的方式来创建A对象,就需要同时手动创建B、C、D三个对象,并和A关联,提高了测试的复杂性。Mock可以自动生成一个虚拟的A对象,就是帮我们省去了这些复杂的创建流程。
参见代码:MyToolService1Test.java、MyFirewallServiceTest.java
 
package cn.mucang.smart.advert.ops.service;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * @author xu
 * @Date 2021/6/17 18:32
 */
@Service
public class MyToolService {
    String name;

    public String findById(int id) {
        return "123";
    }


    public List<String> findAll(int id) {

        return Lists.newArrayList("1", "2", "3");
    }

    public int getIdValue(String str) {
        if (!checkIds(str)) {
            return 0;
        }

        int id = Integer.parseInt(str);

        if (checkIdRange(id)) {
            return id;
        }
        return 0;
    }

    private boolean checkIds(String ids) {
        if (StringUtils.isBlank(ids)) {
            return false;
        }
        if (ids.split(",").length > 1) {
            return true;
        }
        return false;
    }

    public static boolean checkIdRange(int id) {
        if (id > 100 || id < 0) {
            return false;
        }
        return true;
    }

    private final boolean finalMethod(String ids) {
        if (StringUtils.isBlank(ids)) {
            return false;
        }

        return ids.contains("-");
    }

    public String convertStr(String str) {
        if (finalMethod(str)) {
            return str.toUpperCase();
        }
        return str.toLowerCase();
    }

    public void quantity(int count) {

        List<String> arrayList = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            arrayList.add("v-" + i);
        }

        System.out.println(Joiner.on(",").join(arrayList));
    }


    public void execQuantity(String ids) {
        if (checkIds(ids)) {
            dataLog(ids);
            return;
        }

        name = "ABC";
        System.out.println(name);
    }

    private void dataLog(String ids) {
        System.out.println(ids + ",data log....");
    }


    private String checkPrivate(String str) {
        if (checkIds(str)) {
            str = "OK";
            return str;
        }

        str = "NO";
        return str;
    }

}
View Code
import cn.mucang.smart.advert.ops.service.MyToolService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

/**
 * @author xu
 * @Date 2021/6/28 14:59
 */

@RunWith(MockitoJUnitRunner.class)
public class MyToolService1Test {

    //  Mockito是一个java测试框架,它提供mock的创建,验证,打桩。在测试中,可能会依赖外部资源,比如数据库等,mockito可以模拟这些数据,进行测试
    //  单元测试的目的是保证当前方法正常执行

    //Mockito提供了两种Mock的方式:1、注解2、实时调Mockito函数生成

    @Mock //注解的方式生成的是一个代理对象
    private MyToolService myToolService;


    //Mock 和 Spy 生成类区别
    //    Mock:生成的类,所有方法都不是真实的方法,而且返回值都是NULL; 通过when 指定行为。
    //    Spy:生成的类,所有方法都是真实方法,返回值都是和真实方法一样的;通过when 来将某个真实的方法,替换为指定行为的执行


    @Test
    public void findByIdTest() {
        //Mock生成的是一个代理对象,默认情况下,执行对象的所有的方法都返回该方法的返回类型的默认值,不会真正去执行该对象的方法。
        System.out.println(myToolService.findById(1));

        //Mockito.mock函数生成
        MyToolService mock = Mockito.mock(MyToolService.class);
        System.out.println(mock.findById(1));

    }


    @Test
    public void findAllTest() {
        System.out.println(myToolService.findAll(1));
    }

    //如果想让mock出来的对象去执行真实的方法,那就得用另一种方式了,spy
    @Test
    public void quantityTest() {
        //spy会去真实的执行对象的方法
        MyToolService myToolService = Mockito.spy(new MyToolService());

        myToolService.quantity(5);
    }

}
View Code
package cn.mucang.smart.advert.ops.service;

import cn.mucang.simple.db.condition.SqlPath;
import cn.mucang.smart.advert.ops.common.utils.CollectionUtils;
import cn.mucang.smart.advert.ops.dao.FirewallEntityDao;
import cn.mucang.smart.advert.ops.entity.FirewallEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author xu
 * @Date 2021/7/1 15:01
 */
@Service
public class MyFirewallService {

    @Autowired
    private FirewallEntityDao firewallEntityDao;

    public List<FirewallEntity> filterByStatus() {
        SqlPath sqlPath = SqlPath.orderBy();
        sqlPath.asc(FirewallEntity.Fields.id);
        List<FirewallEntity> firewallEntityList = firewallEntityDao.findAll(sqlPath);

        if (CollectionUtils.isEmpty(firewallEntityList)) {
            return Collections.emptyList();
        }

        return firewallEntityList.stream().filter(x -> x.getStatus() > 0).collect(Collectors.toList());
    }


    public List<FirewallEntity> filterByStatus1() {
        SqlPath sqlPath = SqlPath.orderBy();
        sqlPath.asc(FirewallEntity.Fields.id);
        List<FirewallEntity> firewallEntityList = firewallEntityDao.findAll(sqlPath);

        if (CollectionUtils.isEmpty(firewallEntityList)) {
            return Collections.emptyList();
        }

        return firewallEntityList.stream().filter(x -> x.getStatus() > 0).collect(Collectors.toList());
    }
}
View Code
import cn.mucang.simple.db.condition.SqlPath;
import cn.mucang.smart.advert.ops.dao.FirewallEntityDao;
import cn.mucang.smart.advert.ops.entity.FirewallEntity;
import cn.mucang.smart.advert.ops.service.MyFirewallService;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
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.junit.MockitoJUnitRunner;

import java.util.Collections;
import java.util.List;

/**
 * @author xu
 * @Date 2021/7/1 15:09
 */
@RunWith(MockitoJUnitRunner.class)
public class MyFirewallServiceTest {


    @Mock
    private FirewallEntityDao firewallEntityDao;


    //创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
    @InjectMocks
    private MyFirewallService myFirewallService;


    @Test
    public void filterByStatus_empty_Test() {
        //打桩
        //mock出firewallEntityDao.findAll返回空的结果,
        Mockito.when(firewallEntityDao.findAll(Mockito.any(SqlPath.class))).thenReturn(Collections.emptyList());
        List<FirewallEntity> firewallEntityList = myFirewallService.filterByStatus();

        Assert.assertTrue(CollectionUtils.isEmpty(firewallEntityList));
    }


    @Test
    public void filterByStatus_Test() {
        //mock出构造的数据
        Mockito.when(firewallEntityDao.findAll(Mockito.any(SqlPath.class))).thenReturn(generateFirewallData());
        List<FirewallEntity> firewallEntityList = myFirewallService.filterByStatus();

        Assert.assertTrue(firewallEntityList.get(0).getId() == 1);
    }


    private List<FirewallEntity> generateFirewallData() {

        FirewallEntity firewallEntity = new FirewallEntity();
        firewallEntity.setId(1L);
        firewallEntity.setStatus(1);

        FirewallEntity firewallEntity1 = new FirewallEntity();
        firewallEntity1.setId(2L);
        firewallEntity1.setStatus(0);

        return Lists.newArrayList(firewallEntity, firewallEntity1);
    }
}
View Code

 

PowerMock
工作直接用powerMock就可以解决所有问题了,即可用mockito也可用powermockito。
有关powermocktito的说明
https://github.com/powermock/powermock/wiki/mockito#introduction 
 
PowerMock是Java开发中的一种Mock框架,用于单元模块测试
PowerMock可以实现完成对private/static/final方法的Mock(模拟),而Mockito可以对普通的方法进行Mock,如:public等。
PowerMock基本上包含了所有Mockito不能支持的case(大多数情况也就是静态方法,但其实也可以支持私有方法和构造函数的调用)。PowerMock使用了字节码操作,因此它是自带Junit runner的。在使用PowerMock时,必须使用@PrepareForTest注释被测类,mock才会被执行:
 
PowerMock基于Mockito开发,起语法规则与Mockito一致,主要区别在于使用方面,以实现完成对private/static/final等方法(也支持mock的对象是在方法内部new出来的)的Mock(模拟)
参见代码:MyToolServiceTest.java
 
import cn.mucang.smart.advert.ops.service.MyToolService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author xu
 * @Date 2021/6/28 14:59
 */

@RunWith(PowerMockRunner.class)  //告诉JUnit使用PowerMockRunner进行测试
@PrepareForTest({MyToolService.class}) //所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
public class MyToolServiceTest {


    //powermockito private方法
    @Test
    public void getEntity_getIdValue_return_false_test() throws Exception {
        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了false
        PowerMockito.doReturn(false).when(myToolService, "checkIds", Mockito.anyString());

        int idValue = myToolService.getIdValue(Mockito.anyString());

        Assert.assertEquals(0, idValue);
    }

    //powerMockito private方法
    @Test
    public void getEntity_getIdValue_return_true_test() throws Exception {
        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(true).when(myToolService, "checkIds", Mockito.anyString());

        int idValue = myToolService.getIdValue("2");

        Assert.assertEquals(2, idValue);
    }


    //powerMockito 执行static方法
    @Test
    public void getEntity_check_id_range_return_false_test() throws Exception {
        PowerMockito.mockStatic(MyToolService.class);

        //没有去真实的执行静态的checkIdRange,但是按我们的要求,直接返回了false
        Mockito.when(MyToolService.checkIdRange(Mockito.anyInt())).thenReturn(false);

        MyToolService myToolService = PowerMockito.spy(new MyToolService());
        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(true).when(myToolService, "checkIds", Mockito.anyString());

        int idValue = myToolService.getIdValue("2");

        Assert.assertEquals(0, idValue);
    }

    @Test
    public void finalMethodTest() throws Exception {
        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        //没有去真实的执行私有的finalMethods,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(true).when(myToolService, "finalMethod", Mockito.anyString());

        String result = myToolService.convertStr("abc");

        Assert.assertEquals(result, "ABC");

    }


    @Test
    public void finalMethod_lower_Test() throws Exception {
        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        //没有去真实的执行私有的finalMethods,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(false).when(myToolService, "finalMethod", Mockito.anyString());

        String result = myToolService.convertStr("abc");

        Assert.assertEquals(result, "abc");
    }


    //对于一些没有返回值 的方法,如何判断
    //判断某个方法执行过了,执行了几次
    //判断类成员变量是否做了更改。
    @Test
    public void quantityTest() throws Exception {
        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(true).when(myToolService, "checkIds", Mockito.anyString());

        myToolService.execQuantity(Mockito.anyString());

        //断言 dataLog 被执行一次
        PowerMockito.verifyPrivate(myToolService, Mockito.times(1)).invoke("dataLog", Mockito.anyString());


        //重新打桩
        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了false
        PowerMockito.doReturn(false).when(myToolService, "checkIds", Mockito.anyString());

        //再次执行
        myToolService.execQuantity(Mockito.anyString());

        //白盒,获取修改的成员变量
        String newStr = (String) Whitebox.getField(MyToolService.class, "name").get(myToolService);

        Assert.assertTrue("ABC".equals(newStr));
    }

    //查找私有方法,并执行之
    @Test
    public void privateTest() throws InvocationTargetException, IllegalAccessException {

        MyToolService myToolService = PowerMockito.spy(new MyToolService());
        Method checkIds = PowerMockito.method(MyToolService.class, "checkIds", String.class);

        Object invoke = checkIds.invoke(myToolService, "121,363");
        System.out.println(invoke);
    }

    //private String checkPrivate(String str) {

    //私有方法嵌套,则mock之
    //查找私有方法,并执行之
    @Test
    public void privateTest1() throws Exception {

        MyToolService myToolService = PowerMockito.spy(new MyToolService());

        Method checkIds = PowerMockito.method(MyToolService.class, "checkPrivate", String.class);

        //没有去真实的执行私有的checkIds,但是按我们的要求,直接返回了true
        PowerMockito.doReturn(true).when(myToolService, "checkIds", Mockito.anyString());

        Object invoke = checkIds.invoke(myToolService, "121");
        System.out.println(invoke);
    }

    //关于抽象类的模拟,注意第二个参数
    //
    //mSpyAbstractActivity =  Mockito.mock(AbstractActivity.class, Mockito.CALLS_REAL_METHODS)

}
View Code

 

 

Mockito 和 PowerMock的实现原理

  • Mockito:通过代理(bytebuddy动态生成匿名子类)实现类功能的模拟。
  • PowerMock:通过修改字节码实现类功能的模拟。
 

maven引用方式:

<powermock.version>2.0.9</powermock.version>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
 
 
 
 
 
解决报PowerMockito.mockStatic Argument should be a mock, but is: class java.lang.Class的异常,是版本不兼容导致,可强制用下方版本解决。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
</dependency>
posted on 2021-07-20 15:28  xuxu_dragon  阅读(1784)  评论(0)    收藏  举报