springBoot单元测试

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、

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值