原文

翻译并没有完全遵循原文,有个人的理解与提炼。 如果你觉得有什么地方生涩难懂,欢迎反馈。谢谢!

更新时间:2017-05-23 23:16:35

Mocked types and instances   mocked 的类和实例

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

下面的代码演示了使用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 , mock 类及其基类。
  2. @Injectable , mock 类的某个实例,类的其它实例不受影响。
  3. @Capturing , mock 类及其衍生类。

注意这里提到的类也包括接口。

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


Expectations

一个expectation 是一个给定的测试要调用的方法包括构造方法的集合。它可能详细列出对同一个方法多次不同的调用,但这不是必须的。 在测试阶段,被测对象调用mock对象的方法的过程要与 expectation 匹配,但匹配的规则不仅仅限于方法的签名,还与运行时的因素有关系:对应的mock实例,参数值以及调用的次数等。Expactation 就是要指定这几个约束。

参数的匹配,我们可以精确匹配参数值,也可以松散的匹配。

与其他 mock 框架的不同,定制交互行为只需要在expectation 块内直接调用mock对象的方法,无需特殊的 API,但注意,这并非真的调用了那个方法,只是表达一个"期望" :

Test
public void doBusinessOperationXyz(@Mocked final Dependency mockInstance)
{...new Expectations() {{...// An expectation for an instance method:mockInstance.someMethod(1, "test"); result = "mocked";...}};// A call to code under test occurs here, leading to mock invocations// that may or may not match specified expectations.
}


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 。

在使用mock技术的基于行为的测试中,这三个阶段正好对应到:

  1. 录制 - 预期mock 对象可能被调用的方法, 及其应返回的结果;
  2. 回放 - 被测对象执行,调用 mock 对象的方法;
  3. 验证 - 检查mock 对象是否按照预期被调用;

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


Regular, strict, and non-strict expectations

Expectations : 指定的方法或次数必须满足,没指定的随意。但不关心顺序。

StrictExpectation : 意味着严格匹配。 replay 阶段调用了哪几个方法,调用的次数(没有指定就是1)和顺序,都必须和 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和实例。@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();}@Overridepublic 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 成为Mocked 类型,方可对其方法和构造方法录制。

方式二:

@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
   ...
}

因为这里@Mocked 作用于两个同类型的变量,从而决定了之后对复发和的录制是关联到实例而非类。


Flexible matching of argument values  参数值的弹性匹配

在录制或者验证阶段, 不可能穷举所有可能的参数值,这时就需要使用弹性匹配。

弹性匹配通过两种方式表达,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);}};
}

注意这里怎样表达 any list 的。

Using the "with" methods for argument matching

除了预定义的 withXx() :

实例: withSameInstance(), withEqual()/withNotEqual(), withNull()/withNotNull()

类型: withAny(T )  withInstanceLike()  withInstanceOf()

String : withMatch(), withSubString(), withPrefix()/withSuffix()

还可以通过 with(Delegate ) 和 withArgThat(org.hamcrest.Matcher )自定义匹配。

Using the null value to match any object reference

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

也去是说,如果有某个参数匹配采用弹性表达式,剩下的参数如果需要匹配 null 必须使用 withNull() 。

Matching values passed through a varargs parameter 匹配变长参数

如果有确定的个数,每个的值也确定,可以一一列出。 如果个数无所谓(包括0),可以((Object[]) any) 的方式。 如,(String[])any 。

不能组合使用严格值匹配和弹性匹配。


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

在录制和验证阶段都可以指定;指定在对应的方法之后。

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

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


Explicit verification  显式的验证

如前文,对 mock方法调用的次数也可以在验证阶段指定,其语法规则和录制阶段是一样的。

因为 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行,测试也通过。但却不能验证 22,23 行出现在13。 多个验证块时,它们的相对顺序很重要。

Full verification

严格验证被调用的方法有哪些个,不能多也不能少,但顺序无关。

 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

只需要使用空的 FullVerifications  块即可: new FullInvocations(){{}};

录制时具有隐含的验证功能,对于下例

new Expectations() {{mock.a(); time=1;
}}....new FullVerifications(){{ }};

Expectations 块要求mock.a()只能被调用一次,但 FullVerifications 块则进而要求没有其他的方法调用。

如果多个 mock 类型或实例,而我们并不关心所有的,那只需在 FullVerifications 的参数列出感兴趣的类或实例就行了。

Test
public void verifyNoInvocationsOnOneOfTwoMockedDependenciesBeyondThoseRecordedAsExpected(@Mocked final Dependency mock1, @Mocked final AnotherDependency mock2)
{new Expectations() {{// These two are recorded as expected:
      mock1.setSomething(anyInt);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) {};
}

这里表达了,除了 doSomething() 需要被调用一次之外,mock2 不能有其他方法调用。

Verifying unspecified invocations that should not happen

上面已经提到,如果要保证某些方法不被调用的话,不必在 Expectations / verification 块中一一指明 time=0 。 只要不在 FullVerification 块中提到那些方法就行了:

@Test
public void readOnlyOperation(@Mocked final Dependency mock)
{new Expectations() {{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, others are not
   }};
}


Capturing invocation arguments for verification  获得录制阶段传入的参数

对参数验证往往是通过之前提到的参数匹配来实现的,这里提到了另外一种更直观的方式,把参数捕捉到然后做进一步的验证。

这时要使用 withCapture(...) 作为参数匹配器(意味着,此时只获得其值,不做匹配)。 有三种使用情况:

  1. 捕捉只调用一次的方法的参数 :T withCapture() , 使用 withCapture() 匹配参数,然后取返回值;
  2. 捕捉调用多次的方法的参数: T withCapture(List<T>) , 取传入的 list 参数;
  3. 捕捉构造方法的参数: List<T> withCapture(T)

捕捉只调用一次的方法的参数:

@Test
public void capturingArgumentsFromSingleInvocation(@Mocked final Collaborator mock)
{// Inside tested code:new Collaborator().doSomething(0.5, new int[2], "test");new Verifications() {{double d;String s;mock.doSomething(d = withCapture(), null, s = withCapture());assertTrue(d > 0.0);assertTrue(s.length() > 1);}};
}

注意,这里的 withCapture() 只能用在Verifications 块中。 如果这个方法被调用多次,那拿到的是最后一次的值。

捕捉调用多次的方法的参数

@Test
public void capturingArgumentsFromMultipleInvocations(@Mocked final Collaborator mock)
{mock.doSomething(dataObject1);mock.doSomething(dataObject2);new Verifications() {{List<DataObject> dataObjects = new ArrayList<>();mock.doSomething(withCapture(dataObjects));assertEquals(2, dataObjects.size());DataObject data1 = dataObjects.get(0);DataObject data2 = dataObjects.get(1);// Perform arbitrary assertions on data1 and data2.
   }};
}

和前面不同,这里的 withCaptrue(List<T>) 也可以用在 Expectation 块中。

捕捉在回放过程中创建的 mock 实例:

 1 @Test
 2 public void capturingNewInstances(@Mocked Person mockedPerson)
 3 {
 4    // From the code under test:
 5    dao.create(new Person("Paul", 10));
 6    dao.create(new Person("Mary", 15));
 7    dao.create(new Person("Joe", 20));
 8
 9    new Verifications() {{
10       // Captures the new instances created with a specific constructor.
11       List<Person> personsInstantiated = withCapture(new Person(anyString, anyInt));
12
13       // Now captures the instances of the same type passed to a method.
14       List<Person> personsCreated = new ArrayList<>();
15       dao.create(withCapture(personsCreated));
16
17       // Finally, verifies both lists are the same.
18       assertEquals(personsInstantiated, personsCreated);
19    }};
20 }


Delegates: specifying custom results  定制result 的计算逻辑

我们之前已经看到如何通过 result 属性或 returns(...) 录制结果。 也看到了如何通过 anyXxx 属性或 withXxx() 方法来匹配方法参数。 那如何根据回放时传入的参数返回响应的结果呢,可以通过 Delegate 接口定制:

@Test
public void delegatingInvocationsToACustomDelegate(@Mocked final DependencyAbc anyAbc)
{new Expectations() {{anyAbc.intReturningMethod(anyInt, null);result = new Delegate() {int aDelegateMethod(int i, String s){return i == 1 ? i : s.length();}};}};// Calls to "intReturningMethod(int, String)" will execute the delegate method above.new UnitUnderTest().doSomething();
}

此接口是个标志接口,没有方法。所以方法名可以随意; 参数有两种选择,要么和record的方法参数一致,要么没有参数,这两种选择都允许添加一个 Invocation 类型做为第一个参数。

通过 Delegate 也可以定制构造方法:

@Test
public void delegatingConstructorInvocations(@Mocked Collaborator anyCollaboratorInstance)
{new Expectations() {{new Collaborator(anyInt);result = new Delegate() {void delegate(int i) { if (i < 1) throw new IllegalArgumentException(); }};}};// The first instantiation using "Collaborator(int)" will execute the delegate above.new Collaborator(4);
}


Cascading mocks   级联 mock

这种情况: obj1.getObj2(..).getObj3().getObj4().doSomething(...) , 需要mock所有这链接的几个对象和类。

@Test
public void recordAndVerifyExpectationsOnCascadedMocks(@Mocked Socket anySocket, // will match any new Socket object created during the test@Mocked final SocketChannel cascadedChannel // will match cascaded instances
) throws Exception
{new Expectations() {{// Calls to Socket#getChannel() will automatically return a cascaded SocketChannel;// such an instance will be the same as the second mock parameter, allowing us to// use it for expectations that will match all cascaded channel instances:cascadedChannel.isConnected(); result = false;}};// Inside production code:Socket sk = new Socket(); // mocked as "anySocket"SocketChannel ch = sk.getChannel(); // mocked as "cascadedChannel"if (!ch.isConnected()) {SocketAddress sa = new InetSocketAddress("remoteHost", 123);ch.connect(sa);}InetAddress adr1 = sk.getInetAddress();  // returns a newly created InetAddress instanceInetAddress adr2 = sk.getLocalAddress(); // returns another new instance
   ...// Back in test code:new Verifications() {{ cascadedChannel.connect((SocketAddress) withNotNull()); }};
}

因为Socket, SocketChannel 都被 @Mocked, 尽管没有录制 Socket#getChannel(), 此方法每次调用仍然会返回相同的 mocked SockecChannel 实例。 如果不存在一个mocked 实例类型与返回值匹配,那它每次都返回不同的的实例。

注意,方法若没有被录制,缺省返回空对象, 所有方法都是空实现的对象, 不是 null 。 但 String - null, Object - null, 集合-长度为0的集合。

Cascading static factory methods

这种级联mock的特性在mocked类型包含静态的工厂方法是尤为有用:

public void postErrorMessageToUIForInvalidInputFields(@Mocked final FacesContext jsf)
{// Set up invalid inputs, somehow.// Code under test which validates input fields from a JSF page, adding// error messages to the JSF context in case of validation failures.FacesContext ctx = FacesContext.getCurrentInstance();if (some input is invalid) {ctx.addMessage(null, new FacesMessage("Input xyz is invalid: blah blah..."));}...// Test code: verify appropriate error message was added to context.new Verifications() {{FacesMessage msg;jsf.addMessage(null, msg = withCapture());assertTrue(msg.getSummary().contains("blah blah"));}};
}

你看,我们无需录制 FacesContext.getCurrentInstance(),它就可用。

Cascading self-returning methods

流式(Fluent )接口是另一个级联mock好用的情况:

@Test
public void createOSProcessToCopyTempFiles(@Mocked final ProcessBuilder pb) throws Exception
{// Code under test creates a new process to execute an OS-specific command.String cmdLine = "copy /Y *.txt D:\\TEMP";File wrkDir = new File("C:\\TEMP");Process copy = new ProcessBuilder().command(cmdLine).directory(wrkDir).inheritIO().start();int exit = copy.waitFor();...// Verify the desired process was created with the correct command.new Verifications() {{ pb.command(withSubstring("copy")).start(); }};
}

这里 command(),directory() 和 inheritIO() 都返回同一个pb 对象, 最后start()返回一个新 mocked Process 对象。


Accessing private members  访问私有成员

有时我们需要访问被测对象和mocked对象的私有成员,Deencapsulation 提供了静态方法来解决这个问题:

import static mockit.Deencapsulation.*;@Test
public void someTestMethod(@Mocked final DependencyAbc abc)
{final CodeUnderTest tested = new CodeUnderTest();// Defines some necessary state on the tested object:setField(tested, "someIntField", 123);new Expectations() {{// Expectations still recorded, even if the invocations are done through Reflection:newInstance("some.package.AnotherDependency", true, "test"); maxTimes = 1;invoke(abc, "intReturningMethod", 45, ""); result = 1;// other expectations recorded...
   }};tested.doSomething();String result = getField(tested, "result");assertEquals("expected result", result);
}

这里用到了四个方法:setField()/getField(), newInstance() 和 invoke();

注意我们不仅仅通过反射访问被测类的私有成员,对mocked 类/实例的私有方法的录制和验证,通过反射调用也是可以的。 当然,通常不需要录制和验证私有方法,除非在只mock部分方法的场景下。


Partial mocking  mock 部分方法

缺省的,@Mocked 标注的类型及其父类型(除了Object)的所有方法和构造方法都被mock。mock部分方法就是只有录制过的方法才被mock, 而其他的方法直接调用实际的实现。

public class PartialMockingTest
{static class Collaborator{final int value;Collaborator() { value = -1; }Collaborator(int value) { this.value = value; }int getValue() { return value; }final boolean simpleOperation(int a, String b, Date c) { return true; }static void doSomething(boolean b, String s) { throw new IllegalStateException(); }}@Testpublic void partiallyMockingAClassAndItsInstances(){final Collaborator anyInstance = new Collaborator();new Expectations(Collaborator.class) {{anyInstance.getValue(); result = 123;}};// Not mocked, as no constructor expectations were recorded:Collaborator c1 = new Collaborator();Collaborator c2 = new Collaborator(150);// Mocked, as a matching method expectation was recorded:assertEquals(123, c1.getValue());assertEquals(123, c2.getValue());// Not mocked:assertTrue(c1.simpleOperation(1, "b", null));assertEquals(45, new Collaborator(45).value);}@Testpublic void partiallyMockingASingleInstance(){final Collaborator collaborator = new Collaborator(2);new Expectations(collaborator) {{collaborator.getValue(); result = 123;collaborator.simpleOperation(1, "", null); result = false;// Static methods can be dynamically mocked too.Collaborator.doSomething(anyBoolean, "test");}};// Mocked:assertEquals(123, collaborator.getValue());assertFalse(collaborator.simpleOperation(1, "", null));Collaborator.doSomething(true, "test");// Not mocked:assertEquals(2, collaborator.value);assertEquals(45, new Collaborator(45).getValue());assertEquals(-1, new Collaborator().getValue());}
}

mock 部分方法时,需要指定录制的方法影响某个实例还是类。取决于传给构造方法 Expectations(Object ...) 的参数是类还是实例。 如果是类,则类及基类的所有方法及构造方法都可以被录制。

一个对象在通过expectations 构造方法转化成mock对象之后,它之前的状态(field值)仍然保留。

这里总结下, 把类或对象变成mocked 有两种方式: 用 @Mocked 或做为 Expectations()的参数。前者即使不录制,其所有方法也同时被mock;在使用后者时,尽管类或实例转化成了mocked, 但其方法只有在录制之后方法才是mocked。

只要是mocked 类型或实例,其方法就可以被 verify 。即使这个方法没有mocked -- 没有录制。 这里和 Mockito 的spy对象的概念一致。

   @Testpublic void partiallyMockingAnObjectJustForVerifications(){final Collaborator collaborator = new Collaborator(123);new Expectations(collaborator) {};// No expectations were recorded, so nothing will be mocked.int value = collaborator.getValue(); // value == 123collaborator.simpleOperation(45, "testing", new Date());...// Unmocked methods can still be verified:new Verifications() {{ c1.simpleOperation(anyInt, anyString, (Date) any); }};}

最后补充一点,应用部分mocking 更简单的方式是把测试类的成员变量一并标注@Mocked 和@Tested, 这样,此变量不传给Expectations 的构造方法。但我们任然需要录制需要的方法。@Tested 意味着被测对象,两者组合就是把mocked 实例/类也当做被测类,从而可以测试它没有被mock的方法。


Capturing implementation classes and instances   捕捉所有的实现类和实例

下面这个例子并不实用, 但能明了地说明问题。看发行版随附的示例则更实用,可参考其中 Timing Framework 。

public interface Service { int doSomething(); }
final class ServiceImpl implements Service { public int doSomething() { return 1; } }public final class TestedUnit
{private final Service service1 = new ServiceImpl();private final Service service2 = new Service() { public int doSomething() { return 2; } };public int businessOperation(){return service1.doSomething() + service2.doSomething();}
}

businessOperation() 是我们要测试的目标方法。

目前的问题是,service1 和 service2 是 final 的属性,不能通过反射设置。我们需要在实例化被测实例前先 mock ServiceImple 和那个匿名类的 doSomething(), 但是匿名类,怎么mock呢!

Mocking unspecified implementation classes  使给定基类的子孙类被mock

@Capaturing 可以帮我们实现这个目标: 给定基类或接口,使其所有子类被 mock。

public final class UnitTest
{@Capturing Service anyService;@Testpublic void mockingImplementationClassesFromAGivenBaseType(){new Expectations() {{ anyService.doSomething(); returns(3, 4); }};int result = new TestedUnit().businessOperation();assertEquals(7, result);}
}

上例,record 阶段两个返回值指定给 Service#doSomething(), 在replay阶段,不管实现类或实例是哪个,只要调用这个方法都会受到影响。

Specifying behavior for future instances   限定受 @Capturing 影响的实例个数

@Capturing 有个int类型可选属性 "maxInstances" 可以限制受影响的实例个数;这样我们就可以为同一基类的多个mock实例 record 或 verify 不同的行为。这可以通过定义2到多个@Capturing fileds/parameters,  每个都指定 maxInstance , 当然最后一个可以没有。

下面实例仅仅为了方便理解,并无实际用途:

@Test
public void testWithDifferentBehaviorForFirstNewInstanceAndRemainingNewInstances(@Capturing(maxInstances = 1) final Buffer firstNewBuffer,@Capturing final Buffer remainingNewBuffers)
{new Expectations() {{firstNewBuffer.position(); result = 10;remainingNewBuffers.position(); result = 20;}};// Code under test creates several buffers...ByteBuffer buffer1 = ByteBuffer.allocate(100);IntBuffer  buffer2 = IntBuffer.wrap(new int[] {1, 2, 3});CharBuffer buffer3 = CharBuffer.wrap("                ");// ... and eventually read their positions, getting 10 for// the first buffer created, and 20 for the remaining ones.assertEquals(10, buffer1.position());assertEquals(20, buffer2.position());assertEquals(20, buffer3.position());
}

需要注意,只要有 @Capturing 修饰,所有实例都是mocked, 尽管它可能没有被录制。


Instantiation and injection of tested classes   实例化测试类及对其注入依赖

往往,一个测试类往往操作一个被测的类。JMockit 提供的@Tested 可以自动实例化这个类并注入mocked依赖。

@Tested 的属性不能是final的,其实例化和依赖注入发生在测试方法执行前,此时若属性是null,则实例化和依赖注入就会执行,否则略过。

依赖注入的mock需要测试类通过@Injectable 的属性或参数提供,@Mocked 或 @Capturing 没有这个功能。另外,非mock的数据也可以注入,如原始类型或数组。

public class SomeTest
{@Tested CodeUnderTest tested;@Injectable Dependency dep1;@Injectable AnotherDependency dep2;@Injectable int someIntegralProperty = 123;@Testpublic void someTestMethod(@Injectable("true") boolean flag, @Injectable("Mary") String name){// Record expectations on mocked types, if needed.
tested.exerciseCodeUnderTest();// Verify expectations on mocked types, if required.
   }
}

注意,@Injectable 的数据要是非mock的,应该被显式的赋值。在@Injectable 标注参数时,赋值可以通过 @Injectable 的value 属性。

支持构造方法注入和字段注入。对于前者,能要能被 @Injectable 的变量匹配上。 需注意,对于一个给定的测试方法,可注入的变量是@Injectable fileds和这个测试方法@Injectable 参数的组合。因此,不同的测试方法可以提供不同的要注入的参数组合。

在被测试的类型通过被选择的构造器初始化之后,就开始注入它的非final的属性。对于每个要注入的属性来说,它会直接复制测试类中相同类型的@Injectable 变量,如果相同类型的有多个,就根据名称匹配。


Reusing expectation and verification blocks

最简单的办法就是把mock对象定义成属性。

public final class LoginServiceTest
{@Tested LoginService service;@Mocked UserAccount account;@Beforepublic void init(){new NonStrictExpectations() {{ UserAccount.find("john"); result = account; }};}@Testpublic void setAccountToLoggedInWhenPasswordMatches() throws Exception{willMatchPassword(true);service.login("john", "password");new Verifications() {{ account.setLoggedIn(true); }};}private void willMatchPassword(final boolean match){new Expectations() {{ account.passwordMatches(anyString); result = match; }};}@Testpublic void notSetAccountLoggedInIfPasswordDoesNotMatch() throws Exception{willMatchPassword(false);service.login("john", "password");new Verifications() {{ account.setLoggedIn(true); times = 0; }};}// other tests that use the "account" mock field
}

两个方法都要测试 LoginService#login(String accountId, String password) method。它尝试去通过"accountId"查询得到用户账户,因为两个测试都依赖这个方法,所以提过一个 NonStrictExpectation 录制它。记住,一个测试可以有多个 expectations/verifications 块,当样的块也可以在共享的 "before", "after" 方法内。这里还显示了把一个公共的 expectations 抽成一个方法,以备给所有的测试方法共享。

我们还可以创建Expectations 的子类来完成相同的重用:

final class PasswordMatchingExpectations extends Expectations
{PasswordMatchingExpectations(boolean match){account.passwordMatches(anyString); result = match;}
}@Test
public void setAccountToLoggedInWhenPasswordMatches() throws Exception
{new PasswordMatchingExpectations(true);...
}

自定义的扩展类通常应是 fianl 的,除非打算再做扩展,在那种情况下,类名称必须以 "Expectations" 或 "Verifications" 结尾,否则 JMockit 无法识别。


Other topics   其他主题

Mocking multiple interfaces at the same time  mocked 对象同时实现多个接口

下面示例一个mocked 对象怎样实现多个接口:

public interface Dependency
{String doSomething(boolean b);
}public class MultiMocksTest<MultiMock extends Dependency & Runnable>
{@Mocked MultiMock multiMock;@Testpublic void mockFieldWithTwoInterfaces(){new Expectations() {{ multiMock.doSomething(false); result = "test"; }};multiMock.run();assertEquals("test", multiMock.doSomething(false));new Verifications() {{ multiMock.run(); }};}@Testpublic <M extends Dependency & Serializable> void mockParameterWithTwoInterfaces(@Mocked final M mock){new Expectations() {{ mock.doSomething(true); result = "abc"; }};assertEquals("abc", mock.doSomething(true));assertTrue(mock instanceof Serializable);}
}

核心是这样的语法 <MM extends  A & B> 。  然后利用 MM 声明mock 的类型。

Iterated expectations   录制迭代过程

使用 strict expectations 录制的连续的调用过程缺省要求的执行次数是1,我们也可以要求迭代次数,通过 StrictExpectations(numberOfIterations)

@Test
public void recordStrictInvocationsInIteratingBlock(@Mocked final Collaborator mock)
{new StrictExpectations(2) {{mock.setSomething(anyInt);mock.save();}};// In the tested code:mock.setSomething(123);mock.save();mock.setSomething(45);mock.save();
}

Expectations 和 NonstrictExpectations 也可以使用这种方式。迭代次数仅仅作为一个最大/最小次数限制的乘数。对于 NonstrictExpectations 来说

Verifying iterations  验证迭代次数

同样的 Verifications 构造方法也可以指定迭代次数

@Test
public void verifyAllInvocationsInLoop(@Mocked 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();}};
}

例中使用了两种 verifications,仅仅是为了说明两者的区别。对于前者,这个次数参数仅仅作为一个乘数,它与显式的次数(times,minTimes,maxTimes)的积构成是实际的次数要求。而对于 VerificationsInOrder, 因为显式的次数设置对它无效, 所以这个参数相当于把这个顺序的调用过程重复n次 。

同样,这个次数参数在 FullVerifications 及 FullVerificationsInOrder 上应用可以类比这个例子。

转载于:https://www.cnblogs.com/yoogo/p/JMockit.html

JMockit 指南 翻译相关推荐

  1. gradle插件用户指南(翻译)

    gradle插件用户指南(翻译) http://www.open-open.com/lib/view/open1428320744713.html gradle属性文件详解(英文): https:// ...

  2. Gainlo 面试指南 翻译完成

    Gainlo 面试指南 翻译完成 来源:Gainlo Mock Interview 译者:飞龙 在线阅读 PDF格式 EPUB格式 MOBI格式 Github 赞助我 协议 CC BY-NC-SA 4 ...

  3. python海伦公式_python使用海伦公式Python 入门指南翻译

    python使用海伦公式Python 入门指南翻译 更新时间: 2019-07-24 16:38:23 作者:第二电脑网 来源:第二电脑网 浏览数:614 我要评论 最近打算学习Pytho,顺便把Th ...

  4. LPC54114双核使用指南翻译 - 使用MCUXPresso进行双核调试

    前两天参加了恩智浦社区举办的LPC54114双核使用指南翻译活动,现将自己翻译的部分分享出来,如有不妥,敬请指正. 我翻译的是本文附件PPT中的Page 55-Page 60(Dual core HO ...

  5. 桌面虚拟化之用户评估指南 (翻译)

    正在评估桌面虚拟化解决方案,面对如此丰富的功能,却有些无从下手? 不妨看看VMware View用户评估指南,中文翻译版,独家发布. 中文翻译版本: http://down.51cto.com/dat ...

  6. [Elasticsearch] Elasticsearch权威指南翻译目录

    为了方便大家能够更加快速地找到自己需要参考的那部分,对已经翻译完成的部分根据权威指南的目录做了相应目录,希望能够有所帮助. 起步(Getting Started) 1. 你懂的,为了搜索 英文原文链接 ...

  7. OpenCart 官方开发指南翻译一 —— 模块开发

    模块开发   编写 OpenCart 模块可以很好地了解 OpenCart 如何运作的基本原理.就像 OpenCart 的其余部分一样,模块遵循 MVCL 设计模式.本文档指南将介绍如何使用 MVC- ...

  8. Gradle 2.0 用户指南翻译——第五十六章. 多项目构建

    本文禁止w3cschool转载! 翻译项目请关注Github上的地址:https://github.com/msdx/gradledoc . 本文翻译所在分支:https://github.com/m ...

  9. Gradle 2.0 用户指南翻译——第二十二章. 标准的 Gradle 插件

    翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tre ...

最新文章

  1. 微信AI全面放开NLP能力
  2. TMG学习(十),发布DMZ区网站
  3. Hive应用:外部分区表
  4. Memcached服务器的图形化管理工具
  5. 这是啥?也太秀了吧?
  6. android 开发
  7. 格灵深瞳发起 AI · 爱 算法 在线编程挑战赛
  8. UI设计动效\动画素材模板|分层分步骤学习动效设计
  9. React Native之APK文件签名及打包
  10. 以下创建了几个对象_面试题系列第2篇:new String()创建几个对象?
  11. iOS 8.0正式公布啦
  12. 回网友:不用PPT怎么做咨询顾问?
  13. Windows网络编程 WSAstartup()详解
  14. 读《因果的真相》第五章摘抄笔记
  15. PCB学习笔记——原理图编译与检查
  16. 6个实用的 Python 自动化脚本,告别加班,你学会了吗?
  17. Dell戴尔笔记本电脑G3 3779原装出厂Windows10系统恢复原厂oem系统
  18. 今日头条广告_API对接文档学习-1
  19. Eclipse使用指南
  20. Android 之路51---百度地图实现

热门文章

  1. 第101章 SQL函数 NVL
  2. 书画拍卖系统 php源码,网上拍卖系统,源代码
  3. socket.io前后端实践及转发、多服务问题
  4. 几种存储技术的比较(FC SAN、IP SAN、DAS、NAS)
  5. [AV1] AV1 Encoder代码流程图
  6. js找出两个数组中不同的元素
  7. IOS——获取当前运营商(获取漫游接入网络的运营商)
  8. 计算机毕业设计之SSM的医院挂号就诊系统
  9. 蓝桥杯-算法提高-Cowboys
  10. 毕设-基于JavaWeb毕业论文选题系统