手记

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掉依赖才可以更好的测试单元逻辑。

原文链接:http://outofmemory.cn/java/jmockit-mock-anything

1人推荐
随时随地看视频
慕课网APP