JMockit
以前我们单元测试基本不怎么写,或者直接使用@Test 运行 一旦遇到需要调用第三方服务就比较麻烦了,所以今天学习使用JMockit 可以帮助我们解决这个问题同时还有可视化测试覆盖展示。
使用JMockit
- 导入引用jar
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.45</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<!-- guava-->
<!-- https://mvnrepository.com/artifact/com.google/guava -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
<properties>
<jmockit.version>1.45</jmockit.version>
</properties>
- 新建需要测试的类
class com.bj58.drj.bean.MockitBean -- 实体类
class com.bj58.drj.test.service.impl.ZooImpl
class com.bj58.drj.test.service.impl.UserDaoImpl
class com.bj58.drj.test.service.impl.OrderService
interface com.bj58.drj.test.service.IUserDao
interface com.bj58.drj.test.service.UserCheckService
interface com.bj58.drj.test.service.IOrderService
interface com.bj58.drj.test.service.MailService
interface com.bj58.drj.test.service.Zoo
- 新建测试用例类
package com.bj58.drj;
import com.bj58.drj.bean.MockitBean;
import com.bj58.drj.test.service.IOrderService;
import com.bj58.drj.test.service.MailService;
import com.bj58.drj.test.service.UserCheckService;
import com.bj58.drj.test.service.Zoo;
import com.bj58.drj.test.service.impl.OrderService;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import mockit.*;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* @ClassName TestMockit
* @Description: TODO
* @Author drj
* @Date 2021/4/15
* @Version V1.0
**/
public class TestMockit {
private static final String DOG = "dog";
long testUserId = 12345;
long testItemId = 45678;
public static MockitBean mockitBean = new MockitBean();
public static Injector in = null;
/***
* 测试前 通过 InjectMembers 实现 属性注入
* @throws Exception
*/
@Before
public void before() throws Exception {
System.err.println("before******************************");
in.injectMembers(this);
}
/***
* Guice.createInjector 保证在测试类Injector 是单例
*
*/
@BeforeClass
public static void beforeClass() {
System.err.println("beforeClass******************************");
in = Guice.createInjector();
}
/***
* @Tested (由于是接口无法实例话需要配合@Inject)为了配合这个@Injectable
* @Inject 实例化对象
*/
@Tested
@Inject
IOrderService orderService;
/***
* 可以理解就是new 一个Zoo 而且对所有他的实例都一样,
* 同时他的属性都是默认的(引用)null 或者(int)0; 针对不同类型
*/
@Mocked
Zoo zoo;
/**
* 测试案例一:
* 模拟MVC 调用都是本地服务
* 比如当前是controller 由 service 调用 dao
*/
@Test
public void testMockFirstTrue() {
boolean admin = orderService.checIsExistskUserName("admin");
orderService.callPrivateMethod();
Assert.assertTrue(admin);
}
@Test
public void testMockFirstFlase() {
boolean admin = orderService.checIsExistskUserName(null);
orderService.callPrivateMethod();
Assert.assertFalse(admin);
}
/***
* 测试案例二:
* 通过mocked 模拟直接调用第三方服务接口拿到返回值
*/
@Test
public void testMockSecond() {
new Expectations() {{
zoo.dog(anyString);
result = DOG;
}};
Assert.assertTrue(DOG.equals(zoo.dog("")));
}
/***
* 测试案例三:
* 属性中有第三方服务接口
* @Tested (不能单独修饰接口)所以和 @Inject 配合使用
* @param mailService 仿照发送邮件服务测试
* @param userCheckService 仿照 用户合法性接口 测试
* 注意:在1.45以及1.45+ Injectable 修饰的参数禁止在方法中
*
*/
@Injectable
MailService mailService;
@Injectable
UserCheckService userCheckService;
@Test
public void testMockThreeTrue() {
new Expectations() {
{
/***
* @Injectable 实例化当前对象 配合@Tested 他会自动去匹配@Tested 修饰的属性是否有【mailService 和 userCheckService】
* 匹配方式一般是字段和构造方式两种类似spring @Autowire
* 特别注意:
* 1、返回void没有必要在Expectations 写 因为这里是处理有返回值的
* 2、调用顺序和result 赋值需要一起
*/
mailService.sendMail(testUserId, anyString);
result = true;
userCheckService.check(testUserId);
result = true;
}
};
boolean isSucess = orderService.submitOrder(testUserId, testItemId);
Assert.assertTrue(isSucess);
}
@Test
public void testMockThreeFlase() {
new Expectations() {
{
/***
* @Injectable 实例化当前对象 配合@Tested 他会自动去匹配@Tested 修饰的属性是否有【mailService 和 userCheckService】
* 匹配方式一般是字段和构造方式两种类似spring @Autowire
* 特别注意:
* 1、返回void没有必要在Expectations 写 因为这里是处理有返回值的
* 2、调用顺序和result 赋值需要一起
*/
mailService.sendMail(testUserId, anyString);
result = false;
userCheckService.check(testUserId);
result = true;
}
};
boolean isSucess = orderService.submitOrder(testUserId, testItemId);
Assert.assertFalse(isSucess);
}
/***
* 测试案例四
* 注意:版本1。49 禁止这样写 建议use a MockUp instead
* 通过Expectations实现私有方法和静态方法调用
*/
@Test
public void testFour() {
new Expectations(OrderService.class) {{
OrderService.buildBean(anyString);
result = mockitBean;
}};
OrderService order = new OrderService();
new Expectations(order) {{
order.submitOrder(testItemId, testItemId);
result = true;
}};
Assert.assertTrue(order.submitOrder(testItemId, testItemId));
Assert.assertTrue(mockitBean.equals(OrderService.buildBean("")));
}
public static class OrderMockUp extends MockUp<OrderService> {
@Mock
private String fetchCoreData() {
return "drj";
}
@Mock
public static MockitBean buildBean(String userName) {
return mockitBean;
}
}
/***
* 测试案例五:
* 通过MockUp 和@Mock 实现替换 new Expectations() 以及 私有方法的模拟
*/
@Test
public void testFive() {
new OrderMockUp();
Assert.assertTrue(OrderService.buildBean("").equals(mockitBean));
boolean b = orderService.callPrivateMethod();
Assert.assertTrue(!b);
}
}
通过上面的五个测试用例分别对常规测试、单独模拟第三方、第三方接口属性方式注入到测试服务中、通过Expectations实现私有方法和静态方法调用、通过MockUp 和@Mock 实现私有方法和静态方法的模拟。具体使用方法都在类中写了相关注释。如有遗漏或者错误的地方还请大家多多指教。
生成Code Coverage文档
- 引入生成覆盖率文档插件
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<disableXmlReport>true</disableXmlReport>
<argLine>
-Dcoverage-output=html
<!-- 关闭 生成 可视化 界面功能 -->
<!-- -Dcoverage-classes=none-->
-Dcoverage-classes=loaded
-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
</configuration>
</plugin>
</plugins>
-
生成方式
-
直接在每个测试用例方法运行会生成针对改测试用例的覆盖率文档。假如不想每次都测试都生成可以在IDEA或者Eclipse 配置运行参数-Dcoverage-classes=none
-
直接使用 maven test 插件功能 这个正对所有@Test 进行统计。这个相对更全面。pom配置参数也会加载生效。假如这个方式也不想生成覆盖率文档 也是可以在pom 中配置-Dcoverage-classes=none
-
覆盖率计算规则
源文件的覆盖率计算为=100*(NE+NFE)/(NS+NF),其中NS是有(包括绿色和红色)线的总数、NF非final字段数、NE执行线(绿色)的数量和NFEO(绿色)完全执行字段的数量。
- 按照方式二生成文档详情
- 插件配置
-javaagent:" s e t t i n g s . l o c a l R e p o s i t o r y " / o r g / j m o c k i t / j m o c k i t / {settings.localRepository}"/org/jmockit/jmockit/ settings.localRepository"/org/jmockit/jmockit/{jmockit.version}/
jmockit-${jmockit.version}.jar 加载对应的jmockit jar (特别注意版本号)
-Dcoverage-output=html 生成html文档
-Dcoverage-classes=loaded 覆盖率大于0 的被测试的类生成html 文档
-Dcoverage-classes=none 关闭生成文档
- 更加详细配置
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
<!-- Coverage properties -->
<!-- At least one of the following needs to be set: -->
-Dcoverage-output=html <!-- or html-cp, serial, serial-append; if not set, defaults to "html" -->
-Dcoverage-classes=loaded <!-- or a "*" expression for class names; if not set, measures all production code classes -->
<!-- Other properties, if needed: -->
-Dcoverage-outputDir=my-dir <!-- default: target/coverage-report -->
-Dcoverage-srcDirs=sources <!-- default: all "src" directories -->
-Dcoverage-excludes=some.package.* <!-- default: empty -->
-Dcoverage-check=80 <!-- default: no checks -->
</argLine>
</configuration>
</plugin>