Jmockit用户指南中文翻译(未校对)之三

  • 严格和非严格的期望

在期望块new Expectations(){...}中,默认所有被记录下来的期望都是严格的。这意味着,这些期望的调用必须在重播阶段被执行,而且需要按照声明的期 望指定的执行顺序执行,而且,也只允许这些调用被执行。任何一个没有被记录下来的非期望调用都会造成测试用例失败。

另一方面,当我们需要记录(录制)非严格(或者是松散)的期望时,那么我们可以使用mockit.NonStrictExpectations子 类。在一个非严格的期望块中,所有的被mock的类型的所有调用都可以在重播阶段执行,当然包括不在期望中声明的。也就是说,默认情况下,在重播阶段是否 执行mock类型的调用是不会造成测试用例失败的。同样,这种不严格的期望是不要求调用的执行顺序的。

缺省情况下,一个严格的期望会精确匹配重播阶段的一个调用。换而言之,这类型的期望是存在一个隐式的调用次数约束1,就好像它后面紧跟着 times=1这个约束。而另一方面,对于一个非严格的期望,默认是可以匹配重播阶段调用的任意次数的。它存在一个隐式的调用次数下限为0的约束,就好 像,被指定约束minTimes=0.然而,无论哪种情况,隐式的调用次数约束是可以被显式的约束(相当于使用times/minTimes /maxTimes 属性字段)覆盖的。所以,一个严格的期望也可以被设置成允许任何调用次数的约束,同样,非严格的期望约束也可以反过来(当然,其前提是我们确实需要这 样)。

请注意,对于一个严格的期望,所有在重播阶段被期望所匹配的调用,都会隐式被校验通过的。剩余的调用则被认为不符合期望(即造成测试失败),除非, 这个mock的类型被关联到一个非严格的期望上。所以,使用隐式验证的严格期望需要排除verification块的使用,这个块是用于调用的显式校验 的。实际上,在 new Verifications() {...}代码块中,只有那些匹配非严格期望的调用才被允许使用。同样,那些非严格的mock类型中的调用(没有被期望声明的)也可以显式记录。

为了允许在new Expectations(){...}代码块中混合使用严格和非严格调用,我们提供了一个mockit.NonStric注解用于mock属性域或者参 数。任何一个这样的mock类型都可以被认为是一个非严格期望,同时所有没有被录制的调用都可以运行在重播阶段执行(就是说,它们不被认为是非期望的执 行,因此不会造成测试失败)。这是特别有用的,以避免需要记录调用构造函数,或任何不感兴趣的方法。

在一个期望代码块中,一个独立的期望可以通过在调用语句后加上notStrict()方法将该期望标记为非严格期望,尽管在大多数情况下,使用non-strict期望块或者使用non-strict注解mock字段/参数会比这种方式更容易简单。

因此,对于给定一个测试,我们怎么在严格和非严格期望之间进行选择呢?这里没有普遍正确的答案。这通常会依赖于当前单元测试的特点以及个人的经验。总之,请谨记在脑袋的是,一个测试是可以同时混合使用这两种期望的,因此我们可以灵活的使用它们。

  • Strict and non-strict mocks 严格和非严格mock

通常来说,我们最好站在一个独立的期望角度来思考使用严格性,而不是在一个mock实例或者类型上面考虑。等价的说法是,对于一个给定的mock实 例(或者类型)要么是严格的要么是非严格的。这意味着,特定实例/类型相关联的所有期望有相同的严格性。另一方面,在相同的测试,同时记录相同的mock 实例或类型的严格和非严格的期望,是完全有效的。而当使用在测试类级别声明的mock字段时,一个给定的mock实例在一个测试用例中是完全严格的,而同 时,在同一个测试类中,在另一个测试用例中,却是不严格的,这种情况是可以存在的。

总结下,我们可以指定三种不同严格性的方式:1)在一个给定的严格期望块中,如果需要指定某一个期望是非严格的,可以调用notStrict()方 法。2)对于一个特殊的mock类型/实例,其所有期望都需要是完全非严格的,则可以通过注解@NonStrict将其声明为一个mock属性字段或者参 数。3)如果在一个期望块中,需要所有的期望都是非严格的,则可以使用NonStrictExpectations类。

另一方面,这里没有提供任何一种方式去显式的指定某个给定的期望、mock类型/实例、或者一个完整的期望块应该是严格的。当我们没有指定期望是非 严格时,被记录下来的期望都被缺省认为是严格的。因此,对于一个期望需要被声明为严格时,它应该被记录在不在NonStrictExpectations 的期望expectation中,而对于mock类型,则不应该被注解为 @NonStrict. 而关联的mock类型和它相对应的所有实例都会被作为一个整体进行严格的校验,除了哪些匹配到一个被显式标记为非严格的调用。

最后,请注意,如果对于一个给定的Mock类型,在测试代码中没有任何相应的期望被记录,那么这个mock类型会自动被认为是一个完全不严格的(就好像这个mock的字段/参数被注解为 @NonStrict).

  • Iterated expectations 迭代期望
    当一系列有序连续的调用被记录在一个严格期望块中(调用之间的相对顺序与非严格的期望是无关的),整个序列则被期望在重播阶段执行。然而,让我们考虑下这 种情况,这些调用是在测试代码中的一个循环(或者任何其他迭代方式)里面执行。假设在测试代码中,我们是可以预先知道迭代次数,那么我们仍然记录这些期 望,匹配这些将在循环中执行的方法/构造函数(注意,我们并不是在一个期望代码块中,通过循环的方式声明调用期望)。通过使用 Expectations(int numberOfIterations)构造函数,下面的demo显示了这种特性。
    @Test
       public void recordStrictInvocationsInIteratingBlock(final Collaborator mock)
       {
          new Expectations(2) {{
             mock.setSomething(anyInt);
             mock.save();
          }};
     
          // In the tested code:
          mock.setSomething(123);
          mock.save();
          mock.setSomething(45);
          mock.save();
       }
     

这种指定一组调用的迭代次数的方式也适用于非严格期望。然而,非严格期望的情况下,迭代次数只是用于指定调用次数的上限和下限限制约束(包括隐式和显式的)。所以,对于非严格期望来说,没有指定调用次数约束,是没有影响的。

  • Explicit verification 显式验证(校验)
    严格期望是隐式校验的,所以,不需要在一个显式的校验块重复校验。另一方面,非严格期望通常在一个校验结构块中通过显式的方式来校验mock类型的调用。就如不久将看到的一样,一个被录制下来的非严格期望仍然可以通过隐式方式来校验,而不需要在校验块中编码调用校验。

在new Verifications() {...}块中,我们可以使用和NonStrictExpectations() {...}同样的方法或者字段API,用于指定期望返回值以及异常错误。也就说,我们可以自由的使用anyXyz字段、 withXyz(...)方法来匹配方法参数,以及使用times、minTimes和maxTimes来指定调用次数的约束。请看下面的例子。

@Test
   public void verifyInvocationsExplicitlyAtEndOfTest(final Dependency mock)
   {
      // Nothing recorded here, but it could be.
 
      // Inside tested code:
      Dependency dependency = new Dependency();
      dependency.doSomething(123, true, "abc-xyz");
 
      // Verifies that Dependency#doSomething(int, boolean, String) was called at least once,
      // with arguments that obey the specified constraints:
      new Verifications() {{ mock.doSomething(anyInt, true, withPrefix("abc")); }};
 

 

注意,默认情况下,一个verification校验会检查是否在重播阶段至少存在一个调用匹配到该校验。当我们需要指定调用的准确次数(包括1),那么就需要指定times=n这个约束。

  • Importing mocks from expectation blocks 从期望块中导入mock类型/实例

被声明在测试类属性字段域内的mock实例/类型,或者被声明在测试方法参数中的mock实例/类型,都是在校验块中使用。但是,我们知道,在 expectation块中同样可以声明一个局部的mock字段,这些字段是不可以被该块的外部使用。在这种情况下,我们可以参照下面的例子,将这些 mock实例导入到verification块中。

@Test
   public void importLocalMockFromPreviousNonStrictExpectationsBlock()
   {
      new NonStrictExpectations() {
         Dependency mock;
 
         {
            mock.notifyBeforeSave(); result = true;
         }
      };
 
      // Inside tested code:
      Dependency dependency = new Dependency();
      dependency.editABunchMoreStuff();
 
      new Verifications() {
         Dependency mock;
 
         {
            mock.editABunchMoreStuff();
         }
      };
   }
 

 

这些被导入的mock类型是通过在verification中声明的字段域类型来标示的,而不是字段名称,尽管通常情况下,相同的字段名称可以用来避免冲突。当不需要mock参数时,或者在内部创建一个mock实例时,这种特性是很有用的(后面部分再作解释)。

  • Verifying that an invocation never happened 判定一个调用从未执行
    为此,可以在verification块内部,在一个不需要在重播阶段执行的调用期望语句后面,添加times = 0语句。如果存在一个或者多个匹配该声明的调用发生,则会造成测试用例失败。
  • Verification in order 校验顺序
    一般的verification块(就好像上面的那个),是无序的。实际就是说,对于aMethod()和 anotherMethod()方法在重播阶段的执行顺序是不要求校验的,只是,需要保证在重播阶段至少执行一次就行。如果你需要校验调用之间的执行顺 序,那么就可以使用new VerificationsInOrder() {...}来替代。 在这个代码块中,只需要简单的按照mock类型被执行的顺序编写就行了。
    @Test
       public void verifyingExpectationsInOrder(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()方法就可以到达这个目的了。下面demo显示了该用法。
    @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();
          }};
       }
     

    上面的例子实际上有点复杂了,因为它校验挺多东西的:a)一个方法必须要在其他方法之前被调用执行;b)一个方法必须在其他方法之后被执 行;c)AnotherDependency#method1()必须在DependencyAbc#method2()执行之后被调用。在大多数的测试 代码中,我们只需要保证这几种不同顺序的一个调用顺序就可以了。但是,强大的地方就在于这里,我们可以很容易的使用多种复杂顺序校验。

另一种情景没有被上面例子覆盖到的是,我们需要判定一些调用按照给定的顺序执行,同时需要另一些调用按照任意顺序执行。对此,我们需要编写两个独立的verification校验块,就好像上面示例一样(这里的mock是一个测试类的mock属性字段)

@Test
   public void verifyFirstAndLastCallsWithOthersInBetweenInAnyOrder()
   {
      // Invocations that occur while exercising the code under test:
      mock.prepare();
      mock.setSomethingElse("anotherValue");
      mock.setSomething(123);
      mock.notifyBeforeSave();
      mock.save();
 
      new VerificationsInOrder() {{
         mock.prepare(); // first expected call
         unverifiedInvocations(); // others at this point
         mock.notifyBeforeSave(); // just before last
         mock.save(); times = 1; // last expected call
      }};
 
      // Unordered verification of the invocations previously left unverified.
      // Could be ordered, but then it would be simpler to just include these invocations
      // in the previous block, in the place of the "unverifiedInvocations()".
      new Verifications() {{
         mock.setSomething(123);
         mock.setSomethingElse(anyString);
      }};
   }
 

 

通常情况下,当在测试代码中存在多个verification校验块时,它们的执行顺序就很重要。
In the previous test, for example, if the unordered block came before it would have left no "unverified invocations" to match a later call to unverifiedInvocations(); the test would still pass (assuming it originally passed) since it's not required that unverified invocations actually occurred at the called position, but it would not have verified that the unordered group of invocations occurred between the first and last expected calls.

  • Full verification 完整校验
    有时候,对mock类型所有的调用进行校验可能是很重要的。这自然就是记录严格期望时的情况,因为任何一个不希望发生的调用被执行都会造成测试失败。当在 非严格期望中使用显式的校验时,可以使用new FullVerifications() {...}块来确保没有调用未被校验。
    @Test
       public void verifyAllInvocations(final Dependency mock)
       {
          // Code under test included here for easy reference:
          mock.setSomething(123);
          mock.setSomethingElse("anotherValue");
          mock.setSomething(45);
          mock.save();
     
          new FullVerifications()
          {{
             // Verifications here are unordered, so the following invocations could be in any order.
             mock.setSomething(anyInt); // verifies two actual invocations
             mock.setSomethingElse(anyString);
             mock.save(); // if this verification (or any other above) is removed the test will fail
          }};
       }
     

    注意:如果一个下限约束(一个最小调用次数约束)被指定(除非是非严格期望),那么这个约束总会在测试代码最后结束前被隐式校验。因此,在全部验证块中显式的校验严格期望是没有必要的。

  • Full verification in order有序的完整校验
    目前,我们已经知道怎样使用Verifications处理无序的校验,如何使用VerificationsInOrder处理有序的校验,以及使用FullVerifications进行完整校验。但是,又如何处理有序的完整校验呢?足够的简单,如下:
    @Test
       public void verifyAllInvocationsInOrder(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();
          }};
       }
     

    注意到,这里并没有什么不同的语法。在上面例子的verifyAllInvocations测试中,我们可以使用单一的verification校 验调用匹配两个独立mock.setSomething(...)方法。但是,在verifyAllInvocationsInOrder测试代码中,我 们在校验块中写了两个独立的调用,用来正确匹配。

现在,你可以会想,编写一个FullVerificationsInOrder其实就和编写一个Expectations块是一样的,因为 Expectations块中的所有期望都是严格的。那么我们是不是在重复造轮?并不完全是的。对于一个非严格的期望,缺省的被加上调用次数 minTimes = 1的约束,从而允许同一个方法或者构造函数在重播阶段可以多次执行。所以,在上面的例子中,如果setSomethingElse(...)方法在重播阶 段被连续二次调用执行,测试用例依然是可以通过的(前提是,第二次的调用需要按照期望顺序执行)。

  • Restricting the set of mocked types to be fully verified (完全校验某些mock类型的特定集合)

默认情况下,对于给定的测试,当使用 new FullVerifications() {}或者new FullVerificationsInOrder() {} 结构块时,所有mock实例/类型的所有调用都必须显式校验。现在,如果我们有一个测试,它有两个(或者更多)mock类型,但是我们只需要对这些 mock类型中的某一个(如果是多于2个mock类型,则可能是一个mock子集)做完整的调用校验,该怎么办?答案就是,使用 FullVerifications(Object... mockedTypesAndInstancesToVerify)构造函数,它只对这些给出的mock类型实例进行完整校验。下面的测试代码提供这样的 例子。

@Test
   public void verifyAllInvocationsToOnlyOneOfTwoMockedTypes(Dependency mock1, 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;
      }};
   }
 

 

在上面的测试中,mock2.doSomething()调用是不会被验证的。如果需要只是针对某个class的方法(也包括构造函数)需要校验, 这也是可能的,只是需要将class传递给FullVerifications(...)或者 FullVerificationsInOrder(...) 构造函数里面。例如,new FullVerificationsInOrder(AnotherDependency.class) { ... }这个验证块仅仅保证所有AnotherDependency class的mock类型才被校验。

  • Verifying that no invocations occurred 验证没有任何调用被执行

为了在测试代码中验证非严格的mock类型/实例上的所有调用都没有执行,只需要添加一个空的完整校验块就行。一如往常,请注意,一个具有 times/minTimes调用次数约束的已记录的任何期望都会被隐式验证,因此不需要完整校验块;在这种情况下空校验块会确保没有其他调用发生。此 外,在同一个测试用例中,如果一个期望被之前的一个verification校验块验证过,则它同样不会被完整校验块所验证。

如果测试中使用了两个或者多个mock类型/实例,而你又需要校验某些Mock是没有任何调用发生的,那么就需要在一个空的校验块中指定需要校验的mock类型或者实例。正如下面例子所示:

@Test
   public void verifyNoInvocationsOnOneOfTwoMockedDependenciesBeyondThoseRecordedAsExpected(
      final Dependency mock1, final AnotherDependency mock2)
   {
      new NonStrictExpectations()
      {{
         // These two are recorded as expected:
         mock1.setSomething(anyInt); minTimes = 1;
         mock2.doSomething(); times = 1;
      }};
 
      // Inside code under test:
      mock1.prepare();
      mock1.setSomething(1);
      mock1.setSomething(2);
      mock1.save();
      mock2.doSomething();
 
      // Will verify that no invocations other than to "doSomething()" occurred on mock2:
      new FullVerifications(mock2) {};
   }
 

 

  • Verifying unspecified invocations that should not happen 验证未指定的不应该发生的调用
    一个完全校验块(无论是否有序)同样可以让我们来验证,某特定的方法和/或构造函数永远不会被调用,而无需为每一个方法/构造函数指定 times= 0。下面的测试提供了一个例子。
    @Test
       public void readOnlyOperation(final Dependency mock)
       {
          new NonStrictExpectations()
          {{
             mock.getData(); result = "test data";
          }};
     
          // Code under test:
          String data = mock.getData();
          // mock.save() should not be called here
          ...
     
          new FullVerifications()
          {{
             mock.getData(); minTimes = 0; // calls to getData are allowed
          }};
       }
     

如果在重播阶段,Dependency类的任何方法或者构造函数被调用,那么上面的测试将会失败,除非那些在校验块中显式的被校验 (Dependency#getData()就是这样)。另一方面,对于这种情况,我们根本不需要使用任何的验证块,而是更简单的使用一个严格期望就行 了。

  • Verifying iterations 验证迭代
    关于验证块,我们还有最后一种情况需要测试下:能够方便地验证发生在循环中的调用的循环迭代次数。
    @Test
       public void verifyAllInvocations(final Dependency mock)
       {
          int numberOfIterations = 3;
     
          // Code under test included here for easy reference:
          for (int i = 0; i < numberOfIterations; i++) {
             DataItem data = getData(i);
             mock.setData(data);
             mock.save();
          }
     
          new Verifications(numberOfIterations)
          {{
             mock.setData((DataItem) withNotNull());
             mock.save();
          }};
     
          new VerificationsInOrder(numberOfIterations)
          {{
             mock.setData((DataItem) withNotNull());
             mock.save();
          }};
       }
     

上面使用两个verification验证块目的是用来解释有序和无序的迭代验证的区别。在第一个验证块中,每一个验证调用都必须至少重播阶段中相 同方法3次,因为迭代次数被传递到构造函数里。对于无序的迭代块,指定迭代次数会用来乘以下限和上限的调用次数,当在验证块内指定一个明确的约束,如一个 minTimes= 1; maxTimes= 4,就会被乘以迭代次数;在上面的例子中就相当于被各自被乘以3,转化成minTimes= 3; maxTimes= 12;.另一方面,在第二个验证块中,调用次数的约束则无效。相反,产生的效果相当于"展开循环",就好像验证块校验调用的重复每个迭代。

迭代的FullVerifications块的语义和正常的校验块是一样的。迭代FullVerificationsInOrder块和一个VerificationsInOrder块也是一样。

你可能感兴趣的:(单元测试,jmockit)