您的位置:軟件測試 > 開源軟件測試 > 開源單元測試工具 >
用EasyMock更輕松地測試
作者:網(wǎng)絡轉載 發(fā)布時間:[ 2013/2/21 13:56:57 ] 推薦標簽:

設置預期

EasyMock 不只是能夠用固定的結果響應固定的輸入。它還可以檢查輸入是否符合預期。例如,假設 toEuros() 方法有一個 bug(見清單 5),它返回以歐元為單位的結果,但是獲取的是加拿大元的匯率。這會讓客戶發(fā)一筆意外之財或遭受重大損失。

清單 5. 有 bug 的 toEuros() 方法

    
public Currency toEuros(ExchangeRate converter) {
    if ("EUR".equals(units)) return this;
    else {
        double input = amount + cents/100.0;
        double rate;
        try {
            rate = converter.getRate(units, "CAD");
            double output = input * rate;
            return new Currency(output, "EUR");
        } catch (IOException e) {
            return null;
        }
    }
}


但是,不需要為此編寫另一個測試。清單 4 中的 testToEuros 能夠捕捉到這個 bug。當對這段代碼運行清單 4 中的測試時,測試會失敗并顯示以下錯誤消息:

"java.lang.AssertionError:
  Unexpected method call getRate("USD", "CAD"):
    getRate("USD", "EUR"): expected: 1, actual: 0".


注意,這并不是我設置的斷言。EasyMock 注意到我傳遞的參數(shù)不符合測試用例。

在默認情況下,EasyMock 只允許測試用例用指定的參數(shù)調用指定的方法。但是,有時候這有點兒太嚴格了,所以有辦法放寬這一限制。例如,假設希望允許把任何字符串傳遞給 getRate() 方法,而不于 USD 和 EUR。那么,可以指定 EasyMock.anyObject() 而不是顯式的字符串,如下所示:

EasyMock.expect(mock.getRate(
       (String) EasyMock.anyObject(),
       (String) EasyMock.anyObject())).andReturn(1.5);


還可以更挑剔一點兒,通過指定 EasyMock.notNull() 只允許非 null 字符串:

EasyMock.expect(mock.getRate(
        (String) EasyMock.notNull(),
        (String) EasyMock.notNull())).andReturn(1.5);


靜態(tài)類型檢查會防止把非 String 對象傳遞給這個方法。但是,現(xiàn)在允許傳遞 USD 和 EUR 之外的其他 String。還可以通過 EasyMock.matches() 使用更顯式的正則表達式。下面指定需要一個三字母的大寫 ASCII String:

EasyMock.expect(mock.getRate(
        (String) EasyMock.matches("[A-Z][A-Z][A-Z]"),
        (String) EasyMock.matches("[A-Z][A-Z][A-Z]"))).andReturn(1.5);


使用 EasyMock.find() 而不是 EasyMock.matches(),可以接受任何包含三字母大寫子 String 的 String。

EasyMock 為基本數(shù)據(jù)類型提供相似的方法:

    EasyMock.anyInt()
    EasyMock.anyShort()
    EasyMock.anyByte()
    EasyMock.anyLong()
    EasyMock.anyFloat()
    EasyMock.anyDouble()
    EasyMock.anyBoolean()

對于數(shù)字類型,還可以使用 EasyMock.lt(x) 接受小于 x 的任何值,或使用 EasyMock.gt(x) 接受大于 x 的任何值。

在檢查一系列預期時,可以捕捉一個方法調用的結果或參數(shù),然后與傳遞給另一個方法調用的值進行比較。后,通過定義定制的匹配器,可以檢查參數(shù)的任何細節(jié),但是這個過程比較復雜。但是,對于大多數(shù)測試,EasyMock.anyInt()、EasyMock.matches() 和 EasyMock.eq() 這樣的基本匹配器已經足夠了。

嚴格的 mock 和次序檢查

EasyMock 不僅能夠檢查是否用正確的參數(shù)調用預期的方法。它還可以檢查是否以正確的次序調用這些方法,而且只調用了這些方法。在默認情況下,不執(zhí)行這種檢查。要想啟用它,應該在測試方法末尾調用 EasyMock.verify(mock)。例如,如果 toEuros() 方法不只一次調用 getRate(),清單 6 會失敗。

清單 6. 檢查是否只調用 getRate() 一次

    
public void testToEuros() throws IOException {
    Currency expected = new Currency(3.75, "EUR");
    ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
    EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
    EasyMock.replay(mock);
    Currency actual = testObject.toEuros(mock);
    assertEquals(expected, actual);
    EasyMock.verify(mock);
}


EasyMock.verify() 究竟做哪些檢查取決于它采用的操作模式:

    Normal — EasyMock.createMock() :必須用指定的參數(shù)調用所有預期的方法。但是,不考慮調用這些方法的次序。調用未預期的方法會導致測試失敗。

    Strict — EasyMock.createStrictMock() :必須以指定的次序用預期的參數(shù)調用所有預期的方法。調用未預期的方法會導致測試失敗。

    Nice — EasyMock.createNiceMock() :必須以任意次序用指定的參數(shù)調用所有預期的方法。調用未預期的方法不會 導致測試失敗。Nice mock 為沒有顯式地提供 mock 的方法提供合理的默認值。返回數(shù)字的方法返回 0,返回布爾值的方法返回 false。返回對象的方法返回 null。

檢查調用方法的次序和次數(shù)對于大型接口和大型測試更有意義。例如,請考慮 org.xml.sax.ContentHandler 接口。如果要測試一個 XML 解析器,希望輸入文檔并檢查解析器是否以正確的次序調用 ContentHandler 中正確的方法。例如,請考慮清單 7 中的簡單 XML 文檔:

清單 7. 簡單的 XML 文檔

    
<root>
  Hello World!
</root>


根據(jù) SAX 規(guī)范,在解析器解析文檔時,它應該按以下次序調用這些方法:

    setDocumentLocator()
    startDocument()
    startElement()
    characters()
    endElement()
    endDocument()

但是,更有意思的是,對 setDocumentLocator() 的調用是可選的;解析器可以多次調用 characters()。它們不需要在一次調用中傳遞盡可能多的連續(xù)文本,實際上大多數(shù)解析器不這么做。即使是對于清單 7 這樣的簡單文檔,也很難用傳統(tǒng)的方法測試 XML 解析器,但是 EasyMock 大大簡化了這個任務,見清單 8:

清單 8. 測試 XML 解析器

    
import java.io.*;
import org.easymock.EasyMock;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import junit.framework.TestCase;

public class XMLParserTest extends TestCase {

    private  XMLReader parser;

    protected void setUp() throws Exception {
        parser = XMLReaderFactory.createXMLReader();
    }

    public void testSimpleDoc() throws IOException, SAXException {
        String doc = "<root>   Hello World! </root>";
        ContentHandler mock = EasyMock.createStrictMock(ContentHandler.class);

        mock.setDocumentLocator((Locator) EasyMock.anyObject());
        EasyMock.expectLastCall().times(0, 1);
        mock.startDocument();
        mock.startElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"),
                (Attributes) EasyMock.anyObject());
        mock.characters((char[]) EasyMock.anyObject(),
                EasyMock.anyInt(), EasyMock.anyInt());
        EasyMock.expectLastCall().atLeastOnce();
        mock.endElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"));
        mock.endDocument();
        EasyMock.replay(mock);

        parser.setContentHandler(mock);
        InputStream in = new ByteArrayInputStream(doc.getBytes("UTF-8"));
        parser.parse(new InputSource(in));

        EasyMock.verify(mock);
    }
}


這個測試展示了幾種新技巧。首先,它使用一個 strict mock,因此要求符合指定的次序。例如,不希望解析器在調用 startDocument() 之前調用 endDocument()。

第二,要測試的所有方法都返回 void。這意味著不能把它們作為參數(shù)傳遞給 EasyMock.expect()(像對 getRate() 所做的)。(EasyMock 在許多方面能夠 “欺騙” 編譯器,但是還不足以讓編譯器相信 void 是有效的參數(shù)類型)。因此,要在 mock 上調用 void 方法,由 EasyMock 捕捉結果。如果需要修改預期的細節(jié),那么在調用 mock 方法之后立即調用 EasyMock.expectLastCall()。另外注意,不能作為預期參數(shù)傳遞任何 String、int 和數(shù)組。必須先用 EasyMock.eq() 包裝它們,這樣才能在預期中捕捉它們的值。

清單 8 使用 EasyMock.expectLastCall() 調整預期的方法調用次數(shù)。在默認情況下,預期的方法調用次數(shù)是一次。但是,我通過調用 .times(0, 1) 把 setDocumentLocator() 設置為可選的。這指定調用此方法的次數(shù)必須是零次或一次。當然,可以根據(jù)需要把預期的方法調用次數(shù)設置為任何范圍,比如 1-10 次、3-30 次。對于 characters(),我實際上不知道將調用它多少次,但是知道必須至少調用一次,所以對它使用 .atLeastOnce()。如果這是非 void 方法,可以對預期直接應用 times(0, 1) 和 atLeastOnce()。但是,因為這些方法返回 void,所以必須通過 EasyMock.expectLastCall() 設置它們。

后注意,這里對 characters() 的參數(shù)使用了 EasyMock.anyObject() 和 EasyMock.anyInt()。這考慮到了解析器向 ContentHandler 傳遞文本的各種方式。

mock 和真實性

有必要使用 EasyMock 嗎?其實,手工編寫的 mock 類也能夠實現(xiàn) EasyMock 的功能,但是手工編寫的類只能適用于某些項目。例如,對于 清單 3,手工編寫一個使用匿名內部類的 mock 也很容易,代碼很緊湊,對于不熟悉 EasyMock 的開發(fā)人員可讀性可能更好。但是,它是一個專門為本文構造的簡單示例。在為 org.w3c.dom.Node(25 個方法)或 java.sql.ResultSet(139 個方法而且還在增加)這樣的大型接口創(chuàng)建 mock 時,EasyMock 能夠大大節(jié)省時間,以低的成本創(chuàng)建更短更可讀的代碼。

后,提出一條警告:使用 mock 對象可能做得太過分?赡馨烟嗟臇|西替換為 mock,導致即使在代碼質量很差的情況下,測試仍然總是能夠通過。替換為 mock 的東西越多,接受測試的東西越少。依賴庫以及方法與其調用的方法之間的交互中可能存在許多 bug。把依賴項替換為 mock 會隱藏許多實際上可能發(fā)現(xiàn)的 bug。在任何情況下,mock 都不應該是您的第一選擇。如果能夠使用真實的依賴項,應該這么做。mock 是真實類的粗糙的替代品。但是,如果由于某種原因無法用真實的類可靠且自動地進行測試,那么用 mock 進行測試肯定比根本不測試強。

上一頁12下一頁
軟件測試工具 | 聯(lián)系我們 | 投訴建議 | 誠聘英才 | 申請使用列表 | 網(wǎng)站地圖
滬ICP備07036474 2003-2017 版權所有 上海澤眾軟件科技有限公司 Shanghai ZeZhong Software Co.,Ltd