JMockit程序结构
package com.jmokit;
import com.Jmokit;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.junit.Assert;
import org.junit.Test;
public class JomckitConstructureTest {
// 测试属性,被mock掉,并不会真正执行这个属性里的方法。而是对期待的执行有一个result
// 如果没有mock掉,就会真的执行这个javabean里的方法。mock掉,就不执行方法,而是返回期待的result
// 这个期待,并不是对真正执行后的期待,而是coder给的期待
@Mocked
public Jmokit jmokit = new Jmokit();
// 测试方法
@Test
public void test1() {
// 录制(Record) 录制代码块
new Expectations() {
// 这一段为什么放在代码块里,如果不放在{}离,就没法识别jmokit
{
jmokit.sayHello();
//期望返回的result
result = "hello jjj";
}
};
// 重放测试逻辑
String msg = jmokit.sayHello();
Assert.assertTrue(msg.equals("hello jjj"));
// 验证代码块
new Verifications() {
{
jmokit.sayHello();
times = 1;
}
};
}
@Test
public void test2(@Mocked Jmokit jmokit1) {
new Expectations() {
{
jmokit1.sayHello();
result = "1111";
}
};
String msg = jmokit1.sayHello();
Assert.assertTrue(msg.equals("1111"));
}
}
Jmockit 的程序结构分为, 测试属性/ 测试参数, 测试方法, 测试方法中又包含:录制代码块,重放测试逻辑, 验证代码块
- 测试属性/测试参数:
可以用于修饰测试属性的注解API有:@Mocked, @Tested, @Injectable, @Capturing
@Mocked:表示这个测试属性,他的实例化,属性赋值,方法调用的返回值全部由JMockit来接管,接管后,测试属性jmokit的方法sayHello与Jmokit类中定义的就不一样了,而是由录制脚本来定义了。那么被@Mockit修饰后的测试属性,Jmockit到底对他做了什么?
测试参数是测试方法中的参数,仅作用于当前方法。Junit中是不允许测试方法带有参数的,但是加了Jmockit 的注解API(@Mocked, @Tested, @Injectable, @Capturing) 则是可以的。 - 测试方法
Record, Replay, Verification 是JMockit测试程序的主要结构。- 录制代码块Record:
先录制某个类/对象的方法的调用,在当输入什么时,返回什么。 - 重放测试逻辑
把录制的内容,在重新执行一遍 - 验证代码块
验证执行的次数等。
- 录制代码块Record:
API
@Mocked
当@Mocked修饰一个类时:
- Jmockit帮助coder实例化这个对象,不用担心它为null
- 它是怎么帮助coder实例化对象的,为什么静态方法没有执行,因为没有类加载吗?
- 静态方法不起作用,返回null,静态方法没有加载
- 非静态方法也不起作用了,返回值为null
- 自己再创建一个该类的对象,也是如此,coder创建的对象的方法也被mock了。
- 那么在什么场景下要用这个注解?
当@Mocked修饰一个接口/抽象类时: - 同样JMockit会进行实例化。
- 如果接口方法返回值为String,则mock后返回null, 如果接口方法返回值为基本类型,mock后返回0.
- 如果接口方法返回值为引用类型非String, 那么这个返回值对象不为空,且这个对象也是Mocked对象。
所以综上,被@Mocked注解修饰的接口/类,JMocked会帮助生成一个Mocked对象,这个对象方法包含静态方法,返回值是默认值。
当我们测试程序依赖某个接口时,把这个接口@Mocked,JMocked就会帮助我们生成这个接口的实例。
@Injectable
Injectable 也是告诉JMockit生成一个Mocked对象,但是只针对其修饰的实例。且注解没有影响到该类的静态方法以及构造函数。
- 被修饰的类的静态方法和构造函数没有被影响,会正常执行
- 被修饰的类型成员方法其返回值受到影响。
- 被修饰的类,只影响到注解的实例。coder再new一个实例不会受到影响。
@Tested
此注解通常一起使用。
场景实例:在买家下订单时,电商后台需要验证卖家身份是否合法
官网代码如下:
// 邮件服务类,用于发邮件
public interface MailService {
/**
* 发送邮件
*
* @param userId
* 邮件接受人id
* @param content
* 邮件内容
* @return 发送成功了,就返回true,否则返回false
*/
public boolean sendMail(long userId, String content);
}```
```java
// 用户身份校验
public interface UserCheckService {
/**
* 校验某个用户是否是合法用户
*
* @param userId
* 用户ID
* @return 合法的就返回true,否则返回false
*/
public boolean check(long userId);
}//订单服务类 ,用于下订单```
```java
public class OrderService {
// 邮件服务类,用于向某用户发邮件。
MailService mailService;
// 用户身份校验类,用于校验某个用户是不是合法用户
@Resource
UserCheckService userCheckService;
// 构造函数, 这个构造函数,是有参构造函数
public OrderService(MailService mailService) {
this.mailService = mailService;
}
/**
* 下订单
*
* @param buyerId
* 买家ID
* @param itemId
* 商品id
* @return 返回 下订单是否成功
*/
public boolean submitOrder(long buyerId, long itemId) {
// 先校验用户身份
if (!userCheckService.check(buyerId)) {
// 用户身份不合法
return false;
}
// 下单逻辑代码,
// 省略...
// 下单完成,给买家发邮件
if (!this.mailService.sendMail(buyerId, "下单成功")) {
// 邮件发送成功
return false;
}
return true;
}
}```
```java
//@Tested与@Injectable搭配使用
public class TestedAndInjectable {
//@Tested修饰的类,表示是我们要测试对象,在这里表示,我想测试订单服务类。JMockit也会帮我们实例化这个测试对象
@Tested
OrderService orderService;
//测试用户ID
long testUserId = 123456l;
//测试商品id
long testItemId = 456789l;
// 测试注入方式
@Test
public void testSubmitOrder(@Injectable MailService mailService,
@Injectable UserCheckService userCheckService) {
new Expectations() {
{
// 当向testUserId发邮件时,假设都发成功了
mailService.sendMail(testUserId, anyString);
result = true;
// 当检验testUserId的身份时,假设该用户都是合法的
userCheckService.check(testUserId);
result = true;
}
};
// JMockit帮我们实例化了mailService了,并通过OrderService的构造函数,注入到orderService对象中。
//JMockit帮我们实例化了userCheckService了,并通过OrderService的属性,注入到orderService对象中。
Assert.assertTrue(orderService.submitOrder(testUserId, testItemId));
}
}
@Tested注解只能修饰类,如果修饰interface,生成的对象是null
@Tested表示被测试对象。如果该对象没有赋值,JMockit会去实例化它,若@Tested的构造函数有参数,
则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,
不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。
注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象。
当然,我们的测试程序要尽量避免这种情况出现。因为给哪个测试属性/测试参数加@Injectable,是人为控制的。
@Capturing 主要作用于子类/实现类的Mock
当我们是使用代理类,或者子类来实现其父接口的具体方法的时候。@Capturing注解除了使该class/interface 具有@Mocked注解的属性,还会影响到他的子类和实现类
Expectations
两种使用方式:
- 通过引用外部类的@Mocked, @Injectable, @Capturing来录制
- 通过构建函数注入类/对象来录制
如果只希望mock掉类的某一个方法。
把Mock的类传入Expectations的构造函数。可以达到只mock类的部分行为
MockUp 与 @Mock
这个mock方式比较实用于对一些通用类的mock,以减少大量重复的new Expectations{{}}