返回顶部

OutOfMemory.CN技术专栏-> Java-> java单元测试之:jmockit mock静态方法,mock依赖类

java单元测试之:jmockit mock静态方法,mock依赖类

更多

在介绍实际的例子之前,然我们先了解下什么是mock? 为什么mock?

mock的中文含义是假装模仿, 在单元测试里面我们测试的是某个单元的逻辑,即某个方法内的执行结果是否符合我们的预期。有一些方法会依赖于第三方的包,例如在service方法中我们有可能会去调用数据库的执行结果,会取redis中缓存数据,也有可能会使用当前的系统时间,根据系统时间做一些逻辑处理。虽然方法的逻辑依赖于第三方的东西,但是我们的单元测试却不能依赖于第三方的东西,你不可能用单元测试去测试数据库是不是可靠的,数据库的可靠性不是单元测试的目的, 这时候我们就要模仿数据库等第三方包的行为,让这些第三方包返回我们想要的东西,从而将依赖关系简单化,只测试我们自己的逻辑部分。

java的mock框架很多,最强大的是jmockit,本文就介绍jmockit的mock使用。

我们先介绍一下静态方法的mock,假定我们有这么一个方法需要测试:

import java.util.Calendar;
import java.util.Date;

/**
 * 任务服务
 * Created by outofmemory.cn on 2015/10/28.
 */
public class TaskService {

    private static final int YESTERDAY_TASK_LIMIT_HOUR = 8;

    /**
     * 根据任务创建的时间判断任务是否可以执行
     *
     * 如果任务是当天创建的允许执行, 如果是昨天的任务,允许在第二天的早上YESTERDAY_TASK_LIMIT_HOUR点前执行
     * @param taskCreateTime 任务创建时间
     * @return true 任务可以执行, false 任务已过期不可以执行
     */
    public boolean canExecute(Date taskCreateTime) {
        Date now = new Date();
        Calendar nowCalendar = Calendar.getInstance();
        nowCalendar.setTime(now);

        Calendar createTimeCalendar = Calendar.getInstance();
        createTimeCalendar.setTime(taskCreateTime);
        boolean isToday = isSameDay(taskCreateTime, now);

        //如果是当天任务允许执行
        if (isToday) {
            return true;
        }

        nowCalendar.add(Calendar.DATE, -1);
        Date yesterday = nowCalendar.getTime();
        boolean isYesterday = isSameDay(taskCreateTime, yesterday);

        //如果是昨天任务,只允许在8点前执行
        if (isYesterday) {
            return nowCalendar.get(Calendar.HOUR_OF_DAY) < YESTERDAY_TASK_LIMIT_HOUR;
        }

        return false;
    }

    /**
     * 判断两个日期是否为同一天
     * @param date1 date1
     * @param date2 date2
     * @return 是同一天返回true, 否则为false
     */
    private boolean isSameDay(Date date1, Date date2) {
        Calendar calendar1 = Calendar.getInstance();
        calendar1.setTime(date1);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.setTime(date2);
        return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
                && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH)
                && calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
    }
}

canExecute方法的逻辑依赖于当前的时间, 当任务的创建时间和now是同一天时,任务可以执行,当任务的创建时间是昨天时, 需要在今天的8点之前执行。 这个方法的测试也需要依赖于当前时间,所以我们要mock当前时间。

java中当前时间是根据System.currentTimeMillis()的返回值设定的, 要mock当前的时间需要mock System类的这个方法。具体的mock操作请看代码,请注意注释:

import mockit.Expectations;
import mockit.Mocked;
import mockit.integration.junit4.JMockit;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Calendar;
import java.util.Date;

/**
 * TaskService 测试类, 注意此处需要添加@RunWith(JMockit.class), 否则jmockit无法做mock操作
 *
 * Created by outofmemory.cn on 2015/10/28.
 */
@RunWith(JMockit.class)
public class TaskServiceTest {
    /**
     * 要mock当前时间需要mock System类,这里声明一个System类型的字段,并添加@Mocked注解,表示要对此类做mock
     */
    @Mocked
    @SuppressWarnings({"UnusedDeclaration"})
    private System system;

    @Test
    public void canExecuteTest() {
        final Calendar cal = Calendar.getInstance();
        cal.set(2015, Calendar.NOVEMBER, 28, 10, 10, 0);

        //mock
        new Expectations(){{
            //指定要mock的方法
            System.currentTimeMillis();

            //指定mock方法要返回的结果
            result =  cal.getTime().getTime();
        }};

        cal.set(2015, Calendar.NOVEMBER, 28, 15, 0, 0);
        Date createTime = cal.getTime();
        //执行方法逻辑
        TaskService taskService = new TaskService();
        boolean canExecute = taskService.canExecute(createTime);

        //验证是否符合预期
        Assert.assertTrue(canExecute);
    }
}

上述例子是对System类方法的mock,下面我们看一个更接近实际的例子,我们有一个Service类,该Service类引用了另外一个Service, 我们要mock被引用service的方法。

被测试的类如下, ServiceUseRedis,他引用了RedisService。

/**
 * ServiceUseRedis 被测试类,该类引用了redisService, redisService在测试中将被mock
 *
 * Created by outofmemory.cn on 2015/10/26.
 */
public class ServiceUseRedis {
    private RedisService redisService;

    public boolean doSomethingReturnBoolean(String someArg) {
        String somethingInRedis = redisService.get(someArg);
        return someArg.equals(somethingInRedis);
    }
}

RedisService类如下: 

/**
 * Created by outofmmeory.cn on 2015/10/26.
 */
public class RedisService {
    public String get(String key) {
        throw new RuntimeException("I'm redis service, you can not call me in unit test");
    }
}

处于测试目的,这个类只有一个方法get(String),方法直接抛出一个运行时异常, 而我们在测试时会mock此方法,让此方法返回我们需要的结果。

测试类如下:

import mockit.Expectations;
import mockit.Injectable;
import mockit.Mocked;
import mockit.Tested;
import mockit.integration.junit4.JMockit;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;


/**
 * 测试一个类引用另外一个类, 被引用类的方法要做mock
 *
 * Created by outofmemory.cn on 2015/10/26.
 */
@RunWith(JMockit.class)
public class ServiceUseRedisTest {

    /**
     * 被引用类, 被mock
     */
    @Mocked
    @Injectable
    RedisService redisService;


    /**
     * 要测试的类
     */
    @Tested
    ServiceUseRedis serviceTested;

    @Test
    public void doSomethingReturnBooleanTest() {
        // 设置mock期望
        new Expectations(){{
            redisService.get("abc");
            result = "abc";
        }};

        //执行service方法
        boolean doResult = serviceTested.doSomethingReturnBoolean("abc");

        //验证执行结果
        Assert.assertTrue(doResult);
    }
}

 我们可以运行测试,测试通过,说明redisService的get方法已经成功的被我们mock掉,返回了我们想要的abc,而不是抛出运行时异常。

最后提供下jmockit需要的maven依赖:

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.20</version>
            <scope>test</scope>
        </dependency>

 jmockit是一个非常优秀的mock框架,可以很方便的帮我们隔离单元测试的依赖逻辑。 单元测试的核心思想是单元,每一个单元测试都只测试自己关注的逻辑,mock掉依赖才可以更好的测试单元逻辑。

推荐阅读:
支持

1

反对

0

发表评论