2011-09-18 169 views
8

先決條件:我使用的是最新版本的Play! framework和Java版本(不是Scala)。測試與外部服務的交互

我需要在創建用戶時向消息隊列發佈消息,並且我想測試該行爲。我的問題是使這個易於測試。

的控制方法

在其他框架,我就做了是使用構造器注入到控制器,並通過在我的測試中嘲笑隊列;但是,與Play!控制器是靜態的,這意味着我不能在我的測試中做new MyController(mockedQueue)

我可以使用Google Guice並將一個@Inject註釋放在控制器的靜態字段中,但對我來說這並不好,因爲這意味着我必須將該字段公開爲在測試中被替換,或者我必須在測試中使用容器。我更喜歡使用構造函數注入,但玩!似乎並沒有促進這一點。

模型方法

人們常常說你的邏輯應該是在你的模型,而不是你的控制器。這就說得通了;然而,我們不在Ruby這裏,讓你的實體與外部服務(電子郵件,消息隊列等等)交互...比在動態環境中可測試性要差得多,在這種環境中,你可以用一個模擬實例替換你的靜態調用將。

如果我讓實體呼叫進入隊列,那麼可測試性如何?

當然,如果我進行端到端的集成測試,這兩種情況都是不必要的,但我寧願不需要消息隊列或SMTP服務器啓動我的測試運行。

所以我的問題是:我該如何建模我的Play!控制器和/或模型來促進測試與外部服務的交互?

回答

3

正如我所見,這裏沒有一個乾淨的解決方案。

你可以使用你的依賴的Abstract Factory。這個工廠可以爲它生成的對象設置setter方法。

public class MyController { 
    ... 
    private static ServiceFactory serviceFactory = ServiceFactory.getInstance(); 
    ... 
    public static void action() { 
     ... 
     QueueService queue = serviceFactory.getQueueService(); 
     ... 
    } 

}

您的測試應該是這樣的:

public void testAction() { 
    QueueService mock = ... 
    ... 
    ServiceFactory serviceFactory = ServiceFactory.getInstance(); 
    serviceFactory.setQueueService(mock); 
    ... 
    MyController.action(); 
    verify(mock); 
} 

如果不希望公開工廠的setter方法,你可以創建一個接口和配置實現在你的測試中上課。

另一種選擇是使用ØPowerMock爲嘲諷的靜態方法。我以前用過,在大多數情況下它工作得相當好。只是不要過度使用它,或者你在維護地獄......

最後,因爲你願意在你的應用程序中使用Guice,this可能是一個可行的選擇。

祝你好運!

+0

「ServiceFactory」方法存在的一個*潛在問題是該字段將被初始化;因爲它是一個靜態字段,所以'getInstance'可能會在您有機會替換實例之前被初始化程序調用。只是大聲思考,沒有實際驗證這一點。 –

+0

實際上,在這個例子中,ServiceFactory是一個Singleton。您不會替換ServiceFactory本身,而是替換它生成的QueueService。 –

+0

是的,我明白了;但是,由於該字段是靜態的,因此在您能夠替換從getInstance返回的任何值之前,它將被初始化。您通常無法控制執行靜態初始化程序的時間,因此可能會在測試設置發生之前發生。 –

2

我有點困惑。您可以使用一個模擬調用另一個類

public class Users extends Controller { 
    public static void save(@Valid User user) { 
    //check for user validaton 
    user = user.save(); 
    QueueService queueService = new QueueSerice(); 
    queueService.publishMessage(user); 
    } 
} 

您可以編寫測試用例單位的方法QueueService和寫入功能測試用例的用戶控制器保存方法。

+0

也許我不清楚。你將如何用測試中的模擬實例替換'新的QueueService()'?還是你說你會單元測試'QueueService',但是隻對'User'和'QueueService'之間的交互進行功能測試? –

+0

如果這是要走的路,很好;但在我處理的大多數其他框架中,測試兩者之間的交互並不要求存在實際的實時隊列是完全可行的。 –

2

編輯:擴展的答案以前並不清楚

第一個想法是將參考隊列添加到模型中,當你有一個POJO和訪問構造函數。正如你在下面的評論中提到的那樣,當考慮Hibernate水化實體時,模型方法是有問題的,這會丟棄這個實體。

第二種方法是將此引用添加到控制器的隊列中。現在,這似乎是一個壞主意。除了你提到的公衆成員問題之外,我相信控制器背後的想法是檢索請求的參數,驗證它們是否正確(checkAuthenticity,validation等),發送請求進行處理,然後準備響應。

這裏的「鑰匙」是「發送要處理的請求」。在某些情況下,如果控制器很簡單,我們可以在控制器中完成這項工作,但在其他情況下,使用「服務」(以某種方式調用它)似乎更好,您可以在其中使用給定的數據完成所需的工作。

我使用這種分離爲從測試視圖的點更容易(我),以測試通過Selenium控制器和用於服務做單獨的測試(使用JUnit)。

在您的情況下,此服務將包括對您提到的隊列的引用。

關於如何初始化,這將取決於。你可以創建一個單例,每次通過構造函數初始化它等等。在你特定的場景中,這可能取決於與初始化你的隊列服務有關的工作:如果很難,你可能需要一個Singleton和一個Factory方法來檢索服務並且可以在測試中被模擬)並將其作爲參數傳遞給Service對象的構造函數。

希望此更新能夠更清楚地說明我回答時記住的內容。

+0

雞和雞蛋。那麼工廠從哪裏來?單身? –

+0

雞和雞蛋? :|您可能有一個靜態工廠方法,在單例或實用程序類中或某處。它唯一的目標是返回你需要的Queue對象。我的意思是,你想要做什麼其他方式?恐怕我沒有看到問題,或者我不明白你的反對意見。 –

+0

在任何其他框架中,我會使用構造函數注入(有或沒有容器)。使用單例或工廠方法只能隱藏類真正具有的依賴關係。 –

1

這也許不是你要找的東西,但在我的當前項目,我們已經解決了通過集成測試類型的測試和JMS建立與本地隊列和消息傳遞橋。

在稍微更詳細:

  • 你總是代碼的帖子/到/從本地隊列,本地應用服務器上,即隊列(不是外部系統)讀取消息。
  • 消息網橋在需要時將本地隊列連接到外部服務隊列,例如,在生產中或在手動測試環境中。
  • 集成測試會創建新用戶(或任何您想測試的),然後從本地隊列中讀取預期消息。在這種情況下,消息傳遞橋不活動。

在我的項目中,我們使用SoapUI來執行這些測試,被測系統是一個基於SOAP的集成平臺和了SoapUI具有良好的JMS支持。但它也可能是一個普通的JUnit測試,它執行測試並在之後從本地JMS隊列中讀取。