当前位置: 首页 > 工具软件 > JMockit > 使用案例 >

Jmockit 基础

杜良骏
2023-12-01

JMockit程序结构

package com.jmokit;

import com.Jmokit;
import mockit.Expectations;
import mockit.Mocked;
import mockit.Verifications;
import org.junit.Assert;
import org.junit.Test;

public class JomckitConstructureTest {
//    测试属性,被mock掉,并不会真正执行这个属性里的方法。而是对期待的执行有一个result
//    如果没有mock掉,就会真的执行这个javabean里的方法。mock掉,就不执行方法,而是返回期待的result
//    这个期待,并不是对真正执行后的期待,而是coder给的期待
    @Mocked
    public Jmokit jmokit = new Jmokit();
//	  测试方法
    @Test
    public void test1() {
//        录制(Record)  录制代码块
        new Expectations() {
//            这一段为什么放在代码块里,如果不放在{}离,就没法识别jmokit
            {
                jmokit.sayHello();
                //期望返回的result
                result = "hello jjj";
            }
        };
//		重放测试逻辑
        String msg = jmokit.sayHello();
        Assert.assertTrue(msg.equals("hello jjj"));
//		验证代码块
        new Verifications() {
            {
                jmokit.sayHello();
                times = 1;
            }
        };
    }

    @Test
    public void test2(@Mocked Jmokit jmokit1) {
        new Expectations() {
            {
                jmokit1.sayHello();
                result = "1111";
            }
        };

        String msg = jmokit1.sayHello();
        Assert.assertTrue(msg.equals("1111"));
    }
}

Jmockit 的程序结构分为, 测试属性/ 测试参数, 测试方法, 测试方法中又包含:录制代码块,重放测试逻辑, 验证代码块

  • 测试属性/测试参数:
    可以用于修饰测试属性的注解API有:@Mocked, @Tested, @Injectable, @Capturing
    @Mocked:表示这个测试属性,他的实例化,属性赋值,方法调用的返回值全部由JMockit来接管,接管后,测试属性jmokit的方法sayHello与Jmokit类中定义的就不一样了,而是由录制脚本来定义了。那么被@Mockit修饰后的测试属性,Jmockit到底对他做了什么?
    测试参数是测试方法中的参数,仅作用于当前方法。Junit中是不允许测试方法带有参数的,但是加了Jmockit 的注解API(@Mocked, @Tested, @Injectable, @Capturing) 则是可以的。
  • 测试方法
    Record, Replay, Verification 是JMockit测试程序的主要结构。
    • 录制代码块Record:
      先录制某个类/对象的方法的调用,在当输入什么时,返回什么。
    • 重放测试逻辑
      把录制的内容,在重新执行一遍
    • 验证代码块
      验证执行的次数等。

API

@Mocked

当@Mocked修饰一个类时:

  • Jmockit帮助coder实例化这个对象,不用担心它为null
  • 它是怎么帮助coder实例化对象的,为什么静态方法没有执行,因为没有类加载吗?
  • 静态方法不起作用,返回null,静态方法没有加载
  • 非静态方法也不起作用了,返回值为null
  • 自己再创建一个该类的对象,也是如此,coder创建的对象的方法也被mock了。
  • 那么在什么场景下要用这个注解?
    当@Mocked修饰一个接口/抽象类时:
  • 同样JMockit会进行实例化。
  • 如果接口方法返回值为String,则mock后返回null, 如果接口方法返回值为基本类型,mock后返回0.
  • 如果接口方法返回值为引用类型非String, 那么这个返回值对象不为空,且这个对象也是Mocked对象。
    所以综上,被@Mocked注解修饰的接口/类,JMocked会帮助生成一个Mocked对象,这个对象方法包含静态方法,返回值是默认值。
    当我们测试程序依赖某个接口时,把这个接口@Mocked,JMocked就会帮助我们生成这个接口的实例。

@Injectable

Injectable 也是告诉JMockit生成一个Mocked对象,但是只针对其修饰的实例。且注解没有影响到该类的静态方法以及构造函数。

  • 被修饰的类的静态方法和构造函数没有被影响,会正常执行
  • 被修饰的类型成员方法其返回值受到影响。
    - 被修饰的类,只影响到注解的实例。coder再new一个实例不会受到影响。

@Tested

此注解通常一起使用。
场景实例:在买家下订单时,电商后台需要验证卖家身份是否合法
官网代码如下:

// 邮件服务类,用于发邮件
public interface MailService {
 
    /**
     * 发送邮件
     * 
     * @param userId
     *            邮件接受人id
     * @param content
     *            邮件内容
     * @return 发送成功了,就返回true,否则返回false
     */
    public boolean sendMail(long userId, String content);
}```

```java
// 用户身份校验  
public interface UserCheckService {
 
    /**
     * 校验某个用户是否是合法用户
     * 
     * @param userId
     *            用户ID
     * @return 合法的就返回true,否则返回false 
     */
    public boolean check(long userId);
}//订单服务类 ,用于下订单```

```java
public class OrderService {
    // 邮件服务类,用于向某用户发邮件。
    MailService mailService;
    // 用户身份校验类,用于校验某个用户是不是合法用户
    @Resource
    UserCheckService userCheckService;
 
    // 构造函数, 这个构造函数,是有参构造函数
    public OrderService(MailService mailService) {
        this.mailService = mailService;
    }
 
    /**
     * 下订单
     * 
     * @param buyerId
     *            买家ID
     * @param itemId
     *            商品id
     * @return 返回 下订单是否成功
     */
    public boolean submitOrder(long buyerId, long itemId) {
        // 先校验用户身份
        if (!userCheckService.check(buyerId)) {
            // 用户身份不合法
            return false;
        }
        // 下单逻辑代码,
        // 省略...
        // 下单完成,给买家发邮件
        if (!this.mailService.sendMail(buyerId, "下单成功")) {
            // 邮件发送成功
            return false;
        }
        return true;
    }
}```

```java
 //@Tested与@Injectable搭配使用
public class TestedAndInjectable {
   //@Tested修饰的类,表示是我们要测试对象,在这里表示,我想测试订单服务类。JMockit也会帮我们实例化这个测试对象
   @Tested
   OrderService orderService;
   //测试用户ID
   long testUserId = 123456l;
   //测试商品id
   long testItemId = 456789l;
 
   // 测试注入方式
   @Test
   public void testSubmitOrder(@Injectable MailService mailService, 
     @Injectable UserCheckService userCheckService) {
    new Expectations() {
       {
         // 当向testUserId发邮件时,假设都发成功了
         mailService.sendMail(testUserId, anyString);
         result = true;
        // 当检验testUserId的身份时,假设该用户都是合法的
         userCheckService.check(testUserId);
        result = true;
         }
         };
    // JMockit帮我们实例化了mailService了,并通过OrderService的构造函数,注入到orderService对象中。 
    //JMockit帮我们实例化了userCheckService了,并通过OrderService的属性,注入到orderService对象中。 
    Assert.assertTrue(orderService.submitOrder(testUserId, testItemId));
    }
}

@Tested注解只能修饰类,如果修饰interface,生成的对象是null
@Tested表示被测试对象。如果该对象没有赋值,JMockit会去实例化它,若@Tested的构造函数有参数,
则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,
不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。
注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象。
当然,我们的测试程序要尽量避免这种情况出现。因为给哪个测试属性/测试参数加@Injectable,是人为控制的。

@Capturing 主要作用于子类/实现类的Mock

当我们是使用代理类,或者子类来实现其父接口的具体方法的时候。@Capturing注解除了使该class/interface 具有@Mocked注解的属性,还会影响到他的子类和实现类

Expectations

两种使用方式:

  • 通过引用外部类的@Mocked, @Injectable, @Capturing来录制
  • 通过构建函数注入类/对象来录制
    如果只希望mock掉类的某一个方法。
    把Mock的类传入Expectations的构造函数。可以达到只mock类的部分行为

MockUp 与 @Mock

这个mock方式比较实用于对一些通用类的mock,以减少大量重复的new Expectations{{}}

 类似资料: