JMockit使用与UT-Generator插件

最近工作需要,要求开发代码单元测试覆盖率达到多少多少,一个个写单元测试实在麻烦,于是使用了一款插件UT-Generator,还是省去了不少工作量的,这里简单描述一下。

JMockit使用

JMockit的基本使用可以通过JMockit中文网,或者通过我这整理的内容了解,如下:
https://github.com/eussi/Notes/blob/master/note_java/JMockit/JMockit.md

内容目录

  1. JMockit是什么
  2. 配置
    maven pom.xml配置
    JUnit4.x及以下用户特别注意事项
    JMockit Coverage配置
  3. 程序结构
  4. API
    @Mocked
    @Injectable与@Mocked
    @Capturing
    Expectations
    MockUp & @Mock
    Verifications
  5. 常见用法
    Mock类
    Mock实例
    Mock接口
    用JMockit做代码覆盖率
  6. 高级用法
    Mock构造函数&初始化代码块
    Mock一类多实例
    Mock泛型(类型变量)
    Mock方法中调用老方法
    同一方法调用返回时序结果
    定时返回结果
    在Mock时做AOP
    级联Mock
  7. JMockit原理剖析
    JMockit架构
    JMockit启动过程
    Exepectations录制原理
    MockUp的Mock原理
    各Mock注解的Mock逻辑
  8. UT自动生成插件
    特性
    安装
    使用
    生成UT
  9. 相关代码

下面主要介绍下UT-Generator的简单使用

UT自动生成插件

IDEA中有一款插件,UT-Generator,可以方便的生成使用JMockit的单元测试代码

特性

  • 自动生成被测类所有声明方法的测试代码

  • 自动生成被测类依赖的Mock代码,并与测试代码分离

  • 支持增量(新增类和方法)生成

  • 不会覆盖已有测试代码

  • 简单易用,生成速度快

  • 配置灵活多变

安装

IDEA: File -> Setting -> Plugins -> 搜索UT-Generator -> 安装后重启

添加插件配置,如图:

在这里插入图片描述

使用

生成代码时注意添加依赖

        <!-- 先声明jmockit的依赖 -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.37</version>
            <scope>test</scope>
        </dependency>
        <!-- 再声明junit的依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

测试类如下:

// 邮件服务类,用于发邮件
public interface MailService {

	/**
	 * 发送邮件
	 * 
	 * @param userId
	 *            邮件接受人id
	 * @param content
	 *            邮件内容
	 * @return 发送成功了,就返回true,否则返回false
	 */
	public boolean sendMail(long userId, String content);
}
// 用户身份校验  
public interface UserCheckService {

	/**
	 * 校验某个用户是否是合法用户
	 * 
	 * @param userId
	 *            用户ID
	 * @return 合法的就返回true,否则返回false 
	 */
	public boolean check(long userId);
}
//订单服务类 ,用于下订单 
public class OrderService {
	// 邮件服务类,用于向某用户发邮件。
	MailService mailService;
	// 用户身份校验类,用于校验某个用户是不是合法用户
	@Resource
	UserCheckService userCheckService;

	// 构造函数
	public OrderService(MailService mailService) {
		this.mailService = mailService;
	}

	/**
	 * 下订单
	 * 
	 * @param buyerId
	 *            买家ID
	 * @param itemId
	 *            商品id
	 * @return 返回 下订单是否成功
	 */
	public boolean submitOrder(long buyerId, long itemId) {
		// 先校验用户身份
		if (!userCheckService.check(buyerId)) {
			// 用户身份不合法
			return false;
		}
		// 下单逻辑代码,
		// 省略...
		// 下单完成,给买家发邮件
		if (!this.mailService.sendMail(buyerId, "下单成功")) {
			// 邮件发送成功
			return false;
		}
		return true;
	}
}

生成UT

生成前保证代码能够正常编译通过,在Project或Module的任意目录或文件上右击,选中Generate UT,如下点击了上面展示的类文件:

在这里插入图片描述

此时生成两个类:

/**
 * Generated by UTGenerator Plugin on 2020/10/18 14:40.
 */
public class OrderServiceAutoMock {

    //Mock constructor of test class OrderService, should be called before instantiation.
    static class ConstructorMock {
        static void mockMailService() {
            new MockUp<OrderService>() {
                @Mock
                public void $init(MailService mailService) {

                }
            };
        }

    }

    /**
     * Creates a new instance of a given class, with any instance fields left uninitialized,
     * if the given class is abstract or an interface, then a concrete class is created, with
     * empty implementations for its methods.
     */
    static <T> T getMockInstance(Class<? extends T> clazz) {
        return Deencapsulation.newUninitializedInstance(clazz);
    }

    //Sets the value of a non-accessible field on a given object.
    static void setField(Object objectWithField, String fieldName, Object fieldValue) {
        Deencapsulation.setField(objectWithField, fieldName, fieldValue);
    }

    //Sets the value of a non-accessible static field on a given class.
    static void setStaticField(Class<?> classWithStaticField, String fieldName, Object fieldValue) throws Exception {
        Field field = classWithStaticField.getDeclaredField(fieldName);
        field.setAccessible(true);
        if (Modifier.isFinal(field.getModifiers())) {
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        }
        field.set(null, fieldValue);
    }

    //Gets the value of a non-accessible field from a given object.
    static <T> T getField(Object objectWithField, String fieldName) {
        return Deencapsulation.getField(objectWithField, fieldName);
    }

    //Gets the value of a non-accessible static field defined in a given class.
    static <T> T getStaticField(Class<?> classWithStaticField, String fieldName) {
        return Deencapsulation.getField(classWithStaticField, fieldName);
    }

    /**
     * Dependency mock code of test method {@link OrderService#submitOrder}
     */
    static class SubmitOrderMock {
        static <T extends UserCheckService> void mockUserCheckService(final Map<Integer, Boolean> checkMockValue) {
            new MockUp<T>() {
                int checkCount = 0;
                boolean lastCheckMockValue;

                @Mock
                public boolean check(long param0) {
                    boolean result = lastCheckMockValue;
                    if (checkMockValue.containsKey(checkCount)) {
                        result = checkMockValue.get(checkCount);
                        lastCheckMockValue = result;
                    }
                    checkCount++;
                    return result;
                }

            };
        }

        static <T extends MailService> void mockMailService(final Map<Integer, Boolean> sendMailMockValue) {
            new MockUp<T>() {
                int sendMailCount = 0;
                boolean lastSendMailMockValue;

                @Mock
                public boolean sendMail(long param0, String param1) {
                    boolean result = lastSendMailMockValue;
                    if (sendMailMockValue.containsKey(sendMailCount)) {
                        result = sendMailMockValue.get(sendMailCount);
                        lastSendMailMockValue = result;
                    }
                    sendMailCount++;
                    return result;
                }

            };
        }

    }

}
/**
 * Generated by UTGenerator Plugin on 2020/10/18 14:40.
 */
@RunWith(JMockit.class)
public class OrderServiceAutoTest extends OrderServiceAutoMock {

    @BeforeClass
    public static void setUpClass() {
        //operation before all tests
    }

    @AfterClass
    public static void tearDownClass() {
        //operation after all tests
    }

    //test class instance, can be reused
    private OrderService testInstance;

    @Before
    public void setUp() {
        //operation before each test
        MailService mailService = null;
        testInstance = new OrderService(mailService);
    }

    @After
    public void tearDown() {
        //operation after each test
    }

    @Test
    public void testSubmitOrder() {
        //TODO: review the generated mock code, modify the given mock values
        //mock all dependencies of method OrderService#submitOrder
        //mock class UserCheckService
        boolean z = false;
        Map<Integer, Boolean> checkMockValue = new HashMap<>();
        checkMockValue.put(0, z);
        SubmitOrderMock.mockUserCheckService(checkMockValue);

        //mock class MailService
        boolean z1 = false;
        Map<Integer, Boolean> sendMailMockValue = new HashMap<>();
        sendMailMockValue.put(0, z1);
        SubmitOrderMock.mockMailService(sendMailMockValue);

        //TODO: review the generated test code, modify the given method call arguments and assert clause
        //prepare arguments and call the test method
        long buyerId = 0L;
        long itemId = 0L;

        boolean actualResult = testInstance.submitOrder(buyerId, itemId);
        assertFalse(actualResult);
    }

}

前者是Mock代码,后者是测试代码,通过之前的介绍,看懂代码不是问题。

需要注意的是,插件并不是十分的智能,单元测试会出现空指针,类型转换等方面的异常,代码的覆盖率也可能会很低,所以需要在手动调整,达到自己的测试目的。

如上生成的代码,会报如下错误:

java.lang.NullPointerException
	at com.eussi.basic.OrderService.submitOrder(OrderService.java:29)
	at com.eussi.basic.OrderServiceAutoTest.testSubmitOrder(OrderServiceAutoTest.java:61)

可以手动修改OrderServiceAutoTest成如下代码,使单元测试通过:

/**
 * Generated by UTGenerator Plugin on 2020/10/18 14:40.
 */
@RunWith(JMockit.class)
public class OrderServiceAutoTest extends OrderServiceAutoMock {

    @BeforeClass
    public static void setUpClass() {
        //operation before all tests
    }

    @AfterClass
    public static void tearDownClass() {
        //operation after all tests
    }

    //test class instance, can be reused
    @Tested
    private OrderService testInstance;
    @Injectable
    MailService mailService;
    @Injectable
    UserCheckService userCheckService;


    @Before
    public void setUp() {
        //operation before each test
        testInstance = new OrderService(mailService);
    }

    @After
    public void tearDown() {
        //operation after each test
    }

    @Test
    public void testSubmitOrder() {
        //TODO: review the generated mock code, modify the given mock values
        //mock all dependencies of method OrderService#submitOrder
        //mock class UserCheckService
        boolean z = false;
        Map<Integer, Boolean> checkMockValue = new HashMap<>();
        checkMockValue.put(0, z);
        SubmitOrderMock.mockUserCheckService(checkMockValue);

        //mock class MailService
        boolean z1 = false;
        Map<Integer, Boolean> sendMailMockValue = new HashMap<>();
        sendMailMockValue.put(0, z1);
        SubmitOrderMock.mockMailService(sendMailMockValue);

        //TODO: review the generated test code, modify the given method call arguments and assert clause
        //prepare arguments and call the test method
        long buyerId = 0L;
        long itemId = 0L;

        boolean actualResult = testInstance.submitOrder(buyerId, itemId);
        assertFalse(actualResult);
    }

}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值