JMockit 指南 翻译
原文
翻译并没有完全遵循原文,有个人的理解与提炼。 如果你觉得有什么地方生涩难懂,欢迎反馈。谢谢!
更新时间: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标注:
- @Mocked , mock 类及其基类。
- @Injectable , mock 类的某个实例,类的其它实例不受影响。
- @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技术的基于行为的测试中,这三个阶段正好对应到:
- 录制 - 预期mock 对象可能被调用的方法, 及其应返回的结果;
- 回放 - 被测对象执行,调用 mock 对象的方法;
- 验证 - 检查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(...) 作为参数匹配器(意味着,此时只获得其值,不做匹配)。 有三种使用情况:
- 捕捉只调用一次的方法的参数 :T withCapture() , 使用 withCapture() 匹配参数,然后取返回值;
- 捕捉调用多次的方法的参数: T withCapture(List<T>) , 取传入的 list 参数;
- 捕捉构造方法的参数: 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 指南 翻译相关推荐
- gradle插件用户指南(翻译)
gradle插件用户指南(翻译) http://www.open-open.com/lib/view/open1428320744713.html gradle属性文件详解(英文): https:// ...
- Gainlo 面试指南 翻译完成
Gainlo 面试指南 翻译完成 来源:Gainlo Mock Interview 译者:飞龙 在线阅读 PDF格式 EPUB格式 MOBI格式 Github 赞助我 协议 CC BY-NC-SA 4 ...
- python海伦公式_python使用海伦公式Python 入门指南翻译
python使用海伦公式Python 入门指南翻译 更新时间: 2019-07-24 16:38:23 作者:第二电脑网 来源:第二电脑网 浏览数:614 我要评论 最近打算学习Pytho,顺便把Th ...
- LPC54114双核使用指南翻译 - 使用MCUXPresso进行双核调试
前两天参加了恩智浦社区举办的LPC54114双核使用指南翻译活动,现将自己翻译的部分分享出来,如有不妥,敬请指正. 我翻译的是本文附件PPT中的Page 55-Page 60(Dual core HO ...
- 桌面虚拟化之用户评估指南 (翻译)
正在评估桌面虚拟化解决方案,面对如此丰富的功能,却有些无从下手? 不妨看看VMware View用户评估指南,中文翻译版,独家发布. 中文翻译版本: http://down.51cto.com/dat ...
- [Elasticsearch] Elasticsearch权威指南翻译目录
为了方便大家能够更加快速地找到自己需要参考的那部分,对已经翻译完成的部分根据权威指南的目录做了相应目录,希望能够有所帮助. 起步(Getting Started) 1. 你懂的,为了搜索 英文原文链接 ...
- OpenCart 官方开发指南翻译一 —— 模块开发
模块开发 编写 OpenCart 模块可以很好地了解 OpenCart 如何运作的基本原理.就像 OpenCart 的其余部分一样,模块遵循 MVCL 设计模式.本文档指南将介绍如何使用 MVC- ...
- Gradle 2.0 用户指南翻译——第五十六章. 多项目构建
本文禁止w3cschool转载! 翻译项目请关注Github上的地址:https://github.com/msdx/gradledoc . 本文翻译所在分支:https://github.com/m ...
- Gradle 2.0 用户指南翻译——第二十二章. 标准的 Gradle 插件
翻译项目请关注Github上的地址: https://github.com/msdx/gradledoc 本文翻译所在分支: https://github.com/msdx/gradledoc/tre ...
最新文章
- 微信AI全面放开NLP能力
- TMG学习(十),发布DMZ区网站
- Hive应用:外部分区表
- Memcached服务器的图形化管理工具
- 这是啥?也太秀了吧?
- android 开发
- 格灵深瞳发起 AI · 爱 算法 在线编程挑战赛
- UI设计动效\动画素材模板|分层分步骤学习动效设计
- React Native之APK文件签名及打包
- 以下创建了几个对象_面试题系列第2篇:new String()创建几个对象?
- iOS 8.0正式公布啦
- 回网友:不用PPT怎么做咨询顾问?
- Windows网络编程 WSAstartup()详解
- 读《因果的真相》第五章摘抄笔记
- PCB学习笔记——原理图编译与检查
- 6个实用的 Python 自动化脚本,告别加班,你学会了吗?
- Dell戴尔笔记本电脑G3 3779原装出厂Windows10系统恢复原厂oem系统
- 今日头条广告_API对接文档学习-1
- Eclipse使用指南
- Android 之路51---百度地图实现