上一篇: cordova快速上手
  目 录
1 什么是mock
2 自动化规则用例介绍
3 参考文献

自动化规则需要依赖于mdb(内存数据加载),但是在开发时,mdb功能并未完全实现。这样在进行单元测试时,由于无法构造mdb对象, 而出现单元测试无法进行的情况。所以决定采用mock测试。下面是自动化规则采用的jmockit简介。

一、什么是mock

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。 在单元测试时,对于一个类,我们仅仅希望测试这个类的功能,而不是同时测试这个类的依赖,这时我们也需要模拟一个对象。

1.1 jmockit简介

JMockit 是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode。相比于其他的一些mock测试框架,他的功能更强大,具体可见stackoverflow上的一个问题的回答。comparison-between-mockito-vs-jmockit

JMockit的测试方式可以通过下面2个途径实现

  • 根据用例的测试路径,测试代码内部逻辑 对于这种情景,可以使用jmockit的基于行为的mock方式。在这种方式中,目的是测试单元测试及其依赖代码的调用过程,验证代码逻辑是否满足 测试路径。 由于被依赖代码可能在自己单测中已测试过,或者难以测试,就需要把这些被依赖代码的逻辑用预定期待的行为替换掉,也就是mock掉, 从而把待测是代码隔离开,这也是单元测试的初衷。 这种方式和白盒测试接近。
  • 根据测试用例的输入输出数据,测试代码是否功能运行正常 对于这种情景,可以使用jmockit基于状态的mock方式。目的是从被测代码的使用角度出发,结合数据的输入输出来检验程序运行的这个正确性。 使用这个方式,需要把被依赖的代码mock掉,实际上相当于改变了被依赖的代码的逻辑。通常在集成测试中,如果有难以调用的外部接口,就通过 这个方式mock掉,模拟外部接口。 这种方式有点像黑盒测试。

1.2 jmockit使用步骤

当你使用Jmockit,你就有了固定的套路来进行mock测试,总结分为如下步骤:

  1. record record,录制,使用new Expectations{}代码块进行录制,在这个代码块里,你可以对期望mock的对象的行为进行定义
  2. replay replay,播放,对录制的mock对象的行为在测试代码中进行调用。
  3. verify verify,验证,使用new Verifications{}代码块中进行校验。在这个代码块里,你可以对mock的对象定义校验。

1.3 一个例子

1.3.1 考虑到一个业务服务类提供了包括如下步骤的业务操作:

  1. 找到需要被操作所需要的持久化的实体
  2. 持久化一个新的实体状态
  3. 发送一个提醒消息给一个有兴趣的部门

第一二步需要使用应用数据库,其通过使用简单的 API 来使用持久化系统。 第三步骤可以通过使用一个第三方的API来发送邮件,例子中是使用 Apache的 通用邮件库。 因此,这个服务类包含了两个独立的依赖,一个是持久化,另外一个是邮件。为了进行业务操作的单元测试来验证恰当的和这些依赖进行合作,我们 使用了模拟API。

package tutorial.domain;

import java.math.*;
import java.util.*;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;

public final class MyBusinessService
{
   private final EntityX data;

   public MyBusinessService(EntityX data) { this.data = data; }

   public void doBusinessOperationXyz() throws EmailException {
      List<EntityX> items =
(1)      find("select item from EntityX item where item.someProperty = ?1", data.getSomeProperty());

      // Compute or obtain from another service a total value for the new persistent entity:
      BigDecimal total = ...
      data.setTotal(total);

(2)   persist(data);

      sendNotificationEmail(items);
   }

   private void sendNotificationEmail(List<EntityX> items) throws EmailException {
      Email email = new SimpleEmail();
      email.setSubject("Notification about processing of ...");
(3)   email.addTo(data.getCustomerEmail());

      // Other e-mail parameters, such as the host name of the mail server, have defaults defined
      // through external configuration.

      String message = buildNotificationMessage(items);
      email.setMsg(message);

(4)   email.send();
   }

   private String buildNotificationMessage(List<EntityX> items) { ... }
}

数据库类包括了一些静态方法和一个私有的构造函数;查找和持久化的方法是必须的,因此我们这里就没有罗列出来 (假设他们是通过 ORM API来实 现的, 比如 JPA)。 因此,我们改如何不需要任何修改已经存在应用代码的情况下进行 “doBusinessOperationXyz” 方法的单元测试呢?JMockit 提供了两种不同的模拟 APIs, 每一种都能满足你的需求。我们将看到每种的写法的不同例子。 在每一个情况下, 一个 JUnit测试例子将会验证单元测试对外部依赖所兴趣的 调用。这些调用都是通过点 (1)-(4) 如上进行标注。

1.3.2 使用期望 API

    package tutorial.domain;
    
    import org.apache.commons.mail.*;
    import static tutorial.persistence.Database.*;
    
    import org.junit.*;
    import org.junit.rules.*;
    import static org.junit.Assert.*;
    import mockit.*;
    
    public final class MyBusinessServiceTest
    {
       @Rule public final ExpectedException thrown = ExpectedException.none();
    
       @Tested final EntityX data = new EntityX(1, "abc", "someone@somewhere.com");
       @Tested(fullyInitialized = true) MyBusinessService businessService;
       @Mocked SimpleEmail anyEmail;
    
       @Test
       public void doBusinessOperationXyz() throws Exception {
          EntityX existingItem = new EntityX(1, "AX5", "abc@xpta.net");
    (1)   persist(existingItem);
    
          businessService.doBusinessOperationXyz();
    
    (2)   assertNotEquals(0, data.getId()); // implies "data" was persisted
    (4)   new Verifications() ;
       }
    
       @Test
       public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception {
          String email = "invalid address";
          data.setCustomerEmail(email);
    (3)   new Expectations() ;
          thrown.expect(EmailException.class);
    
          businessService.doBusinessOperationXyz();
       }
    }

在行为导向的模拟 API像 JMockit 期望那样, 每一个测试都可以被分为三个连续的步骤: 录制,重放,验证。 在期望的录制块中定义录制, 在期望 验证块中进行验证; 重放指的是在测试的内部代码中进行调用。注意,只有模拟方法的调用次数; 隶属于类或者实例中的方法 (或 构造方法)和相关的 模拟属性或者模拟参数将不被模拟,因此不能被录制,更不能进行验证或者是重放了。+

正如上面的实例测试,录制和验证期望可以通过调用所需要方法在一个录制或者是验证的块中来实现 (包括构造方法,及时没有在这里显示)。 这些方法 的参数匹配可以通过 API的属性例如 “any” 和 “anyString”, 或者是通过 API 方法 比如 “withNotNull()”。 在重放中所匹配的调用返回值 (或 抛出的异常)可以在录制阶段通过result属性进行定义。调用次数的约束可以被定义,不仅仅可以在录制阶段也可以在验证阶段, 通过 API 属性赋 值比如 “times = 1”

1.3.2 使用Jmockit进行测试

  1. 在pom中增加依赖 ```
desired version org.jmockit jmockit ${jmockit.version} test
2.启动JUNIT测试任务

## 二、自动化规则用例介绍<a href="usecase"></a>
自动化规则的数据主要从redis中读取,但是在开发时,mdb还未完全实现好,这时使用mock测试很好的体现了优势
### 2.1 模拟mdb对象获取 

@RunWith(JMockit.class) public abstract class BaseTest {

@Mocked protected MMDevice mmDevice; @Mocked protected MMAutorule mmAutorule; @Mocked protected BillManager billManager;

@SuppressWarnings(“rawtypes”) @Before public void init() { new Expectations(RuleUtils.class) { { RuleUtils.getAllRules(anyLong); List autoRules = mockAutoRules(); result = autoRules; } };

new Expectations(RuleUtils.class) {
  {
    RuleUtils.getRuleOper(anyLong);
    result = new Delegate() {
      public List<AutoRuleOper> getRuleOper(long ruleId) {
        return mockRuleOpers(ruleId);
      }
    };
  }
};   } } ```

这里模拟了从内存中读取自动化规则和自动化规则对应的操作。这样就可以验证代码功能的正确性,而不需要一大段代码,来创建 对象,对逻辑的校验形成干扰。

三、参考文献

  • http://jmockit.org/
  • https://stackoverflow.com/questions/4105592/comparison-between-mockito-vs-jmockit-why-is-mockito-voted-better-than-jmockit