閑人野居
          好好學(xué)習(xí),天天向上
          posts - 57,  comments - 137,  trackbacks - 0

          關(guān)于單元測(cè)試,模擬對(duì)象一直是不可缺少的,尤其對(duì)于復(fù)雜的應(yīng)用來說。
          ?????? 這么多的模擬對(duì)象框架中,個(gè)人覺得比較好用的當(dāng)屬EasyMock了。當(dāng)然JMock也不錯(cuò)。
          ?????? 下面簡(jiǎn)單介紹一下EasyMock?。(基本翻譯EasyMock的文檔,可能有些地方不是很恰當(dāng))
          ?????
          ???????EasyMock 2
          主要用于給指定的接口提供模擬對(duì)象。

          模擬對(duì)象只是模擬領(lǐng)域代碼直接的部分行為,能檢測(cè)是否他們?nèi)缍x中的被使用。使用 Mock 對(duì)象,來模擬合作接口,有助于隔離測(cè)試相應(yīng)的領(lǐng)域類。

          創(chuàng)建和維持 Mock 對(duì)象經(jīng)常是繁瑣的任務(wù),并且可能會(huì)引入錯(cuò)誤。 EasyMock 2 動(dòng)態(tài)產(chǎn)生 Mock 對(duì)象,不需要?jiǎng)?chuàng)建,并且不會(huì)產(chǎn)生代碼。

          有利的方面:

          不需要手工寫類來處理 mock 對(duì)象。

          支持安全的重構(gòu) Mock 對(duì)象:測(cè)試代碼不會(huì)在運(yùn)行期打斷當(dāng)重新命名方法或者更改方法參數(shù)。

          支持返回值和例外。

          支持檢察方法調(diào)用次序,對(duì)于一個(gè)或者多個(gè) Mock 對(duì)象。

          不利的方面: 2.0 僅使用于 java 2 版本 5.0 或者以上
          ????

          ??? 以一個(gè)例子來說明如何使用EasyMock:
          ???假設(shè)有一個(gè)合作接口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);
          }

          我們主要的測(cè)試類為:
          package org.easymock.samples;
          import java.util.HashMap;
          import java.util.HashSet;
          import java.util.Map;
          import java.util.Set;
          public class ClassUnderTest {
          ??? private Set<Collaborator> listeners = new HashSet<Collaborator>();
          ??? private Map<String, byte[]> documents = new HashMap<String, byte[]>();
          ??? public void addListener(Collaborator listener) {
          ??????? listeners.add(listener);
          ??? }
          ??? public void addDocument(String title, byte[] document) {
          ??????? boolean documentChange = documents.containsKey(title);
          ??????? documents.put(title, document);
          ??????? if (documentChange) {
          ??????????? notifyListenersDocumentChanged(title);
          ??????? } else {
          ??????????? notifyListenersDocumentAdded(title);
          ??????? }
          ??? }
          ??? public boolean removeDocument(String title) {
          ??????? if (!documents.containsKey(title)) {
          ??????????? return true;
          ??????? }
          ??????? if (!listenersAllowRemoval(title)) {
          ??????????? return false;
          ??????? }
          ??????? documents.remove(title);
          ??????? notifyListenersDocumentRemoved(title);
          ??????? return true;
          ??? }
          ??? public boolean removeDocuments(String[] titles) {
          ??????? if (!listenersAllowRemovals(titles)) {
          ??????????? return false;
          ??????? }
          ??????? for (String title : titles) {
          ??????????? documents.remove(title);
          ??????????? notifyListenersDocumentRemoved(title);
          ??????? }
          ??????? return true;
          ??? }
          ??? private void notifyListenersDocumentAdded(String title) {
          ??????? for (Collaborator listener : listeners) {
          ??????????? listener.documentAdded(title);
          ??????? }
          ??? }
          ??? private void notifyListenersDocumentChanged(String title) {
          ??????? for (Collaborator listener : listeners) {
          ??????????? listener.documentChanged(title);
          ??????? }
          ??? }
          ??? private void notifyListenersDocumentRemoved(String title) {
          ??????? for (Collaborator listener : listeners) {
          ??????????? listener.documentRemoved(title);
          ??????? }
          ??? }
          ??? private boolean listenersAllowRemoval(String title) {
          ??????? int result = 0;
          ??????? for (Collaborator listener : listeners) {
          ??????????? result += listener.voteForRemoval(title);
          ??????? }
          ??????? return result > 0;
          ??? }
          ??? private boolean listenersAllowRemovals(String[] titles) {
          ??????? int result = 0;
          ??????? for (Collaborator listener : listeners) {
          ??????????? result += listener.voteForRemovals(titles);
          ??????? }
          ??????? return result > 0;
          ??? }
          }

          第一個(gè)Mock 對(duì)象

          我們將創(chuàng)建test case 并且圍繞此理解相關(guān)的EasyMock 包的功能。第一個(gè)測(cè)試方法,用于檢測(cè)是否刪除一個(gè)不存在的文檔,不會(huì)發(fā)通知給合作類。
          ??????????
          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");
          ??? }
          }
          ????對(duì)于多數(shù)測(cè)試類,使用EasyMock 2,我們只需要靜態(tài)引入org.easymock.EasyMock的方法。??????
          ?

          import static org.easymock.EasyMock.*;

          import junit.framework.TestCase;

          ?

          public class ExampleTest extends TestCase {

          ?

          ??? private ClassUnderTest classUnderTest;

          ??? private Collaborator mock;

          ???

          }

          ?????

          為了取得Mock 對(duì)象,需要:

          l???????? 創(chuàng)建Mock 對(duì)象從需要模擬的接口

          l???????? 記錄期待的行為

          l???????? 轉(zhuǎn)換到Mock對(duì)象,replay狀態(tài)。

          例如:?????
          ?
          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");
          ??? }
          ??

          在執(zhí)行第三步后,mock Collaborator接口的Mock對(duì)象,并且期待沒有什么調(diào)用。這就意味著,如果我們改變ClassUnderTest去調(diào)用此接口的任何方法,則Mock對(duì)象會(huì)拋出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)
          ??? ...

          增加行為

          ?????? 讓我們開始第二個(gè)測(cè)試。如果documentclassUnderTest增加,我們期待調(diào)用
          mock.documentAdded()Mock對(duì)象使用document的標(biāo)題作為參數(shù):
          ?
          ?public void testAddDocument() {
          ?????? ?mock.documentAdded("New Document"); // 2
          ??????? replay(mock); // 3
          ??????? classUnderTest.addDocument("New Document", new byte[0]); 
          ????}
          如果classUnderTest.addDocument("New Document", new byte[0])調(diào)用期待的方法,使用錯(cuò)誤的參數(shù),Mock對(duì)象會(huì)拋出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)
          ??? ...

          同樣,如果調(diào)用多次此方法,則也會(huì)拋出例外:

          ?
          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)
          ??? ...

          驗(yàn)證行為

          ?????? 當(dāng)我們指定行為后,我們將驗(yàn)證實(shí)際發(fā)生的。當(dāng)前的測(cè)試將會(huì)判斷是否Mock對(duì)象會(huì)真實(shí)調(diào)用。可以調(diào)用verify(mock)來山正是否指定的行為被調(diào)用。

          ?
          public void testAddDocument() {
          ??????? mock.documentAdded("New Document"); // 2 
          ????????replay(mock); // 3
          ??????? classUnderTest.addDocument("New Document", new byte[0]);
          ?????? ?verify(mock);
          ??? }

          如果失敗,則拋出AssertionError

          期待明顯數(shù)量的調(diào)用

          到現(xiàn)在,我們的測(cè)試只是調(diào)用一個(gè)簡(jiǎn)單的方法。下一個(gè)測(cè)試將會(huì)檢測(cè)是否已經(jīng)存在document導(dǎo)致mock.documentChanged()調(diào)用。為了確認(rèn),調(diào)用三次

          ?
          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);
          ??? }

          為了避免重復(fù)的mock.documentChanged("Document"),EasyMock提供一個(gè)快捷方式。可以通過調(diào)用方法expectLastCall().times(int times)來指定最后一次調(diào)用的次數(shù)。

          ?
          ?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);
          ??? }

          指定返回值

          ?????? 對(duì)于指定返回值,我們通過封裝expect(T value)返回的對(duì)象并且指定返回的值,使用方法andReturn(Object returnValue)expect(T value).返回的對(duì)象。

          例如:

          ?
          public void testVoteForRemoval() {
          ??????? mock.documentAdded("Document");?? // expect document addition
          ??????? // expect to be asked to vote for document removal, and vote for it
          ??????? expect(mock.voteForRemoval("Document")).andReturn((byte) 42);
          ??????? mock.documentRemoved("Document"); // expect document removal
          ??????? replay(mock);
          ??????? classUnderTest.addDocument("Document", new byte[0]);
          ??????? assertTrue(classUnderTest.removeDocument("Document"));
          ??????? verify(mock);
          ??? } 
          ?
          ??? public void testVoteAgainstRemoval() {
          ??????? mock.documentAdded("Document");?? // expect document addition
          ??????? // expect to be asked to vote for document removal, and vote against it
          ??????? expect(mock.voteForRemoval("Document")).andReturn((byte) -42);
          ??????? replay(mock);
          ??????? classUnderTest.addDocument("Document", new byte[0]);
          ??????? assertFalse(classUnderTest.removeDocument("Document"));
          ? ??????verify(mock);
          ??? }
          取代expect(T value)調(diào)用,可以通過expectLastCall().來代替
          ?expect(mock.voteForRemoval("Document")).andReturn((byte) 42);

          等同于

          ?
          mock.voteForRemoval("Document");
          expectLastCall().andReturn((byte) 42);

          處理例外

          對(duì)于指定的例外(更確切的:Throwables)被拋出,由expectLastCall()expect(T value)返回的對(duì)象,提供了方法andThrow(Throwable throwable)。方法不得不被調(diào)用記錄狀態(tài),在調(diào)用Mock對(duì)象后,對(duì)于此指定了要拋出的Throwable


          基本的方法,已經(jīng)說完了,當(dāng)然這不能完全說明EasyMock的使用。更多的因素請(qǐng)參考EasyMock的文檔
          http://www.easymock.org/Documentation.html
          posted on 2006-09-20 20:38 布衣郎 閱讀(3078) 評(píng)論(1)  編輯  收藏 所屬分類: 單元測(cè)試

          FeedBack:
          # re: EasyMock 2 使用指南
          2006-09-21 16:14 | 123bingbing
          如果你是編程高手,這里將是你一個(gè)展現(xiàn)自我的新舞臺(tái)----www.mylinux.com.cn  回復(fù)  更多評(píng)論
            

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           

          <2006年9月>
          272829303112
          3456789
          10111213141516
          17181920212223
          24252627282930
          1234567

          常用鏈接

          留言簿(12)

          隨筆分類(59)

          隨筆檔案(57)

          blog

          java

          uml

          搜索

          •  

          積分與排名

          • 積分 - 357329
          • 排名 - 155

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 阳原县| 北安市| 潼南县| 公安县| 沧源| 喀喇| 石林| 侯马市| 密云县| 梨树县| 高阳县| 赤水市| 湘阴县| 洪泽县| 江源县| 海口市| 绥滨县| 白朗县| 星子县| 江华| 铁力市| 凤庆县| 历史| 荆州市| 四川省| 金平| 运城市| 固镇县| 上高县| 宜良县| 新乐市| 藁城市| 永宁县| 馆陶县| 香港 | 石台县| 奈曼旗| 廊坊市| 兴和县| 阿克| 玉溪市|