什么是单元测试?

单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

一、mock框架功能对比:


二、mock框架简单介绍

EasyMock:

EasyMock 是早期比较流行的MocK测试框架。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。

使用EasyMock大致可以划分为以下几个步骤:

①    使用 EasyMock 生成 Mock 对象;

②    录制 Mock 对象的预期行为和输出;

③    将 Mock 对象切换到 播放 状态;

④    调用 Mock 对象方法进行单元测试;

⑤    对 Mock 对象的行为进行验证。

@Test
public void testMockMethod() {
Class1Mocked obj= createMock(Class1Mocked.class);
① expect(obj.hello("z3")).andReturn("hello l4");
② replay(obj);
③ String actual= obj.hello("z3");
④ assertEquals("hello l4", actual);
verify(obj);⑤ }

mockito:

使用mockito大致可以划分为以下几个步骤:

①    使用 mockito 生成 Mock 对象;

②    定义(并非录制) Mock 对象的行为和输出(expectations部分);

③    调用 Mock 对象方法进行单元测试;

④    对 Mock 对象的行为进行验证。

@Test

public void testMockMethodInOrder() {
Class1Mocked objOther= mock(Class1Mocked.class);
Class1Mocked objCn= mock(Class1Mocked.class);
when(objOther.hello("z3")).thenReturn("hello l4");
when(objCn.hello("z3")).thenReturn("hello 张三");
String other= objOther.hello("z3");
assertEquals("hello l4", other);
String cn= objCn.hello("z3");
assertEquals("hello 张三", cn);
InOrder inOrder= inOrder(objOther, objCn);//此行并不决定顺序,下面的两行才开始验证顺序inOrder.verify(objOther).hello("z3"); inOrder.verify(objCn).hello("z3"); }
mockito的spy()方式模拟中expectations部分使用的语法不同
when(obj.hello("z3")).thenReturn("hello l4"); 会调用原方法 只是将返回值替换了
doReturn("hello l4").when(obj).hello("z3");
PowerMock:

这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对static, final, private方法均不能mock。其实测试架构设计良好的代码,一般并不需要这些功能,但如果是在已有项目上增加单元测试,老代码有问题且不能改时,就不得不使用这些功能了。

Spock:

spock框架是基于java和groovy脚本结合使用的  框架的设计思路参考了JUnit,jMock,RSpec,Groovy,Scala,Vulcans……

def "isUserEnabled should return true only if user status is enabled"() {
given://初始化程序
UserInfo userInfo = new UserInfo(
status: actualUserStatus
);
userDao.getUserInfo(_) >> userInfo;
expect: //简版的when then 预期设置
userService.isUserEnabled(1l) == expectedEnabled;
where:  //条件
actualUserStatus | expectedEnabled
UserInfo.ENABLED | true
UserInfo.INIT | false
UserInfo.CLOSED | false
clean //清除使用资源
}

spock也支持spy,stub之类的mock对象,但是并不推荐使用

Think twice before using this feature. It might be better to change the design of the code under specification

moco

一个简单的设置stub框架,主要用于测试和集成

Moco 可以用于移动开发,模拟尚未开发的服务;Moco 还可以用于前端开发,模拟一个完整的 Web 服务器,等等。

编译原文件:

cd

./gradlew build

启动moco服务:

java -jar moco-runner--standalone.jar start -p 12306 -c foo.json

打开浏览器 根据ip port 地址参数测试

编写配置文件:

{
"request": {
"uri" : "/event"
},
"response": {
"text": "event"
},
"on": {
"complete": {
"async" : "true",
"post" : {
"url" : "http://another_site",
"content": "content"
}
}
}
}

Jmockit:

1.简介

JMockit项目基于 Java 5 SE 的 java.lang.instrument 机制,内部使用 ASM 库来修改Java的Bytecode,是一个能帮我们解决以上问题的轻量级框架,它允许你动态的改变已有的方法,这主要基于java 1.5的Instrumentation框架,允许你重定义private,static and final方法,甚至是no-arg constructors都能够并轻易的重定义,这样便可以使得JMockit能够适应几乎所有的设计。

使用mock的场景 :

真实对象有着不确定的行为

真实对象很难创建

真实对象的行为很难触发

真实对象响应缓慢

真实对象是用户界面

真实对象使用了回调机制

真实对象尚未存在

而对应的mock具有下面的功能:

替换远程对象,如ESB、WEB Service对象等

替换复杂的对象

方便模块化开发

2.JMockit原理

JMockit是依赖JDK提供的instrument机制及ASM来实现其功能的,基本原理是这样的:

在JDK装入类的时候,由于我们设置也-javaagent,JDK会查看这个jar包的/META-INF/MANIFEST.MF文件,找到Premain-Class并加载这个类,然后调用这个类的premain方法将Instrument实现设置进去,然后JMockit就可以在类加载的时候做transformer,在做transformer的时候会通过ASM来动态改变字节码。

Jmockit也可以分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。而Expectations块一般由Expectations类和NonStrictExpectations类定义。用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,所以可以省略掉Verifications块;而用NonStrictExpectations类定义的,则没有这些限制,所以如果需要验证,则要添加Verifications块。

JMockit有两种Mock方式:基于行为的Mock方式和基于状态的Mock方式:

(基于行为的)根据输入输出,功能测试,类似黑盒测试。需要把依赖的代码mock掉,实际相当于改变了被依赖的代码的逻辑。

(基于状态的)根据用例测试路径,测试代码内部逻辑,接近白盒测试。目的是测试单元测试及其依赖代码的调用过程,验证代码逻辑是否满足测试路径。

基于行为:

new Expectations(类){

{

期望方法;

结果;

}

}

方法调用

基于状态:

new Mockup(类) {

模拟方法{

控制该方法返回结果

}

}

pom依赖:放在junit前面 防止冲突

com.googlecode.jmockit
jmockit
${jmockit.version}
test
package com.maoyan.xuanwu.template.provider;
import com.maoyan.xuanwu.template.dao.IGoodsDAO;
import com.maoyan.xuanwu.template.domain.StorePO;
import com.maoyan.xuanwu.template.service.IGoodsService;
import com.maoyan.xuanwu.template.service.IStoreService;
import mockit.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;
/**
* Created by changshuchao on 2017/4/10.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AppServiceApplication.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class GoodsServiceTest {
@Autowired
private IGoodsService goodsService;
@Autowired
private IStoreService storeService;
// @Mocked(methods = { "方法名" }, inverse = false)
// inverse=false 只有声明的method被mock 如果true 代表相反
private StorePO s = new StorePO();
//mock 构造函数
@Test
public void mockConstrutorTest() {
new Expectations() {
{
new MockUp() {
private int id;
@Mock //$init表示构造方法
public void $init(int id) {
this.id = id + 1000;
}
@Mock
public int getRealId() {
return this.id;
}
};
}
};
StorePO ss = new StorePO(1);
Assert.isTrue(ss.getRealId() == 1001);
}
//mock 属性 静态属性
@Test
public void mockFieldTest() {
new Expectations(s) {
{
Deencapsulation.setField(s, "name", "mock field");
Deencapsulation.setField(s, "bossName", "mock maoyan");
}
};
Assert.isTrue(s.getName().equals("mock field"));
Assert.isTrue(s.getBossName().equals("mock maoyan"));
}
//mock 普通方法
@Test
public void mockMethodTest() {
new Expectations(s) {
{
s.getStoreDevelop("mock");
result = "mock method";
}
};
Assert.isTrue(s.getStoreDevelop("mock").equals("mock method"));
}
//mock 私有方法
@Test
public void mockPrivateStaticMethodTest() {
new Expectations(s) {
{
Deencapsulation.invoke(s, "storeDevelop", "mock");
result = "mock private private static method";
}
};
Assert.isTrue(s.getStoreDevelop("mock").equals("mock private private static method"));
}
//mock final方法 final类直接@mocked就可以
@Test
public void mockFinalMethodTest() {
new Expectations(s) {
{
s.getStroreInfo(withAny(anyInt));
result = "mock info" + anyInt;
times = 2;//总共可以调用的次数
}
};
Assert.isTrue(s.getStroreInfo(1001).equals("mock info0"));//第一次调用走mock方法
Assert.isTrue(s.getStroreInfo(1000001).equals("mock info0"));
Assert.isTrue(s.getStroreInfo(1000).equals("1000one two threestore"));//走真实方法逻辑
}
//mock dao层接口注入到service中被调用
//mockUp 可以mock抽象类 实现里面的方法
@Test
public void mockDaoTest() {
new Expectations() {
{
MockUp mockUp = new MockUp() {
@Mock
public Double getPriceByGoodsId(int id) {
return 1.0;
}
};
IGoodsDAO goodsDAO = mockUp.getMockInstance();
this.setField(goodsService, "goodsDAO", goodsDAO);
}
};
/* MockUp mockUp = new MockUp() {
@Mock
public Double getPriceByGoodsId(int id) {
return 1.0;
}
};
IGoodsDAO goodsDAO = mockUp.getMockInstance();
new Expectations() {
{  //mock属性 将mock的dao层注入到service中
this.setField(goodsService, "goodsDAO", goodsDAO);
}
};*/
/* GoodsServiceImpl s=new GoodsServiceImpl();
s.setGoodsDAO(goodsDAO);
System.out.println(s.getTotalPrice(1));*/
//mockUp.tearDown();
Assert.isTrue(goodsService.getTotalPrice(1) == 1.0);
}
@Test
public void mockServiceTest() {
//局部参数进行mock
new Expectations(goodsService) {
{
goodsService.getTotalPrice(1);
result = 1000;
goodsService.saveOrUpdateGoods(1);
result = true;
}
};
Assert.isTrue(storeService.getPriceByid(1) == 1000.0);
}
}