Jmockit示例三部曲之一请按照我期望的走

我们首先使用到的第一种mocking API是:JMockit Expectations API

先将官网上的代码贴上,然后一起理解。

package jmockit.tutorial.domain;

import java.util.*;
import org.apache.commons.mail.*;
import jmockit.tutorial.persistence.*;

import org.junit.*;
import mockit.*;

public final class MyBusinessService_ExpectationsAPI_Test
{
   @Mocked(stubOutClassInitialization = true) final Database unused = null;
   @NonStrict SimpleEmail email; // calls to setters are unimportant, so we make it non-strict

   @Test
   public void doBusinessOperationXyz() throws Exception
   {
      final EntityX data = new EntityX(5, "abc", "5453-1");
      final List<EntityX> items = new ArrayList<EntityX>();
      items.add(new EntityX(1, "AX5", "someone@somewhere.com"));

      new Expectations() {{
         // "Database" is mocked strictly here, so the order of these invocations does matter:
(1)      Database.find(withSubstring("select"), (Object[]) any); result = items;
(2)      Database.persist(data);
      }};

      new Expectations() {{
         // Since "email" is a non-strict mock, this invocation can be replayed in any order:
(4)      email.send(); times = 1; // a non-strict invocation requires a constraint if expected
      }};

      new MyBusinessService().doBusinessOperationXyz(data);
   }

   @Test(expected = EmailException.class)
   public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception
   {
      new NonStrictExpectations() {{
(3)      email.addTo((String) withNotNull()); result = new EmailException();

         // If the e-mail address is invalid, sending the message should not be attempted:
         email.send(); times = 0;
      }};

      EntityX data = new EntityX(5, "abc", "5453-1");
      new MyBusinessService().doBusinessOperationXyz(data);
   }
}
在上一节中,被测类的代码中有标注(1)-(4),这就是产品代码中的依赖,我们的任务,就在写单测的时候,将这4个家伙给mock掉。

首先,我们注意到,依赖的类上面都添加了annotation,有@Mocked和@NonStrict,可以被mock的类型包括:an interface, an abstract class, a final/non-final class, an enum type, an annotation type, or even a generic type parameter等(需要注意的是@Mocked不能mock primitive或者array类型,@Injectable是可以的,这个在以后的介绍中会提到),我们说这个被mock的对象是有类型的(或是实体类,或是接口),而实际上,被mock的类型到底是什么确实挺重要的(因为类型不同,就可能需要采用不同的方式进行mock,这个在后面会提到),而这个对象叫什么却并不重要(示例中,Database直接取名unused),因为无论如何,只要被mock了,该对象就不会是null,我们可以通过调试看到。

我们必须了解的是,如果一个类被声明为一种mock类型了,那么他所有的方法都默认被mock了(要看到一个重点,默认,也就是说我们是有办法可以让其中一些方法不被mock的),也就是说,只要一直保持着被mock,那么在产品代码调用这些方法的时候,实际上,这些方法实际的逻辑是绝对不会执行的。这个对于constructors也是一样的。每个类的初始化代码(比如static blocks)在需要的情况下是可以被stub out的,这个在默认情况下是不做的,所以在mock的时候,如果没有明确指定,那么这些static block就会为null(在官网中,作者用了potentially这个词,来说,有可能为null),在上例中,对于Database这个类,就在@Mocked中,明确指定了需要stub out,我认为就是不鸟static block里面的代码了:

@Mocked(stubOutClassInitialization = true) final Database unused = null;
如果在代码中,没有添加stubOutClassInitialization这个属性,那么就会报错,因为初始化这个类的时候,static block会造成错误,报错如下:

java.lang.InternalError: class redefinition failed: invalid class
    at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
因为在很多的时候,我们的final类或者abstract类都有static block,所以在写单测的时候,避免这个错误很重要。

我们应该注意到的是Jmockit Expectations并没有对被mock的类创建子类,实际上,Jmockit直接修改了被mock的类的实现。另一方面,一个interface的方法或者abstract方法
并没有实现可以被修改,在这种情况下,Jmockit依旧可以自动的完成,而不需要我们再做额外的工作。

正如之前提到过,我们可能不希望一个被声明为mock的类中所有的方法都被mock,通过设置@Mocked的属性,可以达到这个效果。

 @Mocked(methods = { "getApp" }, inverse = false)
其中这个methods属性还支持正则表达式的,inverse意为翻转,为false的时候,表示只mock声明中的方法,为true的时候,表示只不mock声明中的方法。

正如示例中所看到的,期望被引用或者允许被引用的方法是在一个Expectations或者NonStrictExpectations块(which is an anonymous inner class containing an instance initialization block)中进行mock的,而使用Expectations还是NonStrictExpectations,是有许多不同的,我们会在后续的很多例子中继续看到区别。

下面我们了解下通常mock的一个流程:

我们在进行mock的时候,是一个recorded阶段,我们进行一些期望,期望啥呢?期望引用的方法,按照我们设定的返回值返回。在被测方法运行的时候,是一个replay阶段,在这个阶段,被测方法就会引用我们mock了的方法,直到运行test case结束。这就是测试执行中典型的record-replay模型。

额外的行为(behavior,这个就是之前说过的behavior-oriented,我们可以设置各种返回值,来与被测类进行交互)是可以并且经常在进行期望的时候指定,通过result这个field,这是最有用的field了。如下

 Database.find(withSubstring("select"), (Object[]) any); result = items;
异常也是可以指定的:

email.addTo((String) withNotNull()); result = new EmailException();
在示例中, 方法withSubstring,以及字段any,允许非常灵活的匹配被mock方法的参数。然而,如果可以在recorded阶段和replay阶段进行精确的匹配是最好不过了。而对于withxYz()方法以及anyAbc字段,还有很丰富的库,可以通过API document中查看。

下面我们先简单的说说Expectations block和NonStrictExpectations block,所有的期望在Expectations block中进行record,那么在test case结束之前,是会隐性的进行严格的验证,验证啥呢?看代码:

         // "Database" is mocked strictly here, so the order of these invocations does matter:
(1)      Database.find(withSubstring("select"), (Object[]) any); result = items;
(2)      Database.persist(data);
首先,Database是一个严格的mocked类型,所以find方法和persist方法是有严格的先后顺序的,如果在test case中,先执行persist方法,后执行find方法,就会报AssertionError,因为这个是Jmockit隐性的进行了断言判断;其次,因为在Expectations block中,find方法和persist方法必须都在test case中被引用过,否则一样会报AssertionError。而如果mock的类型为@NonStrict或者在NonStrictExpectations block中的期望,则只有在显示的强调会引用的次数,才会进行验证。

 // Since "email" is a non-strict mock, this invocation can be replayed in any order:
(4)      email.send(); times = 1; // a non-strict invocation requires a constraint if expected
首先,email是一个non-stict mock类型,所以他的所有被期望过的方法在被引用的时候,是可以无序的,即使是在一个Expectations block中;其次,只有通过times这个字段声明了会被引用的次数,才会进行验证。也就是如果这里声明的times=1,如果在test case中,该方法没有被引用的话,就会报AssertionError,除了有times这个限制,还可以用minTimes和MaxTimes,通过字面就很容易理解,对吧~

通过这个隐性的验证,我们可以自动的将mock的测试模型,由record-replay模型扩展成record-replay-verify模型。只不过,在Expectation API中,不需要我们显示的进行verify阶段。

到这,Jmockit的Expectation API就介绍的差不多了,我十分喜欢官网上的这个例子,尤其是第二个test case,通过times=0,来进行断言,里面包含了明确的测试思想,看来我们需要学习的还很多,也许有很多人对Expectations和NonStrictExpectations还有疑问,为什么需要两个,因为其实在Expectation中,我们也可以使用times来进行验证,其实我也依旧带着疑问,继续学习吧~

(很多概念,中文还是不好解释,所以在很多地方,我直接使用了原文,后续也是一样)

预告Jmockit示例三部曲的第二部——让我验证下你是否走对了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值