| JDK | 1.5 |
| JUnit | 3.8 |
| EasyMock | 2.2 |
| Eclipse | 3.1.2 |
| MyEclipse | 4.1.0 |
注:EasyMock2.0+只支持JDK1.5+,因為其代碼中用到了很多新增特性,比如for(:)、method(...)等用法。EasyMock最初的1.0版本似乎支持對實(shí)體類(lèi)的模擬,但是貌似在很面的版本中和快就取消了這個(gè)功能。
EasyMock provides Mock Objects for interfaces in JUnit tests by generating them on the fly using Java‘s proxy mechanism. Due to EasyMock‘s unique style of recording expectations, most refactorings will not affect the Mock Objects. So EasyMock is a perfect fit for Test-Driven Development.
EasyMock是用于JUnit中的虛擬測試輔助包,它提供對interface類(lèi)的模擬,能夠通過(guò)錄制、回放、檢查三步來(lái)完成大體的測試過(guò)程,可以驗證方法的調用種類(lèi)、次數、順序,可以令mock對象返回指定的值或拋出指定異常。
EasyMock是放在sourceforge上的一個(gè)開(kāi)源項目,可以自由下載,從那里下載到對應版本的壓縮包后可以直接解壓,對于開(kāi)發(fā),我們只需要easymock.jar這個(gè)文件,把它添加到Eclipse的項目jar庫里就可以使用了。
另外,由于它是用于在JUnit環(huán)境下測試的包,所以在實(shí)際使用的時(shí)候還需要添加JUnit.jar
在正式編程之前,還需要搞懂一些相關(guān)概念,這些有的也是類(lèi)的名字,但是在這里主要是理解他所代表的概念,方便進(jìn)一步使用EasyMock。
Method、Arguments、Invocation
ExpectedInvocation、actual
MockControl、Mock
reset()、replay()、verify()等。
這三個(gè)概念有從屬關(guān)系,method代表要模擬的類(lèi)的一個(gè)方法,arguments是這個(gè)方法的入口參數,invocation代表一次模擬類(lèi)的某 個(gè)方法的調用,它包含一個(gè)method,若干argument(但是在這里不包括返回值)。在EasyMock中有Invocation這個(gè)類(lèi),含有 Object[] arguments、Method method、Object mock參數。
前者是代表一次預期的方法調用,這里的預期是指加入了Matcher(s)的Invocation,不僅要具有Invocation的特征,還要加 上對其入口參數的檢驗器(Matcher),這一概念的引入是為了保證可以判斷類(lèi)似數組這種對象的比較關(guān)系,或者為入口參數設定合法條件(不僅是簡(jiǎn)單的相 等,還有大于等于,字符串的endwith等,用戶(hù)只要按照它的規則,也可以自己制作專(zhuān)門(mén)的matcher)。與其相關(guān)的類(lèi)有 ExpectedInvocation、ExpectedInvocationAndResult、 ExpectedInvocationAndResults,后面兩個(gè)類(lèi)加入了指定的返回值,是對有返回值的函數適用的。
而后面的actual是經(jīng)常出現在EasyMock源代碼中作為參數使用的單詞,用于代表replay過(guò)程中的一次實(shí)際的方法調用,和Invocation屬于一種概念。
MockControl是控制類(lèi),他負責建立整個(gè)框架所需的資源,其成員behavior和RecordState state用于保存方法調用的序列,一個(gè)control可以同時(shí)管理多個(gè)mock。Mock對象對應一個(gè)你需要測試的待測試類(lèi),它會(huì )自動(dòng)建立 JavaProxyFactory<toMock>,再由JavaProxyFactory建立Proxy;同時(shí)建立的還有 ObjectMethodFilter(他持有一個(gè)MockInvocationHandler對象,對hashCode()、equals()、 toString()三個(gè)方法進(jìn)行判斷)和MockInvocationHandler(他持有一個(gè)control對象,似乎是用于添加 Invocation序列的)。
reset()方法是將control對象復位,其內部現實(shí)是靠新建behavior和state兩個(gè)對象完成的。replay()是結束錄制過(guò) 程,他會(huì )調用RecordState.closeMethod()方法來(lái)完成大部分工作。verify()是用于在錄制和回放兩個(gè)步驟完成之后進(jìn)行預期和 實(shí)際結果的檢查。
public interface BaseInterface{
public void methodABC();
public SomeValue methodABC(SomeArgument someArgument);
}
ImplementsBaseInterface繼承了BaseInterface接口,提供方法的具體實(shí)現。
public class TargetClass{
private BaseInterface member;
……//其他成員
public void somMethods(){
……
member.methodABC();
member.methodABC(someArguments,……);
}
public void setMemeber(BaseInterface member){
this.memeber = member;
}
……//其他方法
}
而TargetClass可能會(huì )有一些內部成員,比如現在我們正好有一個(gè)BaseInterface類(lèi)型的內部成員member,而 TargetClass正好有someMethods()需要調用到BaseInterface中的methodABC()和methodABC (SomeArgument someArgument)。
TargetClassTest是一個(gè)JUnit Test Case類(lèi),用于測試TargetClass這個(gè)類(lèi),這是我們的目的。
import static org.easymock.EasyMock.*;
import ……
public class TargetClassTest{
private TargetClass target;
private BaseInterface mock;
private IMocksControl ctrl;
public void setUp(){
ctrl = createStrictControl();
mock = ctrl.createMock( ImplementsBaseInterface.class ); //建立easymock測試類(lèi)
target = new TargetClass();
target.setMember(mock); //將剛剛建立的mock加入到target的成員中
}
public void testSomeMethods() throws Exception{
……//準備工作,生成一些將要使用的對象
ctrl.reset(); //初始化
mock.methodABC();
expect(mock.methodABC(someArguments,……)).andReturn(SomeValue); //由于這個(gè)方法有返回值,所以需要用andReturn()方法來(lái)指定,否則測試的時(shí)候會(huì )扔出異常
ctrl.replay(); //結束整個(gè)control的recorder過(guò)程,下面進(jìn)入回放
target.someMethods();
ctrl.verify(); //結束回放,進(jìn)行檢查,主要檢查回放過(guò)程和錄制過(guò)程是否一致,對于mock對象的方法調用種類(lèi)、數量、順序是否符合要求進(jìn)行驗證。如果有錯誤出現就會(huì )拋出異常,但是沒(méi)有自動(dòng)檢查返回值的功能。
}
}
當只有一個(gè)mock對象的時(shí)候,我們也可以不手動(dòng)生成control對象,而是直接調用EasyMock類(lèi)中的createMock(Class clazz)方法,這樣的話(huà),相應的reset()、replay()、verify()方法也不能使用control的,而要使用EasyMock類(lèi)中 的reset(mock)、replay(mock)、verify(mock)方法,只對單一的一個(gè)mock對象進(jìn)行操作,在有多個(gè)mock對象的時(shí)候 可以做到異步操作(但是這樣做似乎意義不大)。但是實(shí)際上,control對象是一定會(huì )生成的,無(wú)論你是否手動(dòng)生成。
生成control對象的好處在于當有多個(gè)mock的時(shí)候可以一起執行reset()、replay()、verify()操作。
這個(gè)方法只是用于當mock對象的方法需要有返回值的情況下,手動(dòng)設置這個(gè)方法的返回值給調用的測試類(lèi)的。在本例中methodABC (arguments,……)方法就需要在recorder的時(shí)候用andReturn()方法指定返回值給TargetClass。如果對于有返回值的 方法不指定其返回值,在測試的時(shí)候會(huì )拋出"java.lang.IllegalStateException: missing behavior definition for the preceeding method call XXX"異常。
EasyMock提供了三種Mock類(lèi)型:StrictMock、NiceMock、Mock。
| 種類(lèi) | 生成函數 | 檢查順序 | 檢查方法是否調用 | 對未說(shuō)明的方法調用 |
| Mock | createMock() | 否 | 是 | 拋出異常 |
| NiceMock | createNiceMock() | 否 | 是 | 返回0、null,不拋出異常 |
| StrictMock | createStrictMock() | 是 | 是 | 拋出異常 |
對于recorder期的方法調用還有一些設定調用次數的方法比較重要,可能會(huì )用到,比如說(shuō)times(),可以設定調用出現的次數使用方法類(lèi)似andReturn(value)
expect(***).times(3); //指定這個(gè)方法調用出現三次
相當于
expect(***);
expect(***);
expect(***);
還可以設定它出現的次數為不封頂,或在一個(gè)范圍之內,這些方法都可以在MocksControl類(lèi)中找到
times(int);
times(min , max); //將max設為整數最大值就是不封頂,其實(shí)設大點(diǎn)也就夠用了
anyTimes();
once();
看了一天半的時(shí)間,對他的原理有了一些認識,但是還不全面,對一些深層的原理還了解得不太清楚,先簡(jiǎn)單談一談目前的認識,很可能會(huì )有很多錯誤的地方,如果以后發(fā)現,會(huì )陸續修改。
EasyMock是利用線(xiàn)程來(lái)捕獲mock對象函數調用的,所以他特別有一個(gè)LastControl類(lèi),里面有三個(gè)成員, 用于將線(xiàn)程與三個(gè)不同的類(lèi)相關(guān)聯(lián),matcher是匹配器、argument是入口參數、control中含有大部分主要內容。
private static final WeakHashMap<Thread, MocksControl> threadToControl
= new WeakHashMap<Thread, MocksControl>();
private static final WeakHashMap<Thread, Stack<Object[]>> threadToCurrentArguments
= new WeakHashMap<Thread, Stack<Object[]>>();
private static final WeakHashMap<Thread, Stack<IArgumentMatcher>> threadToArgumentMatcherStack
= new WeakHashMap<Thread, Stack<IArgumentMatcher>>();
困惑:matcher和argument應該都是和指定的Invocation相關(guān)聯(lián),但是這里只存了control,而一個(gè)control可以有多個(gè)mock,一個(gè)mock又能有多個(gè)method過(guò)程,怎樣確定matcher、argument和method的對應關(guān)系?是通過(guò)線(xiàn)程實(shí)現的么?
他將recorder到的method及其參數、times都記錄到線(xiàn)性表中,而replay的對應method、參數、times也都記錄下來(lái),進(jìn)行對比,從而實(shí)現verify()功能。
Invocation對應一次具體的方法調用過(guò)程,可能包含返回值Result。
與之對應的ExpectedInvocation系列類(lèi)加入了matcher和result,也就是加入了預期的功能。
UnorderedBehavior是最底層的Behavior類(lèi),其中主要有addExpected()和addActual ()兩個(gè)方法,應該可以完成預期的保存和檢驗兩個(gè)功能。他有一個(gè)ExpectedInvocationAndResults的List,可以記錄一些列的 方法預期,可以指定應記錄一個(gè)mock,但是這點(diǎn)還沒(méi)有證實(shí)。
recorder和replay都是主要靠IMocksControlState接口的實(shí)現類(lèi):RecorderState和ReplayState,各司其職。
RecorderState的成員是:ExpectedInvocation lastInvocation、Result lastResult、IMocksBehavior behavior behavior用于記錄所有提交進(jìn)來(lái)的ExpectedInvocation,而那兩個(gè)lastXXX,應該是用于保留最后一條記錄。verify()方法會(huì )直接拋出異常:calling verify is not allowed in record state
聯(lián)系客服