EasyMock 2.0_ReleaseCandidate Readme
Documentation for release 2.0_ReleaseCandidate (October 15 2005)
© 2001-2005 OFFIS, Tammo Freese.
翻译:GHawk, 2005-12-15
EasyMock 2 is a library that provides an easy way to use Mock Objects for given interfaces. EasyMock 2 is available under the terms of the MIT license.
EasyMock 2 是一套用于通过简单的方法对于给定的接口生成Mock对象的类库。EasyMock 2 采用MIT license 发布。
Mock Objects simulate parts of the behavior of domain code, and are able to check whether they are used as defined. Domain classes can be tested in isolation by simulating their collaborators with Mock Objects.
Mock 对象能够模拟领域对象的部分行为,并且能够检验对领域对象的使用是否与预期的定义一致。领域类通过与Mock 对象协作,从而获得一个孤立的测试环境。
Writing and maintaining Mock Objects often is a tedious task that may introduce errors. EasyMock 2 generates Mock Objects dynamically - no need to write them, and no generated code!
编写和维护Mock 对象是一项枯燥而且可能引入错误的工作。EasyMock 2 能够动态生成Mock 对象——不需要编写Mock对象,也不会产生多余的代码。
EasyMock 2 Benefits EasyMock 2的优点
- Hand-writing classes for Mock Objects is not needed.
- 不需要人为地编写实现Mock对象的类。
- Supports refactoring-safe Mock Objects: test code will not break at runtime when renaming methods or reordering method parameters
- 支持“重构安全”的Mock对象:当重命名方法和对方法参数重新排序时,测试代码不需要在运行时中断。
- Supports return values and exceptions.
- 支持返回值和抛出异常。
- Supports checking the order of method calls, for one or more Mock Objects.
- 支持一个或多个Mock对象的方法调用顺序检查。
EasyMock 2 Drawbacks EasyMock 2的不足
- EasyMock 2 does only work with Java 2 Version 5.0 and above.
- EasyMock 2 只能在Java 5.0以上的版本中运行。(译注:EasyMock 1.2 可用于低版本的JDK。)
EasyMock by default supports the generation of Mock Objects for interfaces only. For those who would like to generate Mock Objects for classes, there is an extension available at the EasyMock home page.
EasyMock 默认只支持为接口生成Mock对象。如果需要为类生成Mock对象,在EasyMock的主页上有扩展包可以实现此功能。
Installation 安装
- Java 2 (at least 5.0) is required.
- Unzip the EasyMock zip file (
easymock2.0_ReleaseCandidate.zip). It contains a directory easymock2.0_ReleaseCandidate. Add the EasyMock jar file (easymock.jar) from this directory to your classpath.
- 首先安装Java 2(5.0以上版本)。
- 解压缩EasyMock zip 文件 (
easymock2.0_ReleaseCandidate.zip)。其中含有一个目录 easymock2.0_ReleaseCandidate。 在classpath中加入目录中的EasyMock jar 文件 (easymock.jar)。
To execute the EasyMock tests, add tests.zip and JUnit (at least 3.8.1) to your class path and start 'java org.easymock.tests.AllTests'.
如果要运行EasyMock的测试包,需要将tests.zip和JUnit(至少3.8.1版)加入到classpath中并且执行“java org.easymock.tests.AllTests”。
The source code of EasyMock is stored in the zip file src.zip.
EasyMock 的源码在src.zip文件中。
Usage 使用
Most parts of a software system do not work in isolation, but collaborate with other parts to get their job done. In a lot of cases, we do not care about using collaborators in unit testing, as we trust these collaborators. If we do care about it, Mock Objects help us to test the unit under test in isolation. Mock Objects replace collaborators of the unit under test.
软件系统中的大部分组件都需要在协作的环境中工作,而无法在孤立的环境中工作。多数情况下,在单元测试中,我们的侧重点不在被测组建的协作过程上,因为我们已经假设这些协作过程是可信赖的。如果我们同时又要兼顾这些协作过程的测试,Mock对象能够帮助我们在孤立其他写作对象的环境中对被测对象进行单元测试。Mock对象用于替换与被测对象发生协作的对象。
The following examples use the interface Collaborator:
下面是一个使用接口Collaborator的例子:
package org.easymock.samples;
public interface Collaborator { void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}
Implementors of this interface are collaborators (in this case listeners) of a class named ClassUnderTest:
使用这个接口的是ClassUnderTest类,类中的listener对象是接口Collaborator的一个实例。
public class ClassUnderTest { // ...
public void addListener(Collaborator listener) { // ...
}
public void addDocument(String title, byte[] document) { // ...
}
public boolean removeDocument(String title) { // ...
}
public boolean removeDocuments(String[] titles) { // ...
}
}
The code for both the class and the interface may be found in the package org.easymock.samples in samples.zip.
以上的类和接口的代码都能在sample.zip的org.easymock.samples包中找到。
The following examples assume that you are familiar with the JUnit testing framework. Although the tests shown here use JUnit in version 3.8.1, you may as well use JUnit 4 or TestNG.
以下的例子假设您对JUnit测试框架比较熟悉。这些测试用的是JUnit3.8.1,但是您也可以使用JUnit4或TestNG。
The first Mock Object 第一个Mock对象
We will now build a test case and toy around with it to understand the functionality of the EasyMock package. samples.zip contains a modified version of this test. Our first test should check whether the removal of a non-existing document does not lead to a notification of the collaborator. Here is the test without the definition of the Mock Object:
我们要编写一个测试案例并且围绕着这个案例来理解 EasyMock的功能。samples.zip中有这个测试的修改版本。第一个测试要检查当删除一个不存在的document对象时,协作对象(collaborator)是否会收到通知。以下是没有定义过Mock对象的测试代码:
package org.easymock.samples;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
protected void setUp() { classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() { // This call should not lead to any notification
// of the Mock Object:
classUnderTest.removeDocument("Does not exist"); }
}
For many tests using EasyMock 2, we only need a static import of methods of org.easymock.EasyMock. This is the only non-internal, non-deprecated class of EasyMock 2.
对于使用EasyMock 2的大部分测试,我们仅需要对org.easymock.EasyMock的方法采用静态引入(static import。译注:Java 5中的新特性。)这是EasyMock 2中唯一一个非内部、非不推荐的类。
import static org.easymock.EasyMock.*;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;
}
To get a Mock Object, we need to
- create a Mock Object for the interface we would like to simulate,
- record the expected behavior, and
- switch the Mock Object to replay state.
要得到一个Mock对象,需要以下几步
- 为想要模拟的接口创建一个Mock对象,
- 记录对该对象预期的行为,然后
- 将Mock对象切换到replay状态。
Here is a first example:
以下是范例代码:
protected void setUp() { mock = createMock(Collaborator.class); // 1
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}
public void testRemoveNonExistingDocument() { // 2 (we do not expect anything)
replay(mock); // 3
classUnderTest.removeDocument("Does not exist"); }
After activation in step 3, mock is a Mock Object for the Collaborator interface that expects no calls. This means that if we change our ClassUnderTest to call any of the interface's methods, the Mock Object will throw an AssertionError:
在第3步,把Mock对象激活后,mock就成为了一个Collaborator的Mock对象,且不接受任何调用。这意味着如果我们修改ClassUnderTest使其调用Collaborator接口中的方法,Mock对象就会抛出AssertionError:
java.lang.AssertionError:
Unexpected method call documentRemoved("Does not exist"): at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentRemoved(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentRemoved(ClassUnderTest.java:74)
at org.easymock.samples.ClassUnderTest.removeDocument(ClassUnderTest.java:33)
at org.easymock.samples.ExampleTest.testRemoveNonExistingDocument(ExampleTest.java:24)
...
Adding Behavior 添加行为
Let us write a second test. If a document is added on the class under test, we expect a call to mock.documentAdded() on the Mock Object with the title of the document as argument:
接着,我们再写第2个测试。当被测对象中有document加入时,预期会调用Mock对象的mock.documentAdded()方法,并且把document的title作为调用的参数。
public void testAddDocument() { mock.documentAdded("New Document"); // 2 replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]); }
So in the record state (before calling replay), the Mock Object does not behave like a Mock Object, but it records method calls. After calling replay, it behaves like a Mock Object, checking whether the expected method calls are really done.
可见,在record状态(调用replay之前),Mock对象不展现Mock对象的行为(模拟接口的实现),它仅仅记录方法的调用。在调用replay后,它开始以Mock对象的行为进行工作,检查预期的方法调用是否真地完成。
If classUnderTest.addDocument("New Document", new byte[0]) calls the expected method with a wrong argument, the Mock Object will complain with an AssertionError:
如果classUnderTest.addDocument(“New Document”, new byte[0]) 以错误的参数调用了预期的方法,Mock对象将抛出AssertionError:
java.lang.AssertionError:
Unexpected method call documentAdded("Wrong title"): documentAdded("New Document"): expected: 1, actual: 0 at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:61)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:28)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
All missed expectations are shown, as well as all fulfilled expectations for the unexpected call (none in this case). If the method call is executed too often, the Mock Object complains, too:
所有没有实现的预期调用会显示出来,满足期望的调用也一起显示(本例中还没有)。如果这个方法调用次数过多(译注:没有按照replay前指定的调用次数),Mock对象也会报错:
java.lang.AssertionError:
Unexpected method call documentAdded("New Document"): documentAdded("New Document"): expected: 1, actual: 1 (+1) at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentAdded(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentAdded(ClassUnderTest.java:62)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:29)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:30)
...
Verifying Behavior 验证行为
There is one error that we have not handled so far: If we specify behavior, we would like to verify that it is actually used. The current test would pass if no method on the Mock Object is called. To verify that the specified behavior has been used, we have to call verify(mock):
目前为止,有一个错误我们还未处理:当我们指定了行为,我们需要验证那确实发生了。当前的测试将忽略Mock对象没有被调用的情形。为了验证指定的调用行为确实发生了,要调用verify(mock)进行验证:
public void testAddDocument() { mock.documentAdded("New Document"); // 2 replay(mock); // 3
classUnderTest.addDocument("New Document", new byte[0]); verify(mock);
}
If the method is not called on the Mock Object, we now get the following exception:
如果Mock对象的方法没有被调用过,将会得到下面的异常:
java.lang.AssertionError:
Expectation failure on verify:
documentAdded("New Document"): expected: 1, actual: 0 at org.easymock.internal.MocksControl.verify(MocksControl.java:70)
at org.easymock.EasyMock.verify(EasyMock.java:536)
at org.easymock.samples.ExampleTest.testAddDocument(ExampleTest.java:31)
...
The message of the exception lists all missed expectations.
这些信息显示了没有被预期到的情况。
Expecting an Explicit Number of Calls 期望指定次数的方法调用
Up to now, our test has only considered a single method call. The next test should check whether the addition of an already existing document leads to a call to mock.documentChanged() with the appropriate argument. To be sure, we check this three times (hey, it is an example ;-)):
目前为止,我们的测试只考虑了单个方法的调用。接下来的测试将检验加入一个已经存在的document是否会导致mock.documentChanged()被正确地调用。为了更确定,我们将检验3次(嘿,别见怪,这只是个例子;-)):
public void testAddAndChangeDocument() { mock.documentAdded("Document"); mock.documentChanged("Document"); mock.documentChanged("Document"); mock.documentChanged("Document"); replay(mock);
classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); verify(mock);
}
To avoid the repetition of mock.documentChanged("Document"), EasyMock provides a shortcut. We may specify the call count with the method times(int times) on the object returned by expectLastCall(). The code then looks like:
为了避免(大量的)重复写mock.documentChanged(“Document”),EasyMock提供了一个捷径。可以用expectLastCall()返回的对象的times(int times)方法指定某个方法要被调用的次数。代码如下:
public void testAddAndChangeDocument() { mock.documentAdded("Document"); mock.documentChanged("Document"); expectLastCall().times(3);
replay(mock);
classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); classUnderTest.addDocument("Document", new byte[0]); verify(mock);
}
If the method is called too often, we get an exception that tells us that the method has been called too many times. The failure occurs immediately at the first method call exceeding the limit:
如果方法调用次数过多,就会抛出异常通知我们某个方法被过多地调用了。在调用次数一旦超过预期次数时,就会出现这个错误。
java.lang.AssertionError:
Unexpected method call documentChanged("Document"): documentChanged("Document"): expected: 3, actual: 3 (+1) at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:29)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:44)
at $Proxy0.documentChanged(Unknown Source)
at org.easymock.samples.ClassUnderTest.notifyListenersDocumentChanged(ClassUnderTest.java:67)
at org.easymock.samples.ClassUnderTest.addDocument(ClassUnderTest.java:26)
at org.easymock.samples.ExampleTest.testAddAndChangeDocument(ExampleTest.java:43)
...
If there are too few calls,