使用JUnit4与JMockit进行打桩测试

发表于:2018-3-26 10:37

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:kingsleylam    来源:博客园

  1. 何为Mock
  项目中各个模块,各个类之间会有互相依赖的关系,在单元测试中,我们只关心被测试的单元,对于其依赖的单元并不关心(会有另外针对该单元的测试)。
  比如,逻辑层A类依赖了数据访问层B类的取数方法,然后进行逻辑处理。在对A的单元测试中,我们关注的是在B返回不同的查询结果的时候,A是怎么处理的,而不是B到底是怎么取的数,如何封装成一个模型等等。
  因此,要屏蔽掉这些外部依赖,而Mock让我们有了一套仿真的环境。
  目前业界有几种Mock,这里选用最全面的JMockit进行总结。
  2. JMockit简介
  JMockit的工作原理是通过asm修改原有class的字节码,再利用jdk的instrument机制替换现有class的内容,从而达到mock的目的。
  这里使用的JMockit是1.21版本,具体使用方法可能与其他版本的不一样,但思想是相通的。Maven 配置如下:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
  JMockit有两种测试方式,一种是基于行为的,一种是基于状态的测试。
  1) Behavior-oriented(Expectations & Verifications)
  2)State-oriented(MockUp<GenericType>)
  通俗点讲,Behavior-oriented是基于行为的mock,对mock目标代码的行为进行模仿,更像黑盒测试。State-oriented 是基于状态的mock,是站在目标测试代码内部的。可以对传入的参数进行检查、匹配,才返回某些结果,类似白盒。而State-oriented的 new MockUp基本上可以mock任何代码或逻辑。
  假设现在有两个类,Service和DAO.  Service通过数据库查询出不同分组货物的数量,得到货物是否畅销。
1 package com.khlin.test.junit.jmockit.demo;
2
3 public class Service {
4
5     private DAO dao;
6
7     public void setDao(DAO dao) {
8         this.dao = dao;
9     }
10
11     /**
12      * 根据存货量判断货物是否畅销
13      * @param group
14      * @return
15      */
16     public Status checkStatus(String group) {
17         int count = this.dao.getStoreCount(group);
18
19         if (count <= 0) {
20             return Status.UNKOWN;
21         } else if (count <= 800) {
22             return Status.UNSALABLE;
23         } else if (count <= 1000) {
24             return Status.NORMAL;
25         } else {
26             return Status.SELLINGWELL;
27         }
28     }
29 }
1 package com.khlin.test.junit.jmockit.demo;
2
3 import java.util.HashMap;
4 import java.util.Map;
5
6 public class DAO {
7
8     private Map<String, Integer> groupCounts = new HashMap<String, Integer>();
9
10     /**
11      * 假数据
12      */
13     {
14         this.groupCounts.put("A", 500);
15         this.groupCounts.put("B", 1000);
16         this.groupCounts.put("C", 1200);
17     }
18
19     public int getStoreCount(String group) {
20         Integer count = this.groupCounts.get(group);
21
22         return null == count ? -1 : count.intValue();
23     }
24 }
1 package com.khlin.test.junit.jmockit.demo;
2
3 public enum Status {
4
5     /**
6      * 畅销
7      */
8     SELLINGWELL,
9     /**
10      * 一般
11      */
12     NORMAL,
13     /**
14      * 滞销
15      */
16     UNSALABLE,
17
18     /**
19      * 状态未知
20      */
21     UNKOWN
22 }
  基于行为的Mock 测试,一共三个阶段:record、replay、verify。
  1)record:在这个阶段,各种在实际执行中期望被调用的方法都会被录制。
  2)repaly:在这个阶段,执行单元测试Case,原先在record 阶段被录制的调用都可能有机会被执行到。这里有“有可能”强调了并不是录制了就一定会严格执行。
  3)verify:在这个阶段,断言测试的执行结果或者其他是否是原来期望的那样。
  假设现在我只想测试Service,在存货量900件的情况下,是否能正确返回NORMAL的状态。那么,我并不关心传入DAO的到底是哪个分组,也不关心DAO怎么去数据库取数,我只想让DAO返回900,这样就可以测试Service了。
  示例代码:
1 @RunWith(JMockit.class)
2 public class ServiceBehavier {
3
4     @Mocked
5     DAO dao = new DAO();
6
7     private Service service = new Service();
8
9     @Test
10     public void test() {
11
12         // 1. record 录制期望值
13         new NonStrictExpectations() {
14             {
15                 /**
16                  * 录制的方法
17                  */
18                 dao.getStoreCount(anyString);// mock这个方法,无论传入任何String类型的值,都返回同样的值,达到黑盒的效果
19                 /**
20                  * 预期结果,返回900
21                  */
22                 result = 900;
23                 /**
24                 times必须调用两次。在Expectations中,必须调用,否则会报错,因此不需要作校验。
25                 在NonStrictExpectations中不强制要求,但要进行verify验证.但似乎已经强制要求了
26                 此外还有maxTimes,minTimes
27                 */
28                 times = 1;
29             }
30         };
31         service.setDao(dao);
32
33         // 2. replay 调用
34         Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
35
36 //        Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
37
38          //3.校验是否只调用了一次。如果上面注释的语句再调一次,且把录制的times改为2,那么在验证阶段将会报错。
39         new Verifications() {
40             {
41                 dao.getStoreCount(anyString);
42                 times = 1;
43             }
44         };
45
46     }
47 }
  基于状态的Mock测试
  通过MockUp类,直接改写了mock类的代码逻辑,有点类似白盒测试
1 public class ServiceState {
2
3     private DAO dao;
4
5     private Service service;
6
7     @Test
8     public void test() {
9
10         //1. mock对象
11         MockUp<DAO> mockUp = new MockUp<DAO>() {
12
13             @Mock
14             public int getStoreCount(String group) {
15                 return 2000;
16             }
17         };
18
19         //2. 获取实例
20         dao = mockUp.getMockInstance();
21         service = new Service();
22         service.setDao(dao);
23
24         //3.调用
25         Assert.assertEquals(Status.SELLINGWELL, service.checkStatus("FFF"));
26
27         //4. 还原对象,避免测试方法之间互相影响。其实对一个实例来说没什么影响,对静态方法影响较大。旧版本的tearDown()方法是Mockit类的静态方法
28         mockUp.tearDown();
29     }
30 }
  3. JMockit mock各种类型或方法的示例代码
  抽象类
1 package com.khlin.test.junit.jmockit.demo.jmockit;
2
3 public abstract class AbstractA {
4
5     public abstract int getAbstractAnything();
6
7     public int getAnything() {
8         return 1;
9     }
10 }
  接口类
1 package com.khlin.test.junit.jmockit.demo.jmockit;
2
3 public interface InterfaceB {
4
5     public int getAnything();
6 }
  普通类
1 package com.khlin.test.junit.jmockit.demo.jmockit;
2
3 public class ClassA {
4
5     InterfaceB interfaceB;
6
7     private int number;
8
9     public void setInterfaceB(InterfaceB interfaceB) {
10         this.interfaceB = interfaceB;
11     }
12
13     public int getAnything() {
14         return getAnythingPrivate();
15     }
16
17     private int getAnythingPrivate() {
18         return 1;
19     }
20
21     public int getNumber() {
22         return number;
23     }
24
25
26
27     public static int getStaticAnything(){
28         return getStaticAnythingPrivate();
29     }
30
31     private static int getStaticAnythingPrivate() {
32         return 1;
33     }
34
35     public int getClassBAnything() {
36         return this.interfaceB.getAnything();
37     }
38 }
  接口实现类
1 package com.khlin.test.junit.jmockit.demo.jmockit;
2
3 public class ClassB implements InterfaceB {
4
5     public int getAnything() {
6         return 10;
7     }
8
9 }
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号