SpringBootTest单元测试组件
一、快速开始
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.3.2-RELEASE</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
一、SpringbootTest 使用Junit4开发
1、添加依赖
2、单元测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootApplicationTests {
@Autowired
private UserService userService;
@Test
public void testAddUser() {
userService.add(user);
}
}
3、JUnit4相关注解
- @BeforeClass:针对所有测试,只执行一次,且必须为static void
- @Before:初始化方法,执行当前测试类的每个测试方法前执行。
- @Test:测试方法,在这里可以测试期望异常和超时时间
- timeout = 2000 测试超时
- expected=Exception.Class 测试抛出的异常
- @After:释放资源,执行当前测试类的每个测试方法后执行
- @AfterClass:针对所有测试,只执行一次,且必须为static void
- @Ignore:忽略的测试方法(只在测试类的时候生效,单独执行该测试方法无效)
二、SpringbootTest 使用Junit5开发
1、添加依赖
2、单元测试类
- @ExtendWith(SpringExtension.class)指定测试的引擎类
- @TestMethodOrder(MethodOrderer.Alphanumeric.class)指定测试的顺序
- @SpringBootTest 指定测试的启动类
- args = “–app.test=one” 指定参数
- classes 指定启动类,可以是多个
- value 指定配置属性
- properties 指定配置属性,和value相同
- webEnvironment 指定web环境,
- MOCK 此值为默认值,该类型提供一个mock环境,此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web端口
- RANDOM_PORT 启动一个真实的web服务,监听一个随机端口
- DEFINED_PORT 启动一个真实的web服务,监听一个定义好的端口(从配置中读取)。
- NONE 启动一个非web的ApplicationContext,既不提供mock环境,也不提供真是的web服务
- @Test 指定测试方法
@Slf4j
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
@SpringBootTest(classes = {VipThinkICodeCourseApplication.class})
public class LearnerCourseServiceTest {
@Autowired
private LearnerCourseService learnerCourseService;
@Test
public void getLearnerCourse() {
try {
List<CouseSchedulVo> couseSchedulers = learnerCourseService.getCouseSchedulers(219772514);
System.out.println(JSONUtil.toJsonPrettyStr(couseSchedulers));
} catch (Exception e) {
log.error("单元测试出现异常!", e);
Assertions.assertTrue(true);
}
}
}
3、Junit5相关注解
(1)执行顺序
- @BeforeEach:在每个单元测试方法执行前都执行一遍
- @BeforeAll:在每个单元测试方法执行前执行一遍(只执行一次)
- @AfterAll 表示在所有单元测试之后执行
- @AfterEach 表示在每个单元测试之后执
(2)其他特性
- @DisplayName(“商品入库测试”):用于指定单元测试的名称
- @Disabled:当前单元测试置为无效,即单元测试时跳过该测试
- @RepeatedTest(n):重复性测试,即执行n次
- @ParameterizedTest:参数化测试,
- @ValueSource(ints = {1, 2, 3}):参数化测试提供数据
- @Timeout(value = 3,unit = TimeUnit.SECONDS)
4、Assertions断言
//嵌套单元测试
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@ActiveProfiles("uat")
@TestPropertySource(
properties = {
"env=uat"
, "app.id=unification-assets"
, "spring.application.name=unification-assets-cld"
, "eureka.client.register-with-eureka=true"
, "spring.application.name=unification-assets-cld"
, "remote.asset.url=http://uat-member.vipthink.cn"
, "xxl.job.executor.port=28999"
, "tradeorder.feign.url=https://uat-trade-order.app.vipthink.net"
}
)
@AutoConfigureMockMvc
@DisplayName("Junit5单元测试")
public class MockTest {
//....
@Nested
@DisplayName("内嵌订单测试")
class OrderTestClas {
@Test
@DisplayName("取消订单")
void cancelOrder() {
int status = -1;
System.out.println("取消订单成功,订单状态为:"+status);
}
}
}
//参数化测试
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
@DisplayName("参数化测试")
void paramTest(int a) {
assertTrue(a > 0 && a < 4);
}
//重复测试
@RepeatedTest(3)
@DisplayName("重复测试")
void repeatedTest() {
System.out.println("调用");
}
//组合测试
@Test
@DisplayName("测试组合断言")
void testAll() {
assertAll("测试item商品下单",
() -> {
//模拟用户余额扣减
assertTrue(1 < 2, "余额不足");
},
() -> {
//模拟item数据库扣减库存
assertTrue(3 < 4);
},
() -> {
//模拟交易流水落库
assertNotNull(new Object());
}
);
}
//测试超时
@Test
@Timeout(value = 3,unit = TimeUnit.SECONDS)
@DisplayName("超时测试")
void timeoutTest() {
System.out.println("超时测试");
}
三、高级特性
1、切片测试
@RunWith(SpringRunner.class)
@WebMvcTest(IndexController.class)
public class SpringBootTest {
@Autowired
private MockMvc mvc;
@Test
public void testExample() throws Exception {
//groupManager访问路径
//param传入参数
MvcResult result=mvc.perform(MockMvcRequestBuilders
.post("/groupManager")
.param("pageNum","1")
.param("pageSize","10"))
.andReturn();
MockHttpServletResponse response = result.getResponse();
String content = response.getContentAsString();
List<JtInfoDto> jtInfoDtoList = GsonUtils.toObjects(content,
new TypeToken<List<JtInfoDto>>() {}.getType());
for(JtInfoDto infoDto : jtInfoDtoList){
System.out.println(infoDto.getJtCode());
}
}
}
2、注解的使用
(1)配置类型
- @TestComponent:该注解是另一种@Component,在语义上用来指定某个Bean是专门用于测试的。该注解适用于测试代码和正式混合在一起时,不加载被该注解描述的Bean,使用不多。
- @TestConfiguration:该注解是另一种@TestComponent,它用于补充额外的Bean或覆盖已存在的Bean。在不修改正式代码的前提下,使配置更加灵活。
- @TypeExcludeFilters:用来排除@TestConfiguration和@TestComponent。适用于测试代码和正式代码混合的场景,使用不多。
- @OverrideAutoConfiguration:可用于覆盖@EnableAutoConfiguration,与ImportAutoConfiguration结合使用,以限制所加载的自动配置类。在不修改正式代码的前提下,提供了修改配置自动配置类的能力。
- @PropertyMapping:定义@AutoConfigure*注解中用到的变量名称,例如在@AutoConfigureMockMvc中定义名为spring.test.mockmvc.webclient.enabled的变量。一般不使用。
- 使用@SpringBootApplication启动测试或者生产代码,被@TestComponent描述的Bean会自动被排除掉。如果不是则需要向@SpringBootApplication添加TypeExcludeFilter。
(2)mock类型的注解
- @MockBean:用于mock指定的class或被注解的属性。
- @MockBeans:使@MockBean支持在同一类型或属性上多次出现。
- @SpyBean:用于spy指定的class或被注解的属性。
- @SpyBeans:使@SpyBean支持在同一类型或属性上多次出现
(4)启动类注解
@SpringBootTest之外的注解都是用来进行切面测试的,他们会默认导入一些自动配。
- @SpringBootTest 自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,默认web环境为MOCK,不监听任务端口
- @DataRedisTest 测试对Redis操作,自动扫描被@RedisHash描述的类,并配置Spring Data Redis的库
- @DataJpaTest 测试基于JPA的数据库操作,同时提供了TestEntityManager替代JPA的EntityManager
- @DataJdbcTest 测试基于Spring Data JDBC的数据库操作
- @JsonTest 测试JSON的序列化和反序列化
- @WebMvcTest 测试Spring MVC中的controllers
- @WebFluxTest 测试Spring WebFlux中的controllers
- @RestClientTest 测试对REST客户端的操作
- @DataLdapTest 测试对LDAP的操作
- @DataMongoTest 测试对MongoDB的操作
- @DataNeo4jTest 测试对Neo4j的操作
四、控制层接口测试
@Slf4j
@SpringBootTest
@AutoConfigureMockMvc //自动配置 MockMvc
class DemoControllerTest1 {
@Autowired
private MockMvc mock;
//测试get提交
@Test
void findById() throws Exception {
MvcResult mvcResult = mock.perform(MockMvcRequestBuilders.get("/demo/find/123").characterEncoding("UTF-8"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value("S0000")) //比较json结果值
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
response.setCharacterEncoding("UTF-8");
String content = response.getContentAsString();
log.info("findById Result: {}", content);
}
//测试post提交
@Test
void save() throws Exception {
MvcResult mvcResult = mock.perform(
MockMvcRequestBuilders.post("/demo/save")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.characterEncoding("UTF-8")
.content(JSONObject.toJSONString(new DemoVo("Hello World")))
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value("S0000")) //比较json结果值
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
response.setCharacterEncoding("UTF-8");
String content = response.getContentAsString();
log.info("save Result: {}", content);
}
}
参考:https://www.cnblogs.com/myitnews/p/12330297.html
https://www.cnblogs.com/haixiang/p/13812363.html
附录:其他单元测试组件整理
一、JMH 多线程性能测试组件
需要IDEA安装JMH插件JMH plugin v1.0.3,
配置compiler -> Annotation Processors -> Enable Annotation Processing
参考:https://www.cnblogs.com/bestzhang/p/10082119.html
1、添加依赖
2、JMH注解
## 3.常用注解说明
###3.1 @BenchmarkMode(Mode.All)
Mode有:- Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用” (thrpt,参加第5点)
- AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。(avgt)
- SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”(simple)
- SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。(ss)
### 3.2 @OutputTimeUnit(TimeUnit.MILLISECONDS)
统计单位, 微秒、毫秒 、分、小时、天
### 3.3 @State
可参:JMHFirstBenchmark.java
类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。由于JMH允许多线程同时执行测试,不同的选项含义如下:
Scope.Thread:默认的State,每个测试线程分配一个实例;
Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
Scope.Group:每个线程组共享一个实例;
### 3.4 @Benchmark
很重要的方法注解,表示该方法是需要进行 benchmark 的对象。和@test 注解一致
### 3.5 @Setup
方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
### 3.6 @TearDown (Level)
方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。
(Level) 用于控制 @Setup,@TearDown 的调用时机,默认是 Level.Trial。
Trial:每个benchmark方法前后;
Iteration:每个benchmark方法每次迭代前后;
Invocation:每个benchmark方法每次调用前后,谨慎使用,需留意javadoc注释;
### 3.7 @Param
@Param注解接收一个String数组 ,
可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。
可参:JMHFirstBenchmark.java
## 4 Options常用选项
### 4.1 include
benchmark 所在的类的名字,这里可以使用正则表达式对所有类进行匹配。
参考:SecondBenchmark.java
### 4.2 fork
JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个@Benchmark方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。
fork选项也可以通过方法注解以及启动参数来设置。
### 4.3 warmupIterations
预热次数,每次默认1秒。
### 4.4 measurementIterations
实际测量的迭代次数,每次默认1秒。
### 4.5 Group
方法注解,可以把多个 benchmark 定义为同一个 group,则它们会被同时执行,譬如用来模拟生产者-消费者读写速度不一致情况下的表现。
### 4.6 Threads
每个fork进程使用多少条线程去执行你的测试方法,默认值是Runtime.getRuntime().availableProcessors()。
3、测试类示例:
@Slf4j
@BenchmarkMode(Mode.AverageTime)// 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MICROSECONDS)// 输出结果的时间粒度为微秒
@State(Scope.Benchmark) // 每个测试线程一个实例
public class JMHFirstBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkState {
volatile double x = Math.PI;
}
@State(Scope.Thread)
public static class ThreadState {
volatile double x = Math.PI;
}
@Benchmark
public void measureUnshared(ThreadState state) {
state.x++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("measureUnshared:"+ state.x);
}
@Benchmark
public void measureShared(BenchmarkState state) {
state.x++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("measureShared:"+ state.x);
}
public static void main(String[] args) throws RunnerException {
// 可以通过注解
Options opt = new OptionsBuilder()
.include(JMHFirstBenchmark.class.getSimpleName())
.warmupIterations(3) // 预热3次
.measurementIterations(2).measurementTime(TimeValue.valueOf("1s"))
.threads(10) // 10线程并发
.forks(2)
.build();
new Runner(opt).run();
}
}
@BenchmarkMode(Mode.All)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class SecondBenchmark {
@Param({"100000"})
private int length;
private int[] numbers;
private Calculator singleThreadCalc;
private Calculator multiThreadCalc;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SecondBenchmark.class.getSimpleName()) // .include("JMHF.*") 可支持正则
.forks(0)
.warmupIterations(2)
.measurementIterations(2).threads(10)
.build();
new Runner(opt).run();
}
@Benchmark
public long singleThreadBench() {
return singleThreadCalc.sum(numbers);
}
@Benchmark
public long multiThreadBench() {
return multiThreadCalc.sum(numbers);
}
@Setup(Level.Trial)
public void prepare() {
int n = length;
numbers =new int[n];
for (int i=0;i<n;i++){
numbers[i]=i;
}
singleThreadCalc = new SinglethreadCalculator();
multiThreadCalc = new MultithreadCalculator(Runtime.getRuntime().availableProcessors());
}
@TearDown
public void shutdown() {
singleThreadCalc.shutdown();
multiThreadCalc.shutdown();
}
}
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
public class ThirdBenchmark {
@State(Scope.Group)
public static class BenchmarkState {
volatile double x = Math.PI;
}
@Benchmark
@Group("custom")
@GroupThreads(10)
public void read(BenchmarkState state) {
state.x++;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThirdBenchmark.read: "+ state.x);
}
@Benchmark
@Group("custom")
public void book(BenchmarkState state) {
state.x++;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThirdBenchmark.book: "+ state.x);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(ThirdBenchmark.class.getSimpleName()) // .include("JMHF.*") 可支持正则
.forks(0)
.warmupIterations(0)
.measurementIterations(2).measurementTime(TimeValue.valueOf("10ms")).threads(5)
.build();
new Runner(opt).run();
}
}
二、使用JMockit作为测试工具
参考:http://jmockit.cn/index.htm
1、快速入门
1、添加依赖
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.36</version>
<scope>test</scope>
</dependency>
2、@Mocked注解
- @Mocked注解可以通过调用构造函数实例化
- @Injectable 也是告诉 JMockit生成一个Mocked对象,但@Injectable只是针对其修饰的实例
public class ProgramConstructureTest {
// 这是一个测试属性
@Mocked
Locale locale
@Test
public void testMocked() {
// 静态方法不起作用了,返回了null
Assert.assertTrue(Locale.getDefault() == null);
// 非静态方法(返回类型为String)也不起作用了,返回了null
Assert.assertTrue(locale.getCountry() == null);
// 自已new一个,也同样如此,方法都被mock了
Locale chinaLocale = new Locale("zh", "CN");
Assert.assertTrue(chinaLocale.getCountry() == null);
}
//模拟Mock参数
@Test
public void testMocked(@Mocked Locale locale) {
// 静态方法不起作用了,返回了null
Assert.assertTrue(Locale.getDefault() == null);
// 非静态方法(返回类型为String)也不起作用了,返回了null
Assert.assertTrue(locale.getCountry() == null);
// 自已new一个,也同样如此,方法都被mock了
Locale chinaLocale = new Locale("zh", "CN");
Assert.assertTrue(chinaLocale.getCountry() == null);
}
//使用@Injectable实现参数实例化
@Test
public void testInjectable(@Injectable Locale locale) {
// 静态方法不mock
Assert.assertTrue(Locale.getDefault() != null);
// 非静态方法(返回类型为String)也不起作用了,返回了null,但仅仅限于locale这个对象
Assert.assertTrue(locale.getCountry() == null);
// 自已new一个,并不受影响
Locale chinaLocale = new Locale("zh", "CN");
Assert.assertTrue(chinaLocale.getCountry().equals("CN"));
}
}
@Tested注解
- @Tested 类似于注入,但是内部由Mock帮助实现注入
//@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));
}
}
@Capturing注解
- @Capturing 模拟某些类,并跳过其中的类似校验
public class CapturingTest {
// 测试用户ID
long testUserId = 123456l;
// 权限检验类,可能是人工写的
IPrivilege privilegeManager1 = new IPrivilege() {
@Override
public boolean isAllow(long userId) {
if (userId == testUserId) {
return false;
}
return true;
}
};
// 权限检验类,可能是JDK动态代理生成。我们通常AOP来做权限校验。
IPrivilege privilegeManager2 = (IPrivilege) Proxy.newProxyInstance(IPrivilege.class.getClassLoader(),
new Class[] { IPrivilege.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if ((long) args[0] == testUserId) {
return false;
}
return true;
}
});
// 有Cautring情形
@Test
public void testCaputring(@Capturing IPrivilege privilegeManager) {
// 加上了JMockit的API @Capturing,
// JMockit会帮我们实例化这个对象,它除了具有@Mocked的特点,还能影响它的子类/实现类
new Expectations() {
{
// 对IPrivilege的所有实现类录制,假设测试用户有权限
privilegeManager.isAllow(testUserId);
result = true;
}
};
// 不管权限校验的实现类是哪个,这个测试用户都有权限
Assert.assertTrue(privilegeManager1.isAllow(testUserId));
Assert.assertTrue(privilegeManager2.isAllow(testUserId));
}
// 没有Cautring情形
@Test
public void testWithoutCaputring() {
// 不管权限校验的实现类是哪个,这个测试用户没有权限
Assert.assertTrue(!privilegeManager1.isAllow(testUserId));
Assert.assertTrue(!privilegeManager2.isAllow(testUserId));
}
}
Expectations录制脚本
//Expectations对外部类的mock对象进行录制
public class ExpectationsTest {
@Mocked
Calendar cal;
@Test
public void testRecordOutside() {
new Expectations() {
{
// 对cal.get方法进行录制,并匹配参数 Calendar.YEAR
cal.get(Calendar.YEAR);
result = 2016;// 年份不再返回当前小时。而是返回2016年
// 对cal.get方法进行录制,并匹配参数 Calendar.HOUR_OF_DAY
cal.get(Calendar.HOUR_OF_DAY);
result = 7;// 小时不再返回当前小时。而是返回早上7点钟
}
};
Assert.assertTrue(cal.get(Calendar.YEAR) == 2016);
Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
// 因为没有录制过,所以这里月份返回默认值 0
Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 0);
}
}
//通过Expectations对其构造函数mock对象进行录制
public class ExpectationsConstructorTest2 {
// 把类传入Expectations的构造函数
@Test
public void testRecordConstrutctor1() {
Calendar cal = Calendar.getInstance();
// 把待Mock的类传入Expectations的构造函数,可以达到只mock类的部分行为的目的
new Expectations(Calendar.class) {
{
// 只对get方法并且参数为Calendar.HOUR_OF_DAY进行录制
cal.get(Calendar.HOUR_OF_DAY);
result = 7;// 小时永远返回早上7点钟
}
};
Calendar now = Calendar.getInstance();
// 因为下面的调用mock过了,小时永远返回7点钟了
Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
// 因为下面的调用没有mock过,所以方法的行为不受mock影响,
Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
}
// 把对象传入Expectations的构造函数
@Test
public void testRecordConstrutctor2() {
Calendar cal = Calendar.getInstance();
// 把待Mock的对象传入Expectations的构造函数,可以达到只mock类的部分行为的目的,但只对这个对象影响
new Expectations(cal) {
{
// 只对get方法并且参数为Calendar.HOUR_OF_DAY进行录制
cal.get(Calendar.HOUR_OF_DAY);
result = 7;// 小时永远返回早上7点钟
}
};
// 因为下面的调用mock过了,小时永远返回7点钟了
Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
// 因为下面的调用没有mock过,所以方法的行为不受mock影响,
Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
// now是另一个对象,上面录制只对cal对象的影响,所以now的方法行为没有任何变化
Calendar now = Calendar.getInstance();
// 不受mock影响
Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == (new Date()).getHours());
// 不受mock影响
Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
}
}
使用MockUp方法
//Mockup & @Mock的Mock方式
public class MockUpTest {
@Test
public void testMockUp() {
// 对Java自带类Calendar的get方法进行定制
// 只需要把Calendar类传入MockUp类的构造函数即可
new MockUp<Calendar>(Calendar.class) {
// 想Mock哪个方法,就给哪个方法加上@Mock, 没有@Mock的方法,不受影响
@Mock
public int get(int unit) {
if (unit == Calendar.YEAR) {
return 2017;
}
if (unit == Calendar.MONDAY) {
return 12;
}
if (unit == Calendar.DAY_OF_MONTH) {
return 25;
}
if (unit == Calendar.HOUR_OF_DAY) {
return 7;
}
return 0;
}
};
// 从此Calendar的get方法,就沿用你定制过的逻辑,而不是它原先的逻辑。
Calendar cal = Calendar.getInstance(Locale.FRANCE);
Assert.assertTrue(cal.get(Calendar.YEAR) == 2017);
Assert.assertTrue(cal.get(Calendar.MONDAY) == 12);
Assert.assertTrue(cal.get(Calendar.DAY_OF_MONTH) == 25);
Assert.assertTrue(cal.get(Calendar.HOUR_OF_DAY) == 7);
// Calendar的其它方法,不受影响
Assert.assertTrue((cal.getFirstDayOfWeek() == Calendar.MONDAY));
}
}
Verifications 验证方法
//Verification的使用
public class VerificationTest {
@Test
public void testVerification() {
// 录制阶段
Calendar cal = Calendar.getInstance();
new Expectations(Calendar.class) {
{
// 对cal.get方法进行录制,并匹配参数 Calendar.YEAR
cal.get(Calendar.YEAR);
result = 2016;// 年份不再返回当前小时。而是返回2016年
cal.get(Calendar.HOUR_OF_DAY);
result = 7;// 小时不再返回当前小时。而是返回早上7点钟
}
};
// 重放阶段
Calendar now = Calendar.getInstance();
Assert.assertTrue(now.get(Calendar.YEAR) == 2016);
Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
// 验证阶段
new Verifications() {
{
Calendar.getInstance();
// 限定上面的方法只调用了1次,当然也可以不限定
times = 1;
cal.get(anyInt);
// 限定上面的方法只调用了2次,当然也可以不限定
times = 2;
}
};
}
}
2、其他例子
1、使用Mock类的各种方法
//用Expectations来mock类
public class ClassMockingByExpectationsTest {
@Test
public void testClassMockingByExpectation() {
AnOrdinaryClass instanceToRecord = new AnOrdinaryClass();
new Expectations(AnOrdinaryClass.class) {
{
// mock静态方法
AnOrdinaryClass.staticMethod();
result = 10;
// mock普通方法
instanceToRecord.ordinaryMethod();
result = 20;
// mock final方法
instanceToRecord.finalMethod();
result = 30;
// native, private方法无法用Expectations来Mock
}
};
AnOrdinaryClass instance = new AnOrdinaryClass();
Assert.assertTrue(AnOrdinaryClass.staticMethod() == 10);
Assert.assertTrue(instance.ordinaryMethod() == 20);
Assert.assertTrue(instance.finalMethod() == 30);
// 用Expectations无法mock native方法
Assert.assertTrue(instance.navtiveMethod() == 4);
// 用Expectations无法mock private方法
Assert.assertTrue(instance.callPrivateMethod() == 5);
}
@BeforeClass
// 加载AnOrdinaryClass类的native方法的native实现
public static void loadNative() throws Throwable {
JNITools.loadNative();
}
}
2、使用MockUp模拟类的方法
public class ClassMockingByMockUpTest {
// AnOrdinaryClass的MockUp类,继承MockUp即可
public static class AnOrdinaryClassMockUp extends MockUp<AnOrdinaryClass> {
// Mock静态方法
@Mock
public static int staticMethod() {
return 10;
}
// Mock普通方法
@Mock
public int ordinaryMethod() {
return 20;
}
@Mock
// Mock final方法
public final int finalMethod() {
return 30;
}
// Mock native方法
@Mock
public int navtiveMethod() {
return 40;
}
// Mock private方法
@Mock
private int privateMethod() {
return 50;
}
}
@Test
public void testClassMockingByMockUp() {
new AnOrdinaryClassMockUp();
AnOrdinaryClass instance = new AnOrdinaryClass();
// 静态方法被mock了
Assert.assertTrue(AnOrdinaryClass.staticMethod() == 10);
// 普通方法被mock了
Assert.assertTrue(instance.ordinaryMethod() == 20);
// final方法被mock了
Assert.assertTrue(instance.finalMethod() == 30);
// native方法被mock了
Assert.assertTrue(instance.navtiveMethod() == 40);
// private方法被mock了
Assert.assertTrue(instance.callPrivateMethod() == 50);
}
@BeforeClass
// 加载AnOrdinaryClass类的native方法的native实现
public static void loadNative() throws Throwable {
JNITools.loadNative();
}
}
3、Mock实例
//mock实例
public class InstanceMockingByExpectationsTest {
@Test
public void testInstanceMockingByExpectation() {
AnOrdinaryClass instance = new AnOrdinaryClass();
// 直接把实例传给Expectations的构造函数即可Mock这个实例
new Expectations(instance) {
{
// 尽管这里也可以Mock静态方法,但不推荐在这里写。静态方法的Mock应该是针对类的
// mock普通方法
instance.ordinaryMethod();
result = 20;
// mock final方法
instance.finalMethod();
result = 30;
// native, private方法无法用Expectations来Mock
}
};
Assert.assertTrue(AnOrdinaryClass.staticMethod() == 1);
Assert.assertTrue(instance.ordinaryMethod() == 20);
Assert.assertTrue(instance.finalMethod() == 30);
// 用Expectations无法mock native方法
Assert.assertTrue(instance.navtiveMethod() == 4);
// 用Expectations无法mock private方法
Assert.assertTrue(instance.callPrivateMethod() == 5);
}
@BeforeClass
// 加载AnOrdinaryClass类的native方法的native实现
public static void loadNative() throws Throwable {
JNITools.loadNative();
}
}
4、Mock接口
//用Expectations来mock接口
public class InterfaceMockingByExpectationsTest {
// 通过@Injectable,让JMockit帮我们生成这个接口的实例,
// 一般来说,接口是给类来依赖的,我们给待测试的类加上@Tested,就可以让JMockit做依赖注入。详细见JMockit基础的章节
@Injectable
AnOrdinaryInterface anOrdinaryInterface;
@Test
public void testInterfaceMockingByExpectation() {
// 录制
new Expectations() {
{
anOrdinaryInterface.method1();
result = 10;
anOrdinaryInterface.method2();
result = 20;
}
};
Assert.assertTrue(anOrdinaryInterface.method1() == 10);
Assert.assertTrue(anOrdinaryInterface.method2() == 20);
}
}
//用MockUp来mock接口
public class InterfaceMockingByMockUpTest {
@Test
public void testInterfaceMockingByMockUp() {
// 手工通过MockUp创建这个接口的实例
AnOrdinaryInterface anOrdinaryInterface = new MockUp<AnOrdinaryInterface>(AnOrdinaryInterface.class) {
// 对方法Mock
@Mock
public int method1() {
return 10;
}
@Mock
public int method2() {
return 20;
}
}.getMockInstance();
Assert.assertTrue(anOrdinaryInterface.method1() == 10);
Assert.assertTrue(anOrdinaryInterface.method2() == 20);
}
}
Mock-SpringBean
//用Expectations来Mock Spring Bean
@ContextConfiguration(locations = { "/META-INF/applicationContext1.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringBeanMockingByExpectationsTest {
// 注入Spring Bean,Mock这个实例,就达到了Mock Spring Bean的目的
@Resource
AnOrdinaryClass anOrdinaryBean;
@Test
public void testSpringBeanMockingByExpectation() {
// 直接把实例传给Expectations的构造函数即可Mock这个实例
new Expectations(anOrdinaryBean) {
{
// 尽管这里也可以Mock静态方法,但不推荐在这里写。静态方法的Mock应该是针对类的
// mock普通方法
anOrdinaryBean.ordinaryMethod();
result = 20;
// mock final方法
anOrdinaryBean.finalMethod();
result = 30;
// native, private方法无法用Expectations来Mock
}
};
Assert.assertTrue(AnOrdinaryClass.staticMethod() == 1);
Assert.assertTrue(anOrdinaryBean.ordinaryMethod() == 20);
Assert.assertTrue(anOrdinaryBean.finalMethod() == 30);
// 用Expectations无法mock native方法
Assert.assertTrue(anOrdinaryBean.navtiveMethod() == 4);
// 用Expectations无法mock private方法
Assert.assertTrue(anOrdinaryBean.callPrivateMethod() == 5);
}
@BeforeClass
// 加载AnOrdinaryClass类的native方法的native实现
public static void loadNative() throws Throwable {
JNITools.loadNative();
}
}
//用MockUp来Mock Spring Bean
@ContextConfiguration(locations = { "/META-INF/applicationContext1.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringBeanMockingByMockUpTest {
// 注入Spring Bean,Mock这个实例,就达到了Mock Spring Bean的目的
@Resource
AnOrdinaryClass anOrdinaryBean;
@Test
public void testSpringBeanMockingByMockUp() {
// 静态方法被mock了
Assert.assertTrue(AnOrdinaryClass.staticMethod() == 10);
// 普通方法被mock了
Assert.assertTrue(anOrdinaryBean.ordinaryMethod() == 20);
// final方法被mock了
Assert.assertTrue(anOrdinaryBean.finalMethod() == 30);
// native方法被mock了
Assert.assertTrue(anOrdinaryBean.navtiveMethod() == 40);
// private方法被mock了
Assert.assertTrue(anOrdinaryBean.callPrivateMethod() == 50);
}
@BeforeClass
public static void beforeClassMethods() throws Throwable {
loadNative();
// 必须在Spring容器初始化前,就对Spring Bean的类做MockUp
addMockUps();
}
// 加载AnOrdinaryClass类的native方法的native实现
public static void loadNative() throws Throwable {
JNITools.loadNative();
}
// 对AnOrdinaryClass的Class
public static class AnOrdinaryClassMockUp extends MockUp<AnOrdinaryClass> {
// Mock静态方法
@Mock
public static int staticMethod() {
return 10;
}
// Mock普通方法
@Mock
public int ordinaryMethod() {
return 20;
}
@Mock
// Mock final方法
public final int finalMethod() {
return 30;
}
// Mock native方法
@Mock
public int navtiveMethod() {
return 40;
}
// Mock private方法
@Mock
private int privateMethod() {
return 50;
}
}
// 添加MockUp
public static void addMockUps() {
new AnOrdinaryClassMockUp();
}
}
MockDubbo
//dubbo消费bean Mock
@SuppressWarnings({ "unchecked", "rawtypes" })
@ContextConfiguration(locations = { "/META-INF/dubbo-consumer.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class DubboConsumerBeanMockingTest {
// 这里要@BeforeClass,因为要抢在spring加载dubbo前,对dubbo的消费工厂bean
// ReferenceBean进行mock,不然dubbo可能因为连上不zk或无法找不
// 服务的提供者等原因而无法初始化的,进而,单元测试运行不下去
@BeforeClass
public static void mockDubbo() {
// 你准备mock哪个消费bean
// 比如要对dubbo-consumber.xml里配置的cn.jmockit.demos.MailService这个消费bean进行mock
Map<String, Object> mockMap = new HashMap<String, Object>();
mockMap.put("cn.jmockit.demos.MailService", new MockUp(MailService.class) {
// 在这里书写对这个消费bean进行mock的mock逻辑,想mock哪个方法,就自行添加,注意方法一定要加上@Mock注解哦
@Mock
public boolean sendMail(long userId, String content) {
// 单元测试时,不需要调用邮件服务器发送邮件,这里统一mock邮件发送成功
return true;
}
}.getMockInstance());
// 如果要Mock其它的消费bean,自行添加,mockMap.put.....如上
new DubboConsumerBeanMockUp(mockMap);
}
// 现在你使用的dubbo消费bean就是本地mock过的了,并不是指向远程dubbo服务的bean了
@Resource
MailService mailService;
@Test
public void testSendMail() {
long userId = 123456;
String content = "test mail content";
Assert.isTrue(mailService.sendMail(userId, content));
}
}
//dubbo消费bean的MockUp(伪类)
@SuppressWarnings("rawtypes")
public class DubboConsumerBeanMockUp extends MockUp<ReferenceBean> {
// 自定义的消费bean mock对象
private Map<String, Object> mockMap;
public DubboConsumerBeanMockUp() {
}
public DubboConsumerBeanMockUp(Map<String, Object> mockMap) {
this.mockMap = mockMap;
}
// 对ReferenceBean的getObject方法的Mock
@SuppressWarnings("unchecked")
@Mock
public Object getObject(Invocation inv) throws Exception {
ReferenceBean ref = inv.getInvokedInstance();
String interfaceName = ref.getInterface();
Object mock = mockMap.get(interfaceName);
if (mock != null) {
return mock;
}
return (new MockUp(Class.forName(interfaceName)) {
}).getMockInstance();
}
}
Mock-消息生产者
//RocketMQ消息生产者 Mock
public class RocetMQProducerMockingTest {
// 把RocketMQ的生产者mock
@BeforeClass
public static void mockRocketMQ() {
new RocketMQProducerMockUp();
}
@Test
public void testSendRocketMQMessage() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("test_producer");
producer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
producer.start();
for (int i = 0; i < 20; i++) {
Message msg = new Message("testtopic", "TagA", ("Hello " + i).getBytes());
// 因为mq生产者已经mock,所以消息并不会真正的发送,即使nameServer连不上,也不影响单元测试的运行
SendResult result = producer.send(msg);
Assert.isTrue(result.getSendStatus() == SendStatus.SEND_OK);
Assert.isTrue(result.getMsgId() != null);
}
producer.shutdown();
}
}
//MQ消息发送者 的MockUp(伪类)
public class RocketMQProducerMockUp extends MockUp<DefaultMQProducer> {
@Mock
void init() throws MQClientException {
// 构造函数也什么都不做
}
@Mock
void start() throws MQClientException {
// 启动,什么都不做
}
@Mock
void shutdown() {
// 关闭,也什么都不做
}
@Mock
List<MessageQueue> fetchPublishMessageQueues(final String topic) throws MQClientException {
// 欺骗调用方,返回不存在的消息队列,因为消息并不会真正发送嘛
List<MessageQueue> queues = new ArrayList<MessageQueue>();
MessageQueue q = new MessageQueue();
q.setBrokerName("testbrokername");
q.setQueueId(1);
q.setTopic("testtopic");
queues.add(q);
return queues;
}
// 下面是对各个send方法的mock,都返回消息成功结果
@Mock
SendResult send(final Message msg)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
SendResult send(final Message msg, final long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
void send(final Message msg, final SendCallback sendCallback)
throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void send(final Message msg, final SendCallback sendCallback, final long timeout)
throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void sendOneway(final Message msg) throws MQClientException, RemotingException, InterruptedException {
}
@Mock
SendResult send(final Message msg, final MessageQueue mq)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
SendResult send(final Message msg, final MessageQueue mq, final long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback)
throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback, long timeout)
throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void sendOneway(final Message msg, final MessageQueue mq)
throws MQClientException, RemotingException, InterruptedException {
}
@Mock
SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
SendResult send(final Message msg, final MessageQueueSelector selector, final Object arg, final long timeout)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
return newSuccessSendResult();
}
@Mock
void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback)
throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void send(final Message msg, final MessageQueueSelector selector, final Object arg, final SendCallback sendCallback,
final long timeout) throws MQClientException, RemotingException, InterruptedException {
sendCallback.onSuccess(this.newSuccessSendResult());
}
@Mock
void sendOneway(final Message msg, final MessageQueueSelector selector, final Object arg)
throws MQClientException, RemotingException, InterruptedException {
}
@Mock
TransactionSendResult sendMessageInTransaction(final Message msg, final LocalTransactionExecuter tranExecuter,
final Object arg) throws MQClientException {
return newTransactionSendResult();
}
private TransactionSendResult newTransactionSendResult() {
TransactionSendResult success = new TransactionSendResult();
success.setSendStatus(SendStatus.SEND_OK);
success.setMsgId(UUID.randomUUID().toString());
MessageQueue q = new MessageQueue();
q.setBrokerName("testbrokername");
q.setQueueId(1);
q.setTopic("testtopic");
success.setMessageQueue(q);
success.setLocalTransactionState(LocalTransactionState.COMMIT_MESSAGE);
return success;
}
private SendResult newSuccessSendResult() {
SendResult success = new SendResult();
success.setSendStatus(SendStatus.SEND_OK);
success.setMsgId(UUID.randomUUID().toString());
MessageQueue q = new MessageQueue();
q.setBrokerName("testbrokername");
q.setQueueId(1);
q.setTopic("testtopic");
success.setMessageQueue(q);
return success;
}
}
实现代码覆盖率可视化
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<disableXmlReport>true</disableXmlReport>
<argLine>-Dcoverage-metrics=all</argLine>
</configuration>
</plugin>
3、高级用法
Mock实体类的构造函数
//Mock构造函数&初始代码块
public class ConstructorAndBlockMockingTest {
// AnOrdinaryClassWithBlock的MockUp类,继承MockUp即可
public static class AnOrdinaryClassWithBlockMockUp extends MockUp<AnOrdinaryClassWithBlock> {
// Mock构造函数和初始代码块, 函数名$init就代表类的构造函数
@Mock
public void $init(int i) {
}
// Mock静态初始代码块,, 函数名$init就代表类的静态代码块
@Mock
public void $clinit() {
}
}
@Test
public void testClassMockingByMockUp() {
new AnOrdinaryClassWithBlockMockUp();
AnOrdinaryClassWithBlock instance = new AnOrdinaryClassWithBlock(10);
// 静态初始代码块被mock了
Assert.assertTrue(AnOrdinaryClassWithBlock.j == 0);
// 构造函数和初始代码块被mock
Assert.assertTrue(instance.getI() == 0);
}
}
一个类多个实例
//一个类多个实例的Mock
public class OneClassManyInstanceMockingTest {
// Mock方法一: 把实例传入Expectations的构造函数。适用场景: 只Mock实例的部分方法,对实例的类的其它实例不产生影响
@Test
public void testMocking1() {
AnOrdinaryClass instance1 = new AnOrdinaryClass();
AnOrdinaryClass instance2 = new AnOrdinaryClass();
// 直接把实例传给Expectations的构造函数即可Mock这个实例
new Expectations(instance1, instance2) {
{
instance1.ordinaryMethod();
result = 20;
instance2.ordinaryMethod();
result = 200;
}
};
AnOrdinaryClass instance3 = new AnOrdinaryClass();
// instance1的ordinaryMethod被Mock了
Assert.assertTrue(instance1.ordinaryMethod() == 20);
// instance2的ordinaryMethod被Mock了
Assert.assertTrue(instance2.ordinaryMethod() == 200);
// instance3不受影响。
Assert.assertTrue(instance3.ordinaryMethod() == 2);
}
// Mock方法二: 用@Mocked。适用场景: 类的所实例都需要Mock,但不同实例也能保留不同的Mock逻辑
@Test
public void testMocking2(@Mocked AnOrdinaryClass instance1, @Mocked AnOrdinaryClass instance2) {
new Expectations() {
{
instance1.ordinaryMethod();
result = 20;
instance2.ordinaryMethod();
result = 200;
}
};
AnOrdinaryClass instance3 = new AnOrdinaryClass();
// instance1的ordinaryMethod被Mock了
Assert.assertTrue(instance1.ordinaryMethod() == 20);
// instance2的ordinaryMethod被Mock了
Assert.assertTrue(instance2.ordinaryMethod() == 200);
// instance3受@Mock的影响。@Mock会把类的所有方法都Mock,返回类型为基本数据类型的返回0
Assert.assertTrue(instance3.ordinaryMethod() == 0);
}
// Mock方法二: 用@Injectable。适用场景: 不是类的所实例都需要Mock,不同实例也能保留不同的Mock逻辑
@Test
public void testMocking3(@Injectable AnOrdinaryClass instance1, @Injectable AnOrdinaryClass instance2) {
new Expectations() {
{
instance1.ordinaryMethod();
result = 20;
instance2.ordinaryMethod();
result = 200;
}
};
AnOrdinaryClass instance3 = new AnOrdinaryClass();
// instance1的ordinaryMethod被Mock了
Assert.assertTrue(instance1.ordinaryMethod() == 20);
// instance2的ordinaryMethod被Mock了
Assert.assertTrue(instance2.ordinaryMethod() == 200);
// instance3不受@Injectable的影响。因为@Injectable只影响某个实例
Assert.assertTrue(instance3.ordinaryMethod() == 2);
}
}
Mock泛型
//Mock泛型
public class GenericMockUpTest {
@Test
public <T extends AnOrdinaryInterface> void testMockUp() {
// 通过传给MockUp一个泛型类型变量,MockUp可以对这个类型变量的上限进行Mock,以后所有这个上限的方法调用,就会走Mock后的逻辑
new MockUp<T>() {
@Mock
public int method1() {
return 10;
}
@Mock
public int method2() {
return 20;
}
};
// 自定义一个AnOrdinaryInterface的实现
AnOrdinaryInterface instance1 = new AnOrdinaryInterface() {
@Override
public int method1() {
return 1;
}
@Override
public int method2() {
return 1;
}
};
// 再定义一个AnOrdinaryInterface的实现
AnOrdinaryInterface instance2 = new AnOrdinaryInterface() {
@Override
public int method1() {
return 2;
}
@Override
public int method2() {
return 2;
}
};
// 发现自定义的实现没有被作用,而是被Mock逻辑替代了
Assert.assertTrue(instance1.method1() == 10);
Assert.assertTrue(instance2.method1() == 10);
Assert.assertTrue(instance1.method2() == 20);
Assert.assertTrue(instance2.method2() == 20);
}
// 其实用@Capturing也是一样的效果
@Test
public <T extends AnOrdinaryInterface> void sameEffect(@Capturing AnOrdinaryInterface instance) {
new Expectations() {
{
instance.method1();
result = 10;
instance.method2();
result = 20;
}
};
// 自定义一个AnOrdinaryInterface的实现
AnOrdinaryInterface instance1 = new AnOrdinaryInterface() {
@Override
public int method1() {
return 1;
}
@Override
public int method2() {
return 1;
}
};
// 再定义一个AnOrdinaryInterface的实现
AnOrdinaryInterface instance2 = new AnOrdinaryInterface() {
@Override
public int method1() {
return 2;
}
@Override
public int method2() {
return 2;
}
};
// 发现自定义的实现没有被作用,而是被Mock逻辑替代了
Assert.assertTrue(instance1.method1() == 10);
Assert.assertTrue(instance2.method1() == 10);
Assert.assertTrue(instance1.method2() == 20);
Assert.assertTrue(instance2.method2() == 20);
}
}
//Mock方法中还可以调用老方法
public class InvocationMockUpTest {
@Test
public void testMockUp() {
// 对Java自带类Calendar的get方法进行定制
new MockUp<Calendar>(Calendar.class) {
// 申明参数invocation,表示老方法的调用
@Mock
public int get(Invocation invocation, int unit) {
// 只希望时间是早上7点
if (unit == Calendar.HOUR_OF_DAY) {
return 7;
}
// 其它时间(年份,月份,日,分,秒均不变)
return invocation.proceed(unit);
}
};
Calendar now = Calendar.getInstance();
// 只有小时变成Mock方法
Assert.assertTrue(now.get(Calendar.HOUR_OF_DAY) == 7);
// 其它的还是走老的方法
Assert.assertTrue(now.get(Calendar.MONTH) == (new Date()).getMonth());
Assert.assertTrue(now.get(Calendar.DAY_OF_MONTH) == (new Date()).getDate());
}
}
//同一方法返回时序结果
public class ReturnSequenceResultInOneMethodTest {
// 一个类所有实例的某个方法,返回时序结果。
// 适用场景:每次调用,期望返回的数据不一样。比如从tcp数据流中拿数据
@Test
public void testIfMethodOfClass() {
AnOrdinaryClass instance = new AnOrdinaryClass();
new Expectations(AnOrdinaryClass.class) {
{
instance.ordinaryMethod();
// 对类AnOrdinaryClass所有实例调用ordinaryMethod方法时,依次返回1,2,3,4,5
result = new int[] { 1, 2, 3, 4, 5 };
}
};
AnOrdinaryClass instance1 = new AnOrdinaryClass();
Assert.assertTrue(instance1.ordinaryMethod() == 1);
Assert.assertTrue(instance1.ordinaryMethod() == 2);
Assert.assertTrue(instance1.ordinaryMethod() == 3);
Assert.assertTrue(instance1.ordinaryMethod() == 4);
Assert.assertTrue(instance1.ordinaryMethod() == 5);
// 因为在上面录制脚本中,只录制了5个结果,当大于5时,就以最后一次结果为准
Assert.assertTrue(instance1.ordinaryMethod() == 5);
Assert.assertTrue(instance1.ordinaryMethod() == 5);
}
// 与上述不一样的地方,仅仅是对某一个实例的返回值进行录制
@Test
public void testIfMethodOfIntance() {
AnOrdinaryClass instance = new AnOrdinaryClass();
new Expectations(instance) {
{
instance.ordinaryMethod();
// 对实例instance调用ordinaryMethod方法时,依次返回1,2,3,4,5
result = new int[] { 1, 2, 3, 4, 5 };
}
};
// 只影响了instance这个实例
Assert.assertTrue(instance.ordinaryMethod() == 1);
Assert.assertTrue(instance.ordinaryMethod() == 2);
Assert.assertTrue(instance.ordinaryMethod() == 3);
Assert.assertTrue(instance.ordinaryMethod() == 4);
Assert.assertTrue(instance.ordinaryMethod() == 5);
// 因为在上面录制脚本中,只录制了5个结果,当大于5时,就以最后一次结果为准
Assert.assertTrue(instance.ordinaryMethod() == 5);
Assert.assertTrue(instance.ordinaryMethod() == 5);
// 类AnOrdinaryClass的其它实例并不会受到影响
AnOrdinaryClass instance1 = new AnOrdinaryClass();
// ordinaryMethod这个方法本来就返回2
Assert.assertTrue(instance1.ordinaryMethod() == 2);
Assert.assertTrue(instance1.ordinaryMethod() == 2);
}
// 下面以对tcp数据流返回数据为例子,进行mock
@Test
public void testInputStreamSequence() {
try {
// 依据地址创建URL
URL url = new URL("http://jmockit.cn");
// 获得urlConnecion
URLConnection connection = url.openConnection();
// 打开连接
connection.connect();
InputStream in = connection.getInputStream();
//现在对jmockit.cn服务器返回的数据进行mock
new Expectations(in) {
{
in.read();
// -1表示流数据结束了
result = new int[] { 1, 2, 3, 4, 5, -1 };
}
};
// 读取jmockit.cn服务器返回的内容,如果没有上面的mock,返回将是实际的内容
Assert.assertTrue(in.read() == 1);
Assert.assertTrue(in.read() == 2);
Assert.assertTrue(in.read() == 3);
Assert.assertTrue(in.read() == 4);
Assert.assertTrue(in.read() == 5);
Assert.assertTrue(in.read() == -1);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 定制返回结果
public class DeletgateResultTest {
@SuppressWarnings("rawtypes")
@Test
public void testDelegate() {
new Expectations(SayHello.class) {
{
SayHello instance = new SayHello();
instance.sayHello(anyString, anyInt);
result = new Delegate() {
// 当调用sayHello(anyString, anyInt)时,返回的结果就会匹配delegate方法,
// 方法名可以自定义,当入参和返回要与sayHello(anyString, anyInt)匹配上
@SuppressWarnings("unused")
String delegate(Invocation inv, String who, int gender) {
// 如果是向动物鹦鹉Polly问好,就说hello,Polly
if ("Polly".equals(who)) {
return "hello,Polly";
}
// 其它的入参,还是走原有的方法调用
return inv.proceed(who, gender);
}
};
}
};
SayHello instance = new SayHello();
Assert.isTrue(instance.sayHello("david", ISayHello.MALE).equals("hello Mr david"));
Assert.isTrue(instance.sayHello("lucy", ISayHello.FEMALE).equals("hello Mrs lucy"));
Assert.isTrue(instance.sayHello("Polly", ISayHello.FEMALE).equals("hello,Polly"));
}
}
//通过在mock时做AOP测试方法的时间性能
public class MethodCostPerformanceTest {
// 测试SayHello类每个方法的时间性能
@Test
public void testSayHelloCostPerformance() {
// 把方法的调用时间记录到costMap中。key是方法名称,value是平均调用时间
Map<String, Long> costMap = new HashMap<String, Long>();
new MockUp<SayHello>() {
@Mock
public Object $advice(Invocation invocation) {
long a = System.currentTimeMillis();
Object result = invocation.proceed();
long cost = System.currentTimeMillis() - a;
// 把某方法的平均调用时间记录下来
String methodName = invocation.getInvokedMember().getName();
Long preCost = costMap.get(methodName);
if (preCost == null) {
costMap.put(methodName, cost);
} else {
costMap.put(methodName, (preCost + cost) / 2);
}
return result;
}
};
SayHello sayHello = new SayHello();
sayHello.sayHello("david", ISayHello.MALE);
sayHello.sayHello("lucy", ISayHello.FEMALE);
for (Iterator<String> iterator = costMap.keySet().iterator(); iterator.hasNext();) {
String methodName = (String) iterator.next();
// 期望每个方法的调用时间不超过20ms
Assert.isTrue(costMap.get(methodName) < 20);
}
}
}
//级联Mock:对Mock对象的方法返回再进行Mock
public class CascadingMockTest {
@Test
public void testCascading() {
//下面以Mock EntityManager.createNativeQuery的返回对象为例
EntityManager entityManager = new MockUp<EntityManager>() {
@Mock
public Query createNativeQuery(String sqlString) {
//返回一个自定义Query的匿名内部类就可以
return new Query() {
@Override
public List getResultList() {
//在这里书写你的Mock逻辑,
// mock的返回数据
List<Object> mockResult = new ArrayList<Object>();
mockResult.add(new Object());
mockResult.add(new Object());
return mockResult;
}
@Override
public Object getSingleResult() {
// TODO Auto-generated method stub
return null;
}
@Override
public int executeUpdate() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Query setMaxResults(int maxResult) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setFirstResult(int startPosition) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setHint(String hintName, Object value) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(String name, Object value) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(String name, Date value, TemporalType temporalType) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(String name, Calendar value, TemporalType temporalType) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(int position, Object value) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(int position, Date value, TemporalType temporalType) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setParameter(int position, Calendar value, TemporalType temporalType) {
// TODO Auto-generated method stub
return null;
}
@Override
public Query setFlushMode(FlushModeType flushMode) {
// TODO Auto-generated method stub
return null;
}
};
}
}.getMockInstance();
String yoursql = "";
//可以发现,我们成功地对entityManager.createNativeQuery方法返回值进行了Mock
Assert.assertTrue(entityManager.createNativeQuery(yoursql).getResultList().size() == 2);
}
Jacoco代码覆盖率用法:
<!-- 代码测试覆盖率 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.8</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
执行:mvn clean install -Dmaven.test.failure.ignore=true
完成后可以看到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1sSFl3Xm-1692499904848)(typora-img/1335795-20190226124935424-542142469.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v5A18q5m-1692499904849)(typora-img/1335795-20190226125148502-1552011628.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avWNNjWL-1692499904850)(typora-img/1335795-20190226125740827-891053062.png)]
三、TestableMock 单元测试 Mock 工具
git:
https://github.com/alibaba/testable-mock
文档:
https://alibaba.github.io/testable-mock/#/
四、github的工具junitperf
https://github.com/houbb/junitperf
1、添加依赖
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>junitperf</artifactId>
<version>2.0.7</version>
</dependency>
2、普通例子
- @JunitPerfConfig 指定测试时的属性配置
- @JunitPerfRequire 指定测试时需要达到的要求
public class HelloWorldTest {
//间隔1000秒
@JunitPerfConfig(duration = 1000)
public void helloTest() throws InterruptedException {
Thread.sleep(100);
System.out.println("Hello Junit5");
}
/**
* 2个线程运行。
* 准备时间:1000ms
* 运行时间: 2000ms
*/
@JunitPerfConfig(threads = 2, warmUp = 1000, duration = 2000)
public void junitPerfConfigTest() throws InterruptedException {
System.out.println("junitPerfConfigTest");
Thread.sleep(200);
}
/**
* 配置:2个线程运行。准备时间:1000ms。运行时间: 2000ms。
* 要求:最快不可低于 210ms, 最慢不得低于 250ms, 平均不得低于 225ms, 每秒运行次数不得低于 4 次。
* 20% 的数据不低于 220ms, 50% 的数据不得低于 230ms;
*/
@JunitPerfConfig(threads = 2, warmUp = 1000, duration = 2000)
@JunitPerfRequire(min = 210, max = 250, average = 225, timesPerSecond = 4, percentiles = {"20:220", "50:230"})
public void junitPerfConfigTest() throws InterruptedException {
System.out.println("junitPerfConfigTest");
Thread.sleep(200);
}
//打印测试结果
@JunitPerfConfig(duration = 1000, reporter = {HtmlReporter.class,
ConsoleReporter.class})
public void helloTest() throws InterruptedException {
Thread.sleep(100);
System.out.println("Hello Junit5");
}
}
3、