常用鏈接

          統(tǒng)計(jì)

          最新評論

          EasyMock 2 使用指南(轉(zhuǎn))

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

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

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

          有利的方面:

          不需要手工寫類來處理 mock 對象。

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

          支持返回值和例外。

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

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

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

          我們主要的測試類為:
                      
          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;
              }
          }

          第一個Mock 對象

          我們將創(chuàng)建test case 并且圍繞此理解相關(guān)的EasyMock 包的功能。第一個測試方法,用于檢測是否刪除一個不存在的文檔,不會發(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");
              }
          }
              對于多數(shù)測試類,使用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 對象,需要:

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

          l         記錄期待的行為

          l         轉(zhuǎn)換到Mock對象,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對象,并且期待沒有什么調(diào)用。這就意味著,如果我們改變ClassUnderTest去調(diào)用此接口的任何方法,則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)
              ...

          增加行為

                 讓我們開始第二個測試。如果documentclassUnderTest增加,我們期待調(diào)用
          mock.documentAdded()在Mock對象使用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)用期待的方法,使用錯誤的參數(shù),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)
              ...

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

                      
           
                      
          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)前的測試將會判斷是否Mock對象會真實(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)在,我們的測試只是調(diào)用一個簡單的方法。下一個測試將會檢測是否已經(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提供一個快捷方式。可以通過調(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);
              }

          指定返回值

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

          例如:

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

          處理例外

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


          基本的方法,已經(jīng)說完了,當(dāng)然這不能完全說明EasyMock的使用。更多的因素請參考EasyMock的文檔
          http://www.easymock.org/Documentation.html

          posted on 2007-10-24 13:49 九寶 閱讀(267) 評論(0)  編輯  收藏 所屬分類: Java

          主站蜘蛛池模板: 石河子市| 呼和浩特市| 江川县| 五家渠市| 中西区| 长乐市| 化德县| 成安县| 西平县| 伊吾县| 宝山区| 腾冲县| 贞丰县| 保山市| 那曲县| 深水埗区| 辽阳市| 北安市| 辽宁省| 静安区| 盘山县| 顺义区| 盐边县| 麻城市| 鸡西市| 平谷区| 揭东县| 全南县| 民乐县| 湄潭县| 宁安市| 崇左市| 清流县| 玉树县| 舒城县| 稻城县| 民勤县| 巴楚县| 灵寿县| 安达市| 镇江市|