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通过数据库查询出不同分组货物的数量,得到货物是否畅销。

 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());
}
}

使用JUnit4与JMockit进行打桩测试的更多相关文章

  1. mock打桩测试

    pom依赖: <!-- https://mvnrepository.com/artifact/org.jmockit/jmockit --> <dependency> < ...

  2. Junit4学习(二)测试失败的情况

    一,前言 首先理解: 1,测试用例不是证明你是对的,而是证明你没有错 2,测试用例用来达到想要的预期结果,但对于逻辑错误无能为力 二,两种测试失败:error And Failure 1,Failur ...

  3. JUnit4时间(超时)测试实例

    “时间测试”是指,一个单元测试运行时间是否超过指定的毫秒数,测试将终止并标记为失败. import org.junit.*; /** * JUnit TimeOut Test * @author yi ...

  4. JUnit4忽略(Ignore)测试实例

    这种“忽略”是指方法还没有准备好进行测试,JUnit引擎会绕过(忽略)这个方法. import org.junit.*; /** * JUnit Ignore Test * @author yiiba ...

  5. 使用Junit4对web项目进行测试(一)Junit初配置

    Junit测试用例不是用来证明你是对的,而是用来证明你没有错 1.功能   -在项目未在浏览器运行之前对获得的结果和预期的结果进行比较调试,减少BUG和发布时的修复工作 2.测试类和代码类应分开存放. ...

  6. Junit4使用详解一:测试失败的两种情况

    Junit4最佳实践 1.把测试文件夹和代码文件夹分离,这两者的代码互不干扰,代码目录和测试目录是并列的关系 2.Java代码 3.创建单元测试代码文件 4.运行测试代码  5.查看测试结果 现在的情 ...

  7. JUNIT4 GroboUtils多线程测试

    阅读更多 利用JUNIT4,GroboUtils进行多线程测试 多线程编程和测试一直是比较难搞的事情,特别是多线程测试.只用充分的测试,才可以发现多线程编码的潜在BUG.下面就介绍一下我自己在测试多线 ...

  8. junit4 套件测试

    junit4 中的套件可以用来测试一个需要依赖的业务流程,如购买必须依赖与登录成功 代码实现: 测试数据存放 public class BaseTest { protected static Hash ...

  9. JMockit使用总结

    Jmockit可以做什么 使用JMockit API来mock被依赖的代码,从而进行隔离测试. 类级别整体mock和部分方法重写 实例级别整体mock和部分mock mock静态方法.私有变量.局部方 ...

随机推荐

  1. [swustoj 191] 迷宫逃离

    迷宫逃离(0191) 描述 江鸟突然想到了一个迷宫逃离的游戏,话说有三个人被困于一个n*m的迷宫里,他们三人都可以向上.向下.向左.向右四个方向进行走动,当然他们所在的初始位置没有障碍物,同时只能走到 ...

  2. easyui datagriad 框架 自适应r

    easyui datagriad 框架 在做列自适应时可以采取以下步骤: 1. fitColumns: true,/*自动扩大或缩小列的尺寸以适应表格的宽度并且防止水平滚动*/ 使之保持与父类宽度相同 ...

  3. Azure Site Recovery 通过一键式流程将虚拟机故障转移至 Azure虚拟机

    Ruturaj Dhekane 云 + Enterprise 项目经理  现在,AzureSite Recovery可以通过其"灾难恢复至 Azure"功能保护您的工作负荷,并 ...

  4. bzoj 1030 [JSOI2007]文本生成器(AC自动机+DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1030 [题意] 给n个小串,随机构造一个长为m的大串,一个串合法当且仅当包含一个或多个 ...

  5. 输入A和B,计算并输出A+B

    EOF是一个预定义的常量,等于-1. 输入A和B,计算并输出A+B Sample input: 1    5 10  20 Sample output: 6 30 #include <iostr ...

  6. 【原创】Hadoop机架感知对性能调优的理解

    Hadoop作为大数据处理的典型平台,在海量数据处理过程中,其主要限制因素是节点之间的数据传输速率.因为集群的带宽有限,而有限的带宽资源却承担着大量的刚性带宽需求,例如Shuffle阶段的数据传输不可 ...

  7. HDU4607 - Park Visit(树的直径)

    题目大意 给定一颗树,要求走过其中连续的k个点,使得步数最少 题解 每条边要么经过两次,要么一次,因为我们的目标就是使得走一次的边尽量的多,这样就转换成求树的直径了,求树的直径我用的是两次dfs,先随 ...

  8. hdoj 5112 A Curious Matt

    A Curious Matt Time Limit: 2000/2000 MS (Java/Others)    Memory Limit: 512000/512000 K (Java/Others) ...

  9. 利用ACE 自己实现的线程池

    1: 线程池组件的配置文件: [log] ;DEBUG = 0 ;INFO = 1 ;WARN = 2 ;FAULT = 3 level=0 ;SCREENOUT = 0 ;FILEOUT = 1 ; ...

  10. Node.js学习(14)----EJS模板引擎

    这个入门教程将从以下几个方面来讲解: 1. 引入EJS 2. 创建一个模板 3. 使用视图工具组件 4. 使用错误处理组件 5. 什么情况下应使用EJS 引入EJS 在我们正式开始前,我们先来做点准备 ...