最近工作需要,要求开发代码单元测试覆盖率达到多少多少,一个个写单元测试实在麻烦,于是使用了一款插件UT-Generator,还是省去了不少工作量的,这里简单描述一下。
JMockit使用
JMockit的基本使用可以通过JMockit中文网,或者通过我这整理的内容了解,如下:
https://github.com/eussi/Notes/blob/master/note_java/JMockit/JMockit.md
内容目录
- JMockit是什么
- 配置
maven pom.xml配置
JUnit4.x及以下用户特别注意事项
JMockit Coverage配置 - 程序结构
- API
@Mocked
@Injectable与@Mocked
@Capturing
Expectations
MockUp & @Mock
Verifications - 常见用法
Mock类
Mock实例
Mock接口
用JMockit做代码覆盖率 - 高级用法
Mock构造函数&初始化代码块
Mock一类多实例
Mock泛型(类型变量)
Mock方法中调用老方法
同一方法调用返回时序结果
定时返回结果
在Mock时做AOP
级联Mock - JMockit原理剖析
JMockit架构
JMockit启动过程
Exepectations录制原理
MockUp的Mock原理
各Mock注解的Mock逻辑 - UT自动生成插件
特性
安装
使用
生成UT - 相关代码
下面主要介绍下UT-Generator的简单使用
UT自动生成插件
IDEA中有一款插件,UT-Generator,可以方便的生成使用JMockit的单元测试代码
特性
-
自动生成被测类所有声明方法的测试代码
-
自动生成被测类依赖的Mock代码,并与测试代码分离
-
支持增量(新增类和方法)生成
-
不会覆盖已有测试代码
-
简单易用,生成速度快
-
配置灵活多变
安装
IDEA: File -> Setting -> Plugins -> 搜索UT-Generator -> 安装后重启
添加插件配置,如图:
使用
生成代码时注意添加依赖
<!-- 先声明jmockit的依赖 -->
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
<!-- 再声明junit的依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
测试类如下:
// 邮件服务类,用于发邮件
public interface MailService {
/**
* 发送邮件
*
* @param userId
* 邮件接受人id
* @param content
* 邮件内容
* @return 发送成功了,就返回true,否则返回false
*/
public boolean sendMail(long userId, String content);
}
// 用户身份校验
public interface UserCheckService {
/**
* 校验某个用户是否是合法用户
*
* @param userId
* 用户ID
* @return 合法的就返回true,否则返回false
*/
public boolean check(long userId);
}
//订单服务类 ,用于下订单
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;
}
}
生成UT
生成前保证代码能够正常编译通过,在Project或Module的任意目录或文件上右击,选中Generate UT,如下点击了上面展示的类文件:
此时生成两个类:
/**
* Generated by UTGenerator Plugin on 2020/10/18 14:40.
*/
public class OrderServiceAutoMock {
//Mock constructor of test class OrderService, should be called before instantiation.
static class ConstructorMock {
static void mockMailService() {
new MockUp<OrderService>() {
@Mock
public void $init(MailService mailService) {
}
};
}
}
/**
* Creates a new instance of a given class, with any instance fields left uninitialized,
* if the given class is abstract or an interface, then a concrete class is created, with
* empty implementations for its methods.
*/
static <T> T getMockInstance(Class<? extends T> clazz) {
return Deencapsulation.newUninitializedInstance(clazz);
}
//Sets the value of a non-accessible field on a given object.
static void setField(Object objectWithField, String fieldName, Object fieldValue) {
Deencapsulation.setField(objectWithField, fieldName, fieldValue);
}
//Sets the value of a non-accessible static field on a given class.
static void setStaticField(Class<?> classWithStaticField, String fieldName, Object fieldValue) throws Exception {
Field field = classWithStaticField.getDeclaredField(fieldName);
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
field.set(null, fieldValue);
}
//Gets the value of a non-accessible field from a given object.
static <T> T getField(Object objectWithField, String fieldName) {
return Deencapsulation.getField(objectWithField, fieldName);
}
//Gets the value of a non-accessible static field defined in a given class.
static <T> T getStaticField(Class<?> classWithStaticField, String fieldName) {
return Deencapsulation.getField(classWithStaticField, fieldName);
}
/**
* Dependency mock code of test method {@link OrderService#submitOrder}
*/
static class SubmitOrderMock {
static <T extends UserCheckService> void mockUserCheckService(final Map<Integer, Boolean> checkMockValue) {
new MockUp<T>() {
int checkCount = 0;
boolean lastCheckMockValue;
@Mock
public boolean check(long param0) {
boolean result = lastCheckMockValue;
if (checkMockValue.containsKey(checkCount)) {
result = checkMockValue.get(checkCount);
lastCheckMockValue = result;
}
checkCount++;
return result;
}
};
}
static <T extends MailService> void mockMailService(final Map<Integer, Boolean> sendMailMockValue) {
new MockUp<T>() {
int sendMailCount = 0;
boolean lastSendMailMockValue;
@Mock
public boolean sendMail(long param0, String param1) {
boolean result = lastSendMailMockValue;
if (sendMailMockValue.containsKey(sendMailCount)) {
result = sendMailMockValue.get(sendMailCount);
lastSendMailMockValue = result;
}
sendMailCount++;
return result;
}
};
}
}
}
/**
* Generated by UTGenerator Plugin on 2020/10/18 14:40.
*/
@RunWith(JMockit.class)
public class OrderServiceAutoTest extends OrderServiceAutoMock {
@BeforeClass
public static void setUpClass() {
//operation before all tests
}
@AfterClass
public static void tearDownClass() {
//operation after all tests
}
//test class instance, can be reused
private OrderService testInstance;
@Before
public void setUp() {
//operation before each test
MailService mailService = null;
testInstance = new OrderService(mailService);
}
@After
public void tearDown() {
//operation after each test
}
@Test
public void testSubmitOrder() {
//TODO: review the generated mock code, modify the given mock values
//mock all dependencies of method OrderService#submitOrder
//mock class UserCheckService
boolean z = false;
Map<Integer, Boolean> checkMockValue = new HashMap<>();
checkMockValue.put(0, z);
SubmitOrderMock.mockUserCheckService(checkMockValue);
//mock class MailService
boolean z1 = false;
Map<Integer, Boolean> sendMailMockValue = new HashMap<>();
sendMailMockValue.put(0, z1);
SubmitOrderMock.mockMailService(sendMailMockValue);
//TODO: review the generated test code, modify the given method call arguments and assert clause
//prepare arguments and call the test method
long buyerId = 0L;
long itemId = 0L;
boolean actualResult = testInstance.submitOrder(buyerId, itemId);
assertFalse(actualResult);
}
}
前者是Mock代码,后者是测试代码,通过之前的介绍,看懂代码不是问题。
需要注意的是,插件并不是十分的智能,单元测试会出现空指针,类型转换等方面的异常,代码的覆盖率也可能会很低,所以需要在手动调整,达到自己的测试目的。
如上生成的代码,会报如下错误:
java.lang.NullPointerException
at com.eussi.basic.OrderService.submitOrder(OrderService.java:29)
at com.eussi.basic.OrderServiceAutoTest.testSubmitOrder(OrderServiceAutoTest.java:61)
可以手动修改OrderServiceAutoTest成如下代码,使单元测试通过:
/**
* Generated by UTGenerator Plugin on 2020/10/18 14:40.
*/
@RunWith(JMockit.class)
public class OrderServiceAutoTest extends OrderServiceAutoMock {
@BeforeClass
public static void setUpClass() {
//operation before all tests
}
@AfterClass
public static void tearDownClass() {
//operation after all tests
}
//test class instance, can be reused
@Tested
private OrderService testInstance;
@Injectable
MailService mailService;
@Injectable
UserCheckService userCheckService;
@Before
public void setUp() {
//operation before each test
testInstance = new OrderService(mailService);
}
@After
public void tearDown() {
//operation after each test
}
@Test
public void testSubmitOrder() {
//TODO: review the generated mock code, modify the given mock values
//mock all dependencies of method OrderService#submitOrder
//mock class UserCheckService
boolean z = false;
Map<Integer, Boolean> checkMockValue = new HashMap<>();
checkMockValue.put(0, z);
SubmitOrderMock.mockUserCheckService(checkMockValue);
//mock class MailService
boolean z1 = false;
Map<Integer, Boolean> sendMailMockValue = new HashMap<>();
sendMailMockValue.put(0, z1);
SubmitOrderMock.mockMailService(sendMailMockValue);
//TODO: review the generated test code, modify the given method call arguments and assert clause
//prepare arguments and call the test method
long buyerId = 0L;
long itemId = 0L;
boolean actualResult = testInstance.submitOrder(buyerId, itemId);
assertFalse(actualResult);
}
}