首页 > 其他 > 详细

JMockit Turorial 翻译

时间:2016-01-12 01:19:48      阅读:460      评论:0      收藏:0      [点我收藏+]

原文 

 

Mocked types and instances

方法包括构造方法是 mock 的目标。Mocking 提供了把被测代码和它的依赖隔离的机制。我们通过标注测试类的属性和测试方法的参数来提供 mock 对象。mock 对象的类型可以是任意的引用类型:interface, class (包括 final), annotation 和 enum 。 

缺省的,类的所有方法包括构造方法都会被mock, 不管它有private, final, static 等任何修饰符。 同时,类的所有除了Object 之外的所有祖先类也被mock。被mock 的方法调用都会被重定向给 JMockit 处理。

下面的代码演示了使用mock field 和参数的典型方式:

// "Dependency" is mocked for all tests in this test class.
// The "mockInstance" field holds a mocked instance automatically created for use in each test.
@Mocked Dependency mockInstance;

@Test
public void doBusinessOperationXyz(@Mocked final AnotherDependency anotherMock)
{
   ...
   new Expectations() {{ // an "expectation block"
      ...
      // Record an expectation, with a given value to be returned:
      mockInstance.mockedMethod(...); result = 123;
      ...
   }};
   ...
   // Call the code under test.
   ...
   new Verifications() {{ // a "verification block"
      // Verifies an expected invocation:
      anotherMock.save(any); times = 1;
   }};
   ...
}

上例中mock的参数是在JMockit生成之后,由Junit或TestNG的runner传入的。(显然,这就是为什么JMockit 依赖JUnit或TestNG 的原因)

有三种mock标注:

  1. @Mocked
  2. @Injectable , 只 mock 实例方法
  3. @Capturing , 用于扩展被mock的接口和类

使用 2 或 3 , 意味着隐含的使用了1 。 

与其他的mock框架不同, @Mocked 和 @Capturing 缺省mock的是类。这意味着,一旦你这样" @Mocked  SomeClass  ins1" ,  即使你调用构造方法new一个对象 ins2,ins2也是mock对象。


 

Expectations

与其他 mock 框架的不同,定制交互行为只需要在expectation 块内直接调用mock对象的方法,无需特殊的 API.  

 


The record-replay-verify model

任何测试可以被划分到3各阶段: 

@Test
public void someTestMethod()
{
   // 1. Preparation: whatever is required before the code under test can be exercised.
   ...
   // 2. The code under test is exercised, usually by calling a public method.
   ...
   // 3. Verification: whatever needs to be checked to make sure the code exercised by
   //    the test did its job.
   ...
}

这3个阶段也可以称为 Arrange, Act, Assert 。 

当然,Expectations 块或 Verifacations 块也有多个也可以没有。

 


Regular, strict, and non-strict expectations

Expectations 块中记录的调用是常规的, 意味着最低期望。最低期望哪些方法会在 replay 阶段调用,调用的最少次数。但不关心顺序。

StrictExpectation 意味着必须。 replay 阶段调用了哪几个方法,调用的次数和顺序,都必须和 record 阶段一样,否则测试fail (unexpected invocation)。

NonStrictExpectations 则无所谓,也就是相当于不做验证,只是用来指定result。建议仅被用于setup(@Before )阶段。 

以上三者的区别意味着对于 StrictExpectation 不需要 Verifications 块,而NonStrictExpectations 需要。 ??终于明白它为啥叫 Expectations 了??


Recording results for an expectation

在Expectations 块内,对于 non-void 的方法,紧跟在方法调用之后给 result 赋值来指定返回值。

可以通过给 result 赋 Throwable 的实例来让方法抛出异常,这也适用与构造方法。 

对与同一个方法连续调用而返回不同的值或异常,有3种选择可以办到:1,给result 赋值一个数组或list ; 2, 调用 results(Object ...) ; 3, 在同一行连续 对result 赋值多次。 

看下面这个示例:

首先是被测的类:

public class UnitUnderTest
{
(1)private final DependencyAbc abc = new DependencyAbc();

   public void doSomething()
   {
(2)   int n = abc.intReturningMethod();

      for (int i = 0; i < n; i++) {
         String s;

         try {
(3)         s = abc.stringReturningMethod();
         }
         catch (SomeCheckedException e) {
            // somehow handle the exception
         }

         // do some other stuff
      }
   }
}

 

其中 3 个数字是三个测试点,考虑要如何 expect 构造方法及对(3)的连续调用。

测试如下:

@Test
public void doSomethingHandlesSomeCheckedException(@Mocked final DependencyAbc abc) throws Exception
{
   new Expectations() {{
(1)   new DependencyAbc();

(2)   abc.intReturningMethod(); result = 3;

(3)   abc.stringReturningMethod();
      returns("str1", "str2");
      result = new SomeCheckedException();
   }};

   new UnitUnderTest().doSomething();
}

 


Matching invocations to specific instances

@Mocked 会作用与类的所有实例上。 但有时我们需要verify在指定实例上的方法调用,或者我们也同时需要mocked和未 mocked 的实例。@Injectable 用于满足这个要求。

正因为@Injectable 只作用于指定的实例,所有不能mock构造方法和static的方法,也不影响祖先类。

示例:

被测类型:

public final class ConcatenatingInputStream extends InputStream
{
   private final Queue<InputStream> sequentialInputs;
   private InputStream currentInput;

   public ConcatenatingInputStream(InputStream... sequentialInputs)
   {
      this.sequentialInputs = new LinkedList<InputStream>(Arrays.asList(sequentialInputs));
      currentInput = this.sequentialInputs.poll();
   }

   @Override
   public int read() throws IOException
   {
      if (currentInput == null) return -1;

      int nextByte = currentInput.read();

      if (nextByte >= 0) {
         return nextByte;
      }

      currentInput = sequentialInputs.poll();
      return read();
   }
}

 

测试:

@Test
public void concatenateInputStreams(
   @Injectable final InputStream input1, @Injectable final InputStream input2)
   throws Exception
{
   new Expectations() {{
      input1.read(); returns(1, 2, -1);
      input2.read(); returns(3, -1);
   }};

   InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
   byte[] buf = new byte[3];
   concatenatedInput.read(buf);

   assertArrayEquals(new byte[] {1, 2, 3}, buf);
}

 

注意这里必须使用@Injectable 的原因是,我们不想影响基类 InputStream 的 read() 方法, 所以不能用 @Mocked。 

The onInstance(m) constraint

我们使用 @Mocked 或 @Capturing 时,也可以在record 阶段通过  onInstance(mocked) 方法来限制作用范围仅在指定的实例上。 

@Test
public void matchOnMockInstance(@Mocked final Collaborator mock)
{
   new Expectations() {{
      onInstance(mock).getValue(); result = 12;
   }};

   // Exercise code under test with mocked instance passed from the test:
   int result = mock.getValue();
   assertEquals(12, result);

   // If another instance is created inside code under test...
   Collaborator another = new Collaborator();

   // ...we won‘t get the recorded result, but the default one:
   assertEquals(0, another.getValue());
}

 

实际上,如果 @Mocked 或 @Capturing 作用与多个相同的类型变量时,JMockit 会自动推断 record 是仅仅 match 指定的实例的。也就是说,不需要 onInstance() 方法。

Instances created with a given constructor

不同的构造方法产生的实例,record 不同的行为,可以采用以下两种方式之一:

方式一:

@Test
public void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator anyCollaborator)
{
   // Record different behaviors for each set of instances:
   new Expectations() {{
      // One set, instances created with "a value":
      Collaborator col1 = new Collaborator("a value");
      col1.doSomething(anyInt); result = 123;

      // Another set, instances created with "another value":
      Collaborator col2 = new Collaborator("another value");
      col2.doSomething(anyInt); result = new InvalidStateException();
   }};

   // Code under test:
   new Collaborator("a value").doSomething(5); // will return 123
   ...
   new Collaborator("another value").doSomething(0); // will throw the exception
   ...
}

 

注意这里,anyCollaborator 参数并没有用到,但这里 @Mocked 使得类型 Collaborator 可以定制 Expectations。 

方式二:

@Test
public void newCollaboratorsWithDifferentBehaviors(
   @Mocked final Collaborator col1, @Mocked final Collaborator col2)
{
   new Expectations() {{
      // Map separate sets of future instances to separate mock parameters:
      new Collaborator("a value"); result = col1;
      new Collaborator("another value"); result = col2;

      // Record different behaviors for each set of instances:
      col1.doSomething(anyInt); result = 123;
      col2.doSomething(anyInt); result = new InvalidStateException();
   }};

   // Code under test:
   new Collaborator("a value").doSomething(5); // will return 123
   ...
   new Collaborator("another value").doSomething(0); // will throw the exception
   ...
}

 

方式二看起来与record非构造方法的风格一致。而且不影响未mock的实例。 


Flexible matching of argument values

对于 record 和 verify , 很多时候我们并不希望精确的匹配参数值,这时就需要使用弹性匹配。

弹性匹配通过两种方式表达,anyXxx 或 withXx() 方法,他们都是 Expectations 和 Verifacations 的基类 mockit.Invocations 的成员,所以对所有的Expectation 和 Verifaction 可用。

Using the "any" fields for argument matching

三类:原始及其包装类型,anyString , 还有一个 any 。 

@Test
public void someTestMethod(@Mocked final DependencyAbc abc)
{
   final DataItem item = new DataItem(...);

   new Expectations() {{
      // Will match "voidMethod(String, List)" invocations where the first argument is
      // any string and the second any list.
      abc.voidMethod(anyString, (List<?>) any);
   }};

   new UnitUnderTest().doSomething(item);

   new Verifications() {{
      // Matches invocations to the specified method with any value of type long or Long.
      abc.anotherVoidMethod(anyLong);
   }};
}

 

注意, 1, 怎样表达任意的 List ;  2, void 方法没有必要放在Expectations 块中吧。 

Using the "with" methods for argument matching

相比较与anyXx, withXx() 允许传入参数,这就使得他们更灵活一些。 除了预定义的 withXx() 之外,还可以通过 with(Delegate ) 和 withArgThat(org.hamcrest.Matcher )自定义匹配。 

Using the null value to match any object reference

可以使用 null 去替代 any 或 withAny() 来配置任意值,但仅限于至少一个参数使用了 anyXx 或 withXx() ,否则它精确匹配 null 。 另外可用 withNull() 去匹配 null 值。 

Matching values passed through a varargs parameter

如果参数的个数无所谓的话,可以使用 "(Object[]) any" 匹配;

变长参数不能使用值和matcher的组合。 


Specifying invocation count constraints  指定调用次数约束

在Expectations 或 Verifacations 块内都可以指定;指定在对应的方法之后。

通过三个属性指定: times, minTimes 和 maxTimes 。 每个属性只能使用一次。

times=0 或 maxTimes=0 意味着一旦目标方法被调用测试就fail。


 

Explicit verification

 

如前文,对 mock方法调用的次数的验证可以在 Expectations 块中指定,也可以在Verifacations 块中指定。其语法规则和 Expectations 中是一样的。

因为 StrictExpectations 已经严格的表达了验证的要求,不能多也不能少,所以不能再有 Verifacations 块。 

对与 regular的 Expectations , 因为它 record 的是最低要求,如果还有更多要求则需要在 Verifications 阶段补充。比如,不允许一个方法调用过,可以通过 times=0 指定。 

Verification in order  顺序验证

如果需要验证方法 replay 的顺序,则使用 VerificationsInOrder 。 

@Test
public void verifyingExpectationsInOrder(@Mocked final DependencyAbc abc)
{
   // Somewhere inside the tested code:
   abc.aMethod();
   abc.doSomething("blah", 123);
   abc.anotherMethod(5);
   ...

   new VerificationsInOrder() {{
      // The order of these invocations must be the same as the order
      // of occurrence during replay of the matching invocations.
      abc.aMethod();
      abc.anotherMethod(anyInt);
   }};
}

 

注意这里, abc.doSomething() 没有验证,所以它是否出现及出现的顺序都无关紧要。 

Partially ordered verification 局部顺序验证

可以通过  unverifiedInvocations()  隔离方法之间顺序的相关性。

@Mocked DependencyAbc abc;
@Mocked AnotherDependency xyz;

@Test
public void verifyingTheOrderOfSomeExpectationsRelativeToAllOthers()
{
   new UnitUnderTest().doSomething();

   new VerificationsInOrder() {{
      abc.methodThatNeedsToExecuteFirst();
      unverifiedInvocations(); // Invocations not verified must come here...
      xyz.method1();
      abc.method2();
      unverifiedInvocations(); // ... and/or here.
      xyz.methodThatNeedsToExecuteLast();
   }};
}

 

这里会验证 1,abc.methodThtNeedsToExecuteFirst() 是第一个调用 2,xyz.methodThatNeedsToExecuteLast() 是最好一个调用; 3,xyz.method1(); abc.method2() 先后调用;但在 unverifiedInvocations() 地方可能还有更多的方法调用,但无所谓。 

有时,我们关心某几个方法的调用顺序,但剩下的几个方法只验证调用了,不要求顺序,这时可通过两个验证块来表达:

 1 @Test
 2 public void verifyFirstAndLastCallsWithOthersInBetweenInAnyOrder()
 3 {
 4    // Invocations that occur while exercising the code under test:
 5    mock.prepare();
 6    mock.setSomethingElse("anotherValue");
 7    mock.setSomething(123);
 8    mock.notifyBeforeSave();
 9    mock.save();
10 
11    new VerificationsInOrder() {{
12       mock.prepare(); // first expected call
13       unverifiedInvocations(); // others at this point
14       mock.notifyBeforeSave(); // just before last
15       mock.save(); times = 1; // last expected call
16    }};
17 
18    // Unordered verification of the invocations previously left unverified.
19    // Could be ordered, but then it would be simpler to just include these invocations
20    // in the previous block, at the place where "unverifiedInvocations()" is called.
21    new Verifications() {{
22       mock.setSomething(123);
23       mock.setSomethingElse(anyString);
24    }};
25 }

上面即使去掉第13行,测试也通过。但却不能验证 6,7 行出现在5和8之间。 

 

Full verification

类似 StrictExpectations, 严格验证被调用的方法有哪些个,不能多也不能少。 

 1 @Test
 2 public void verifyAllInvocations(@Mocked final Dependency mock)
 3 {
 4    // Code under test included here for easy reference:
 5    mock.setSomething(123);
 6    mock.setSomethingElse("anotherValue");
 7    mock.setSomething(45);
 8    mock.save();
 9 
10    new FullVerifications() {{
11       // Verifications here are unordered, so the following invocations could be in any order.
12       mock.setSomething(anyInt); // verifies two actual invocations
13       mock.setSomethingElse(anyString);
14       mock.save(); // if this verification (or any other above) is removed the test will fail
15    }};
16 }

 

注意如果最小次数验证在Expectations 中指定,则在 FullExpections 中无需再指定。 这个验证始终在测试结束前执行。

Full verification in order

 

@Test
public void verifyAllInvocationsInOrder(@Mocked final Dependency mock)
{
   // Code under test included here for easy reference:
   mock.setSomething(123);
   mock.setSomethingElse("anotherValue");
   mock.setSomething(45);
   mock.save();

   new FullVerificationsInOrder() {{
      mock.setSomething(anyInt);
      mock.setSomethingElse(anyString);
      mock.setSomething(anyInt);
      mock.save();
   }};
}

 

这里, mock.setSomething(anyInt)  必须指定两次。 

 

Restricting the set of mocked types to be fully verified  只对指定的 mock 对象或类型做完全验证

@Test
public void verifyAllInvocationsToOnlyOneOfTwoMockedTypes(
   @Mocked final Dependency mock1, @Mocked AnotherDependency mock2)
{
   // Inside code under test:
   mock1.prepare();
   mock1.setSomething(123);
   mock2.doSomething();
   mock1.editABunchMoreStuff();
   mock1.save();

   new FullVerifications(mock1) {{
      mock1.prepare();
      mock1.setSomething(anyInt);
      mock1.editABunchMoreStuff();
      mock1.save(); times = 1;
   }};
}

 

也就是传递 mock objects 或 Classes 做参数给 FullVerifications(...) , 即只关心指定的 mock 对象或类型。 

 

Verifying that no invocations occurred

 待续

Verifying unspecified invocations that should not happen

待续


Capturing invocation arguments for verification

 

JMockit Turorial 翻译

原文:http://www.cnblogs.com/yoogo/p/JMockit.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!