模擬對象單元測試的關鍵點
如果你是開發團隊的一員,你的代碼通常也依賴於隊友編寫的代碼。但是,如果他們的代碼目前不可用怎麼辦——例如,你的隊友尚未完成編寫?或者,如果你需要的代碼需要其他難以設置的外部依賴項怎麼辦?如果由於你無法控制的其他因素而無法測試代碼怎麼辦?你會只是閒逛,什麼也不做,等待你的團隊完成或一切就緒嗎?當然不會!在本文中,我將說明如何編寫解決此依賴項問題的代碼。理想情況下,需要具備單元測試的一些背景知識,並且 SitePoint 上已經有 Michelle Saver 撰寫的關於單元測試的優秀入門文章。雖然本文不需要,但請查看我關於自動化數據庫測試的其他文章。
模擬對象的案例
你可能已經猜到,模擬對象可以解決我在引言中提到的棘手情況。但是什麼是模擬對象?模擬對像是替代實際對象的實際實現的替代對象。為什麼你想要一個替代對象而不是一個真實的對象?模擬對像用於單元測試,以在測試用例中模擬真實對象的運行行為。通過使用它們,你正在實現的對象的功能將更容易測試。以下是一些使用模擬對像有益的情況:
對象的一個或多個依賴項的實際實現尚未實現。假設你的任務是對數據庫中的一些數據進行一些處理。你可能會調用某種形式的數據訪問對像或數據存儲庫的方法,但是如果數據庫尚未設置怎麼辦?如果沒有任何數據可用(我遇到過太多次的情況)或者查詢數據庫的代碼尚未編寫怎麼辦?模擬數據訪問對象通過返回一些預定義的值來模擬真實的數據訪問對象。這使你免於設置數據庫、查找數據或編寫查詢數據庫的代碼的負擔。
對象的依賴項的實際實現取決於難以模擬的因素。假設你想按天統計 Facebook 帖子點贊和評論的數量。你從哪裡獲取數據?你的 Facebook 開發者帳戶是新的。哎呀,你仍然沒有朋友!你將如何模擬點贊和評論?模擬對像比麻煩你的同事為你點贊或評論一些帖子提供更好的方法。如果你想按天顯示數據,你將如何模擬所有這些操作在一個時間範圍內?按月呢?如果發生不可想像的事情,Facebook 目前宕機了怎麼辦?模擬對象可以假裝是 Facebook 庫並返回你所需的數據。你不必經歷我剛才提到的麻煩就能開始執行你的任務。
模擬對像在實踐中
既然我們知道什麼是模擬對象,讓我們看看一些實際示例。我們將實現前面提到的簡單功能,例如統計 Facebook 帖子的點贊和評論。我們將從以下單元測試開始,以定義我們對對象將如何被調用以及返回值將是什麼樣的期望:
<?php class StatusServiceTest extends PHPUnit_Framework_TestCase { private $statusService; private $fbID = 1; public function setUp() { $this->statusService = new StatusService(); } public function testGetAnalytics() { $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02")); $this->assertEquals(array( "2012-01-01" => array( "comments" => 5, "likes" => 3, ), "2012-01-02" => array( "comments" => 5, "likes" => 3, ), "2012-01-03" => array( "comments" => 5, "likes" => 3, ), "2012-01-04" => array( "comments" => 5, "likes" => 3, ), "2012-01-05" => array( "comments" => 5, "likes" => 3, ) ), $analytics); } }
如果你運行此測試,你將得到一個失敗的結果。這是預期的,因為我們還沒有實現任何內容!現在讓我們編寫服務的實現。當然,第一步是從 Facebook 獲取數據,所以讓我們先這樣做:
<?php class StatuService { private $facebook; public function getAnalytics($id, $from, $to) { $post = $this->facebook->get($id); } }
此測試也會失敗,因為 Facebook 對象為空。你可以通過使用 Facebook 應用程序 ID 等創建實際實例來插入實際實現,但為了什麼?我們都知道這樣做會讓你偏離手頭的任務所帶來的痛苦。我們可以通過注入模擬對象來避免這種情況!使用模擬對象(至少在我們的例子中)的方法是創建一個具有 get() 方法並返回模擬值的類。這應該會愚弄我們的客戶端,使其認為它正在調用實際對象的實現,而實際上它只是被模擬了。
<?php class StatusServiceTest extends PHPUnit_Framework_TestCase { // test here } class MockFacebookLibrary { public function get($id) { return array( // mock return from Facebook here ); } }
現在我們有了模擬類,讓我們實例化一個實例,然後將其註入 StatusService 以便可以使用它。但首先,使用 setter 更新 StatusService 以用於 Facebook 庫:
<?php class StatusService { // other lines of code public function setFacebook($facebook) { $this->facebook = facebook; } }
現在註入模擬 Facebook 庫:
<?php class StatusServiceTest extends PHPUnit_Framework_TestCase { private $statusService; private $fbID = 1; public function setUp() { $this->statusService = new StatusService(); } public function testGetAnalytics() { $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02")); $this->assertEquals(array( "2012-01-01" => array( "comments" => 5, "likes" => 3, ), "2012-01-02" => array( "comments" => 5, "likes" => 3, ), "2012-01-03" => array( "comments" => 5, "likes" => 3, ), "2012-01-04" => array( "comments" => 5, "likes" => 3, ), "2012-01-05" => array( "comments" => 5, "likes" => 3, ) ), $analytics); } }
測試仍然失敗,但至少我們不再收到有關在非對像上調用方法的錯誤。更重要的是,你剛剛解決了滿足此依賴項的需求。你現在可以開始編寫你被分配的任務的業務邏輯並通過測試。
<?php class StatuService { private $facebook; public function getAnalytics($id, $from, $to) { $post = $this->facebook->get($id); } }
更進一步:使用模擬框架
雖然當你剛開始時可以使用手工製作的模擬對象,但後來,正如我自己發現的那樣,隨著你的需求變得越來越複雜,你可能需要使用一個真正的模擬框架。在本文中,我將展示如何使用 PHPUnit 附帶的模擬框架。根據我的經驗,與使用手動編寫的模擬對象相比,使用模擬框架有一些好處:
使用 PHPUnit 的模擬框架
現在讓我們關注使用 PHPUnit 的模擬框架,這些步驟實際上非常直觀,一旦你掌握了它,它就會成為第二天性。在本節中,我們將使用 PHPUnit 的模擬框架為我們的示例情況創建一個模擬對象。但是,在我們這樣做之前,請註釋掉或刪除測試中使用我們手工製作的模擬對象的代碼行。我們需要先失敗,這樣我們才能通過。稍後,我們將注入新的模擬實現。
<?php class StatusServiceTest extends PHPUnit_Framework_TestCase { // test here } class MockFacebookLibrary { public function get($id) { return array( // mock return from Facebook here ); } }
驗證運行 PHPUnit 時測試確實失敗。現在,考慮一下我們如何手動模擬一個對象和我們想要調用的方法。我們做了什麼?
第一步是識別要模擬的對象。在上面的分析示例功能中,我們模擬了 Facebook 庫。我們正在執行與第一步相同的操作。
現在我們已經定義了要模擬的類,我們必須知道要模擬的類中的方法,如果存在任何方法,則指定參數和返回值。我在大多數情況下使用的基本模板大致如下:
讓我們將剛才提到的步驟應用到我們的示例測試中。
<?php class StatusServiceTest extends PHPUnit_Framework_TestCase { private $statusService; private $fbID = 1; public function setUp() { $this->statusService = new StatusService(); } public function testGetAnalytics() { $analytics = $this->statusService->getAnaltyics(1, strtotime("2012-01-01"), strtotime("2012-01-02")); $this->assertEquals(array( "2012-01-01" => array( "comments" => 5, "likes" => 3, ), "2012-01-02" => array( "comments" => 5, "likes" => 3, ), "2012-01-03" => array( "comments" => 5, "likes" => 3, ), "2012-01-04" => array( "comments" => 5, "likes" => 3, ), "2012-01-05" => array( "comments" => 5, "likes" => 3, ) ), $analytics); } }
在我們再次創建模擬 facebook 對像後,再次將其註入我們的服務中:
<?php class StatuService { private $facebook; public function getAnalytics($id, $from, $to) { $post = $this->facebook->get($id); } }
現在,你應該再次通過測試了。恭喜!你已經開始使用模擬對象進行測試了!希望你能夠更有效地編程,最重要的是擺脫將來遇到的障礙依賴項。
圖片來自 Fotolia
(此處應添加關於模擬對象測試的常見問題解答部分,內容與輸入文本中的FAQ部分一致,但需進行輕微的改寫和潤色,以避免重複)
以上是PHP主|模擬對象測試的簡介的詳細內容。更多資訊請關注PHP中文網其他相關文章!