2009-06-14 50 views
2

下面的測試類驗證一個簡單的HttpService從給定的URL獲取內容。所顯示的兩個實現都使測試通過,但顯然這是錯誤的,因爲它構造的URL的參數不正確。Groovy:驗證存根URL的構造

爲了避免這種情況並正確指定我想要的行爲,我想驗證在測試用例的使用塊中,我構造了一個(且只有一個)URL類實例,並且url參數到構造函數是正確的。 A Groovy enhancement似乎將讓我添加語句

mockURLContext.demand.URL { assertEquals "http://www.foo.com", url } 

,但我可以做什麼,而不在於Groovy的增強?

更新:在標題中用「存根」代替「模擬」,因爲我只想檢查狀態,而不一定是相互作用的細節。 Groovy有一個我沒有用過的StubFor機制,所以我將保留原來的代碼,但我認爲你可以在整個過程中用StubFor代替MockFor。

import grails.test.* 
import groovy.mock.interceptor.MockFor 

class HttpServiceTests extends GrailsUnitTestCase { 
    void testGetsContentForURL() { 
     def content = [text : "<html><body>Hello, world</body></html>"] 

     def mockURLContext = new MockFor(URL.class) 
     mockURLContext.demand.getContent { content } 
     mockURLContext.use { 
      def httpService = new HttpService() 
      assertEquals content.text, httpService.getContentFor("http://www.foo.com") 
     } 
    } 
}  

// This is the intended implementation. 
class HttpService { 
    def getContentFor(url) { 
     new URL(url).content.text 
    } 
} 

// This intentionally wrong implementation also passes the test! 
class HttpService { 
    def getContentFor(url) { 
     new URL("http://www.wrongurl.com").content.text 
    } 
} 

回答

3

嘲諷URL會給你帶來什麼?這使得測試很難寫。你將無法對模擬對象給出的關於URL類的API設計的反饋作出反應,因爲它不在你的控制之下。如果您沒有精確地僞造URL的行爲以及它公開的HTTP協議的內容,則測試將不可靠。

你想要測試你的「HttpService」對象是否真的從給定的URL加載數據,正確處理不同的內容類型編碼,適當地處理不同類別的HTTP狀態代碼等。當我需要測試這種類型的對象 - 僅僅包裝一些底層技術基礎架構時 - 我寫了一個真正的集成測試來驗證對象是否確實正確地使用底層技術。

對於HTTP,我編寫一個測試,創建一個HTTP服務器,將一個servlet插入服務器,該服務器將返回一些罐頭數據,將該servlet的URL傳遞給該對象以使其加載數據,檢查加載的結果與用於初始化servlet的數據相同,並在夾具拆卸時停止服務器。我使用Jetty或與JDK 6捆綁的簡單HTTP服務器。

我只使用模擬對象來測試與我已集成測試的對象的接口對話的對象的行爲。

0

你到底是什麼期待有失敗?這是不明顯的,你試圖用該代碼進行測試。通過Mocking URL.getContent您告訴Groovy總是返回變量contentURL.getContent() is invoked。您是否希望根據URL字符串將條件返回值URL.getContent()?如果是這樣的話,下面的完成是:

import grails.test.* 
import groovy.mock.interceptor.MockFor 

class HttpServiceTests extends GrailsUnitTestCase { 
    def connectionUrl 

    void testGetsContentForURL() { 
     // put the desired "correct" URL as the key in the next line 
     def content = ["http://www.foo.com" : "<html><body>Hello, world</body></html>"] 

     def mockURLContext = new MockFor(URL.class) 
     mockURLContext.demand.getContent { [text : content[this.connectionUrl]] } 
     mockURLContext.use { 
      def httpService = new HttpService() 
      this.connectionUrl = "http://www.wrongurl.com" 
      assertEquals content.text, httpService.getContentFor(this.connectionUrl) 
     } 
    } 
} 
+0

嗯。我遵循他的問題,但不是你的回答。他試圖模擬URL類,而不是他的HTTPService。 HTTPService是被測試的代碼。 – cwash 2009-06-23 21:07:46

+0

我刪除了部分評論。我問這個問題的原因是因爲我無法告訴原始海報試圖用這裏提供的代碼和描述來完成什麼。我提供的代碼將通過預期會通過的情況,並使預計失敗的情況失敗。它真的證明了什麼? – laz 2009-06-23 21:28:05

+0

這是一個簡單的例子,但是展示了基於狀態測試的一些微妙之處。他試圖驗證的是他的模擬設置正確。在大多數嘲諷框架中,這是通過設定預期參數集的預期值來完成的。在這裏,他試圖將參數的期望值設置給構造函數,但由於他提到的增強而無法實現。 – cwash 2009-06-23 21:39:54

0

這很難模擬出被聲明爲final ...您的問題,因爲你通過增強參考JDK類,是沒有辦法創造除調用構造函數之外的URL。

我嘗試從我的代碼的其餘部分分開創建這些類型的對象;我會創建一個工廠來分隔創建網址。這應該很簡單,不足以進行測試。其他人則採用典型的包裝/裝飾方法。或者,您可以將adapter pattern轉換爲您編寫的域對象。

下面是一個類似的答案爲出奇類似的問題:Mocking a URL in Java

我認爲這證明的東西,有很多人做更多的測試後得知:代碼我們寫使事情更容易測試是爲了隔離我們想要測試的東西,我們可以肯定地說已經在其他地方測試過了。這是我們爲了進行單元測試而必須做出的基本假設。它也可以提供一個體面的例子,說明爲什麼好的單元測試不一定能達到100%的代碼覆蓋率。他們也必須經濟。

希望這會有所幫助。

2

把我的「小編程」和「單元測試100%」的帽子,你可能認爲這是一個單一的方法,做了太多事情。您可以重構HttpService的到:

class HttpService { 
    def newURLFrom(urlString) { 
    new URL(urlString) 
    } 
    def getContentText(url) { 
    url.content.text 
    } 
    def getContentFor(urlString) { 
    getContentText(newURLFrom(urlString)) 
    } 
} 

這會給你一個用於測試的幾個選項,以及拆出從屬性操縱工廠方面。測試選項是有點世俗,那麼:

class HttpServiceTests extends GroovyTestCase { 

    def urlString = "http://stackoverflow.com" 
    def fauxHtml = "<html><body>Hello, world</body></html>"; 
    def fauxURL = [content : [text : fauxHtml]] 

    void testMakesURLs() { 
    assertEquals(urlString, 
       new HTTPService().newURLFrom(urlString).toExternalForm()) 
    } 
    void testCanDeriveContentText() { 
    assertEquals(fauxHtml, new HTTPService().getContentText(fauxURL)); 
    } 
    // Going a bit overboard to test the line combining the two methods 
    void testGetsContentForURL() { 
    def service = new HTTPService() 
    def emc = new ExpandoMetaClass(service.class, false) 
    emc.newURLFrom = { input -> assertEquals(urlString, input); return fauxURL } 
    emc.initialize() 
    service.metaClass = emc 
    assertEquals(fauxHtml, service.getContentFor(urlString)) 
    } 
} 

我認爲,這使所有你想要的斷言,但在最後一種情況下犧牲可讀性測試的成本。

我會同意納特關於這一點,使得集成測試更有意義。 (您正在某種程度上與Java的URL庫集成。)但假設這個例子簡化了一些複雜的邏輯,您可以使用元類來覆蓋實例類,從而有效地部分嘲諷實例。