• 使用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>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
    <dependency>
      <groupId>org.jmockit</groupId> 
      <artifactId>jmockit</artifactId> 
      <version>1.21</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
      <groupId>org.jacoco</groupId>
      <artifactId>org.jacoco.ant</artifactId>
      <version>0.8.4</version>
    </dependency>
    
    <dependency>
      <groupId>org.jacoco</groupId>
      <artifactId>org.jacoco.agent</artifactId>
      <version>0.8.4</version>
    </dependency>
    
    <dependency>
      <groupId>org.jacoco</groupId>
      <artifactId>org.jacoco.report</artifactId>
      <version>0.8.4</version>
    </dependency>
    <dependency>
      <groupId>org.jacoco</groupId>
      <artifactId>org.jacoco.core</artifactId>
      <version>0.8.4</version>
    </dependency>

    如果需要使用jmock1.0版本,则maven配置如下 新版去废除了一些功能(如@Mocked不能修饰成员)

    <dependency>
    
                <groupId>junit</groupId>
    
                <artifactId>junit</artifactId>
    
                <version>4.11</version>
    
                <scope>test</scope>
            </dependency>
            
            <dependency>
                <groupId>com.googlecode.jmockit</groupId>
                <artifactId>jmockit</artifactId>
                <version>1.0</version>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.jacoco</groupId>
                <artifactId>org.jacoco.ant</artifactId>
                <version>0.8.4</version>
            </dependency>
    
            <dependency>
                <groupId>org.jacoco</groupId>
                <artifactId>org.jacoco.agent</artifactId>
                <version>0.8.4</version>
            </dependency>
    
            <dependency>
                <groupId>org.jacoco</groupId>
                <artifactId>org.jacoco.report</artifactId>
                <version>0.8.4</version>
            </dependency>
            <dependency>
                <groupId>org.jacoco</groupId>
                <artifactId>org.jacoco.core</artifactId>
                <version>0.8.4</version>
            </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通过数据库查询出不同分组货物的数量,得到货物是否畅销。

    package com.khlin.test.junit.jmockit.demo;
    
    public class Service {
        
        private DAO dao;
        
        public void setDao(DAO dao) {
            this.dao = dao;
        }
        
        /**
         * 根据存货量判断货物是否畅销
         * @param group
         * @return
         */
        public Status checkStatus(String group) {
            int count = this.dao.getStoreCount(group);
    
            if (count <= 0) {
                return Status.UNKOWN;
            } else if (count <= 800) {
                return Status.UNSALABLE;
            } else if (count <= 1000) {
                return Status.NORMAL;
            } else {
                return Status.SELLINGWELL;
            }
        }
    }


    package com.khlin.test.junit.jmockit.demo;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class DAO {
    
        private Map<String, Integer> groupCounts = new HashMap<String, Integer>();
    
        /**
         * 假数据
         */
        {
            this.groupCounts.put("A", 500);
            this.groupCounts.put("B", 1000);
            this.groupCounts.put("C", 1200);
        }
    
        public int getStoreCount(String group) {
            Integer count = this.groupCounts.get(group);
    
            return null == count ? -1 : count.intValue();
        }
    }
    package com.khlin.test.junit.jmockit.demo;
    
    public enum Status {
    
        /**
         * 畅销
         */
        SELLINGWELL,
        /**
         * 一般
         */
        NORMAL,
        /**
         * 滞销
         */
        UNSALABLE,
        
        /**
         * 状态未知
         */
        UNKOWN
    }

    基于行为的Mock 测试,一共三个阶段:record、replay、verify。

    1)record:在这个阶段,各种在实际执行中期望被调用的方法都会被录制。

    2)repaly:在这个阶段,执行单元测试Case,原先在record 阶段被录制的调用都可能有机会被执行到。这里有“有可能”强调了并不是录制了就一定会严格执行。

    3)verify:在这个阶段,断言测试的执行结果或者其他是否是原来期望的那样。

    假设现在我只想测试Service,在存货量900件的情况下,是否能正确返回NORMAL的状态。那么,我并不关心传入DAO的到底是哪个分组,也不关心DAO怎么去数据库取数,我只想让DAO返回900,这样就可以测试Service了。

    示例代码:

    @RunWith(JMockit.class)
    public class ServiceBehavier {
    
        @Mocked
        DAO dao = new DAO();
    
        private Service service = new Service();
    
        @Test
        public void test() {
    
            // 1. record 录制期望值
            new NonStrictExpectations() {
                {
                    /**
                     * 录制的方法
                     */
                    dao.getStoreCount(anyString);// mock这个方法,无论传入任何String类型的值,都返回同样的值,达到黑盒的效果
                    /**
                     * 预期结果,返回900
                     */
                    result = 900;
                    /**
                    times必须调用两次。在Expectations中,必须调用,否则会报错,因此不需要作校验。
                    在NonStrictExpectations中不强制要求,但要进行verify验证.但似乎已经强制要求了
                    此外还有maxTimes,minTimes
                    */
                    times = 1;
                }
            };
            service.setDao(dao);
    
            // 2. replay 调用
            Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
    
    //        Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
    
             //3.校验是否只调用了一次。如果上面注释的语句再调一次,且把录制的times改为2,那么在验证阶段将会报错。
            new Verifications() {
                {
                    dao.getStoreCount(anyString);
                    times = 1;
                }
            };
    
        }
    }

    基于状态的Mock测试

    通过MockUp类,直接改写了mock类的代码逻辑,有点类似白盒测试。

    public class ServiceState {
    
        private DAO dao;
        
        private Service service;
    
        @Test
        public void test() {
            
            //1. mock对象
            MockUp<DAO> mockUp = new MockUp<DAO>() {
    
                @Mock
                public int getStoreCount(String group) {
                    return 2000;
                }
            };
            
            //2. 获取实例
            dao = mockUp.getMockInstance();
            service = new Service();
            service.setDao(dao);
            
            //3.调用
            Assert.assertEquals(Status.SELLINGWELL, service.checkStatus("FFF"));
            
            //4. 还原对象,避免测试方法之间互相影响。其实对一个实例来说没什么影响,对静态方法影响较大。旧版本的tearDown()方法是Mockit类的静态方法
            mockUp.tearDown();
        }
    }

    3. JMockit mock各种类型或方法的示例代码

    抽象类 

    package com.khlin.test.junit.jmockit.demo.jmockit;
    
    public abstract class AbstractA {
        
        public abstract int getAbstractAnything();
    
        public int getAnything() {
            return 1;
        }
    }

    接口类

    package com.khlin.test.junit.jmockit.demo.jmockit;
    
    public interface InterfaceB {
        
        public int getAnything();
    }

    普通类

    package com.khlin.test.junit.jmockit.demo.jmockit;
    
    public class ClassA {
        
        InterfaceB interfaceB;
        
        private int number;
        
        public void setInterfaceB(InterfaceB interfaceB) {
            this.interfaceB = interfaceB;
        }
        
        public int getAnything() {
            return getAnythingPrivate();
        }
        
        private int getAnythingPrivate() {
            return 1;
        }
        
        public int getNumber() {
            return number;
        }
        
        
        
        public static int getStaticAnything(){
            return getStaticAnythingPrivate();
        }
        
        private static int getStaticAnythingPrivate() {
            return 1;
        }
        
        public int getClassBAnything() {
            return this.interfaceB.getAnything();
        }
    }

    接口实现类

    package com.khlin.test.junit.jmockit.demo.jmockit;
    
    public class ClassB implements InterfaceB {
    
        public int getAnything() {
            return 10;
        }
        
    }

    终极测试代码

    package com.khlin.test.junit.jmockit.demo;
    
    import mockit.Deencapsulation;
    import mockit.Expectations;
    import mockit.Injectable;
    import mockit.Mock;
    import mockit.MockUp;
    import mockit.Mocked;
    import mockit.NonStrictExpectations;
    import mockit.Tested;
    import mockit.Verifications;
    import mockit.integration.junit4.JMockit;
    
    import org.junit.Assert;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    
    import com.khlin.test.junit.jmockit.demo.jmockit.AbstractA;
    import com.khlin.test.junit.jmockit.demo.jmockit.ClassA;
    import com.khlin.test.junit.jmockit.demo.jmockit.ClassB;
    import com.khlin.test.junit.jmockit.demo.jmockit.InterfaceB;
    
    @RunWith(JMockit.class)
    public class JMockitTest {
    
        /**
         * mock私有方法
         */
        @Test
        public void testPrivateMethod() {
    
            final ClassA a = new ClassA();
            // 局部参数,把a传进去
            new NonStrictExpectations(a) {
                {
                    Deencapsulation.invoke(a, "getAnythingPrivate");
                    result = 100;
                    times = 1;
                }
            };
    
            Assert.assertEquals(100, a.getAnything());
    
            new Verifications() {
                {
                    Deencapsulation.invoke(a, "getAnythingPrivate");
                    times = 1;
                }
            };
        }
    
        /**
         * mock私有静态方法
         */
        @Test
        public void testPrivateStaticMethod() {
    
            new NonStrictExpectations(ClassA.class) {
                {
                    Deencapsulation
                            .invoke(ClassA.class, "getStaticAnythingPrivate");
                    result = 100;
                    times = 1;
                }
            };
    
            Assert.assertEquals(100, ClassA.getStaticAnything());
    
            new Verifications() {
                {
                    Deencapsulation
                            .invoke(ClassA.class, "getStaticAnythingPrivate");
                    times = 1;
                }
            };
    
        }
    
        /**
         * mock公有方法
         */
        @Test
        public void testPublicMethod() {
            final ClassA classA = new ClassA();
            new NonStrictExpectations(classA) {
                {
                    classA.getAnything();
                    result = 100;
                    times = 1;
                }
            };
    
            Assert.assertEquals(100, classA.getAnything());
    
            new Verifications() {
                {
                    classA.getAnything();
                    times = 1;
                }
            };
        }
    
        /**
         * mock公有静态方法--基于行为
         */
        @Test
        public void testPublicStaticMethod() {
    
            new NonStrictExpectations(ClassA.class) {
                {
                    ClassA.getStaticAnything();
                    result = 100;
                    times = 1;
                }
            };
    
            Assert.assertEquals(100, ClassA.getStaticAnything());
    
            new Verifications() {
                {
                    ClassA.getStaticAnything();
                    times = 1;
                }
            };
        }
        
        /**
         * mock公有静态方法--基于状态
         */
        @Test
        public void testPublicStaticMethodBaseOnStatus() {
    
            MockUp<ClassA> mockUp = new MockUp<ClassA>() {
                @Mock
                public int getStaticAnything() { //注意这里不用声明为static
                    return 100;
                }
            };
            
            Assert.assertEquals(100, ClassA.getStaticAnything());
        }
        
        /**
         * mock接口
         */
        @Test
        public void testInterface() {
            
            InterfaceB interfaceB = new MockUp<InterfaceB>() {
                @Mock
                public int getAnything() {
                    return 100;
                }
            }.getMockInstance();
            
            
            ClassA classA = new ClassA();
            classA.setInterfaceB(interfaceB);
            
            Assert.assertEquals(100, classA.getClassBAnything());
        }
        
        /**
         * mock接口--基于状态
         */
        @Test
        public void testInterfaceBasedOnStatus() {
            final InterfaceB interfaceB = new ClassB();
            
            new NonStrictExpectations(interfaceB) {
                {
                    interfaceB.getAnything();
                    result = 100;
                    times = 1;
                }
            };
            
            ClassA classA = new ClassA();
            classA.setInterfaceB(interfaceB);
            
            Assert.assertEquals(100, classA.getClassBAnything());
            
            new Verifications() {
                {
                    interfaceB.getAnything();
                    times = 1;
                }
            };
        }
        
        
        
        /**
         * mock抽象类
         */
        @Test
        public void testAbstract() {
            AbstractA abstractA = new MockUp<AbstractA>() {
                @Mock
                public int getAbstractAnything(){
                    return 100;
                }
                
                @Mock
                public int getAnything(){
                    return 1000;
                }
            }.getMockInstance();
            
            Assert.assertEquals(100, abstractA.getAbstractAnything());
            
            Assert.assertEquals(1000, abstractA.getAnything());
        }
    }
  • 相关阅读:
    Mego(02)
    Mego(01)
    ThoughtWorks(中国)程序员读书雷达 —— 书籍下载整理
    Spring源码编译一次性通过&遇到的坑解决方法
    Elasticsearch怎么修改索引字段类型?
    Flume 自定义拦截器 多行读取日志+截断
    用Hibernate框架把hql生成可执行的sql语句-Oracle方言
    深入浅出SQL Server中的死锁 [转于CareySon]
    第一次迭代随笔
    结对编程代码分析
  • 原文地址:https://www.cnblogs.com/wsy0202/p/11374123.html
Copyright © 2020-2023  润新知