zoukankan      html  css  js  c++  java
  • 使用JUnit4与JMockit进行打桩测试

    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 }
    View Code

    接口类

    1 package com.khlin.test.junit.jmockit.demo.jmockit;
    2 
    3 public interface InterfaceB {
    4     
    5     public int getAnything();
    6 }
    View Code

    普通类

     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 }
    View Code

    接口实现类

    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 }
    View Code

    终极测试代码

      1 package com.khlin.test.junit.jmockit.demo;
      2 
      3 import mockit.Deencapsulation;
      4 import mockit.Expectations;
      5 import mockit.Injectable;
      6 import mockit.Mock;
      7 import mockit.MockUp;
      8 import mockit.Mocked;
      9 import mockit.NonStrictExpectations;
     10 import mockit.Tested;
     11 import mockit.Verifications;
     12 import mockit.integration.junit4.JMockit;
     13 
     14 import org.junit.Assert;
     15 import org.junit.Test;
     16 import org.junit.runner.RunWith;
     17 
     18 import com.khlin.test.junit.jmockit.demo.jmockit.AbstractA;
     19 import com.khlin.test.junit.jmockit.demo.jmockit.ClassA;
     20 import com.khlin.test.junit.jmockit.demo.jmockit.ClassB;
     21 import com.khlin.test.junit.jmockit.demo.jmockit.InterfaceB;
     22 
     23 @RunWith(JMockit.class)
     24 public class JMockitTest {
     25 
     26     /**
     27      * mock私有方法
     28      */
     29     @Test
     30     public void testPrivateMethod() {
     31 
     32         final ClassA a = new ClassA();
     33         // 局部参数,把a传进去
     34         new NonStrictExpectations(a) {
     35             {
     36                 Deencapsulation.invoke(a, "getAnythingPrivate");
     37                 result = 100;
     38                 times = 1;
     39             }
     40         };
     41 
     42         Assert.assertEquals(100, a.getAnything());
     43 
     44         new Verifications() {
     45             {
     46                 Deencapsulation.invoke(a, "getAnythingPrivate");
     47                 times = 1;
     48             }
     49         };
     50     }
     51 
     52     /**
     53      * mock私有静态方法
     54      */
     55     @Test
     56     public void testPrivateStaticMethod() {
     57 
     58         new NonStrictExpectations(ClassA.class) {
     59             {
     60                 Deencapsulation
     61                         .invoke(ClassA.class, "getStaticAnythingPrivate");
     62                 result = 100;
     63                 times = 1;
     64             }
     65         };
     66 
     67         Assert.assertEquals(100, ClassA.getStaticAnything());
     68 
     69         new Verifications() {
     70             {
     71                 Deencapsulation
     72                         .invoke(ClassA.class, "getStaticAnythingPrivate");
     73                 times = 1;
     74             }
     75         };
     76 
     77     }
     78 
     79     /**
     80      * mock公有方法
     81      */
     82     @Test
     83     public void testPublicMethod() {
     84         final ClassA classA = new ClassA();
     85         new NonStrictExpectations(classA) {
     86             {
     87                 classA.getAnything();
     88                 result = 100;
     89                 times = 1;
     90             }
     91         };
     92 
     93         Assert.assertEquals(100, classA.getAnything());
     94 
     95         new Verifications() {
     96             {
     97                 classA.getAnything();
     98                 times = 1;
     99             }
    100         };
    101     }
    102 
    103     /**
    104      * mock公有静态方法--基于行为
    105      */
    106     @Test
    107     public void testPublicStaticMethod() {
    108 
    109         new NonStrictExpectations(ClassA.class) {
    110             {
    111                 ClassA.getStaticAnything();
    112                 result = 100;
    113                 times = 1;
    114             }
    115         };
    116 
    117         Assert.assertEquals(100, ClassA.getStaticAnything());
    118 
    119         new Verifications() {
    120             {
    121                 ClassA.getStaticAnything();
    122                 times = 1;
    123             }
    124         };
    125     }
    126     
    127     /**
    128      * mock公有静态方法--基于状态
    129      */
    130     @Test
    131     public void testPublicStaticMethodBaseOnStatus() {
    132 
    133         MockUp<ClassA> mockUp = new MockUp<ClassA>() {
    134             @Mock
    135             public int getStaticAnything() { //注意这里不用声明为static
    136                 return 100;
    137             }
    138         };
    139         
    140         Assert.assertEquals(100, ClassA.getStaticAnything());
    141     }
    142     
    143     /**
    144      * mock接口
    145      */
    146     @Test
    147     public void testInterface() {
    148         
    149         InterfaceB interfaceB = new MockUp<InterfaceB>() {
    150             @Mock
    151             public int getAnything() {
    152                 return 100;
    153             }
    154         }.getMockInstance();
    155         
    156         
    157         ClassA classA = new ClassA();
    158         classA.setInterfaceB(interfaceB);
    159         
    160         Assert.assertEquals(100, classA.getClassBAnything());
    161     }
    162     
    163     /**
    164      * mock接口--基于状态
    165      */
    166     @Test
    167     public void testInterfaceBasedOnStatus() {
    168         final InterfaceB interfaceB = new ClassB();
    169         
    170         new NonStrictExpectations(interfaceB) {
    171             {
    172                 interfaceB.getAnything();
    173                 result = 100;
    174                 times = 1;
    175             }
    176         };
    177         
    178         ClassA classA = new ClassA();
    179         classA.setInterfaceB(interfaceB);
    180         
    181         Assert.assertEquals(100, classA.getClassBAnything());
    182         
    183         new Verifications() {
    184             {
    185                 interfaceB.getAnything();
    186                 times = 1;
    187             }
    188         };
    189     }
    190     
    191     
    192     
    193     /**
    194      * mock抽象类
    195      */
    196     @Test
    197     public void testAbstract() {
    198         AbstractA abstractA = new MockUp<AbstractA>() {
    199             @Mock
    200             public int getAbstractAnything(){
    201                 return 100;
    202             }
    203             
    204             @Mock
    205             public int getAnything(){
    206                 return 1000;
    207             }
    208         }.getMockInstance();
    209         
    210         Assert.assertEquals(100, abstractA.getAbstractAnything());
    211         
    212         Assert.assertEquals(1000, abstractA.getAnything());
    213     }
    214 }
    View Code
  • 相关阅读:
    web service
    常用的正则表达式
    xml
    sql helper
    sql server 表连接
    asp.net页面生命周期
    创建简单的ajax对象
    checkbox选中问题
    ES6之扩展运算符 三个点(...)
    Object.assign()的用法 -- 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象
  • 原文地址:https://www.cnblogs.com/kingsleylam/p/5116802.html
Copyright © 2011-2022 走看看