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. 以编程方式使用 Word 中的内置对话框

    使用 Microsoft Office Word 时,有时需要显示用户输入对话框.虽然可以创建自己的对话框,您也许还希望采用使用 Word 中内置对话框的方法,这些对话框在Application 对象 ...

  2. PHP 'ext/gd/gd.c' gdImageCrop整数符号错误漏洞

    漏洞版本: PHP 5.5.x 漏洞描述: CVE ID:CVE-2013-7328 PHP是一种HTML内嵌式的语言. PHP 'ext/gd/gd.c' gdImageCrop函数存在多个整数符号 ...

  3. c语言输入与输出库函数#include<stdio.h>

    last modified: 2010-05-28 输入与输出<stdio.h> 头文件<stdio.h>定义了用于输入和输出的函数.类型和宏.最重要的类型是用于声明文件指针的 ...

  4. MsoShapeType /InlineShape枚举

    指定形状的类型或形状范围. 名称 值 说明 msoAutoShape 1 自选图形. msoCallout 2 标注. msoCanvas 20 画布. msoChart 3 图. msoCommen ...

  5. Android 访问 wcf

    IService1.cs 添加的接口 [OperationContract] [WebInvoke(Method = "POST", BodyStyle = WebMessageB ...

  6. HDU 4622 Reincarnation(SAM)

    Problem Description Now you are back,and have a task to do:Given you a string s consist of lower-cas ...

  7. uvalive 3938 "Ray, Pass me the dishes!" 线段树 区间合并

    题意:求q次询问的静态区间连续最大和起始位置和终止位置 输出字典序最小的解. 思路:刘汝佳白书 每个节点维护三个值 pre, sub, suf 最大的前缀和, 连续和, 后缀和 然后这个题还要记录解的 ...

  8. java中字符串切割的方法总结

    StringTokenizer最快 ,基本已经不用了,除非在某些需要效率的场合.Scanner最慢. String和Pattern速度差不多.Pattern稍快些. String和Pattern的sp ...

  9. 代码-Weka的决策树类J48

    package kit.weka;   /** * desc:试试Weka的决策树类 * <code>J48Test</code> * */ import java.io.Fi ...

  10. Error message “Assembly must be registered in isolation” when registering Plugins in Microsoft Dynamics CRM 2011 2013 解决办法

    Error message “Assembly must be registered in isolation” when registering Plugins in Microsoft Dynam ...