首頁 > 後端開發 > php教程 > PHP主|模擬對象測試的簡介

PHP主|模擬對象測試的簡介

William Shakespeare
發布: 2025-02-26 11:27:11
原創
727 人瀏覽過

PHP Master | An Introduction to Mock Object Testing

模擬對象單元測試的關鍵點

  • 模擬對像是單元測試中用於替代真實對象的替身,模擬真實對象的運行行為。當對象的依賴項尚未實現或依賴難以模擬的因素時,模擬對象非常有用。
  • 在測試中,創建模擬對象並將其註入系統以滿足依賴關係,允許開發人員開始編寫業務邏輯。
  • 雖然最初可以使用手工製作的模擬對象,但隨著測試需求變得越來越複雜,可能需要一個真正的模擬框架。模擬框架可以節省時間並產生更簡潔的代碼。
  • PHPUnit 的模擬框架就是這樣一種工具,可用於創建用於測試的模擬對象。此過程涉及識別要模擬的對象、定義要模擬的方法以及指定參數和返回值。
  • 模擬對象主要用於單元測試以隔離被測系統。它們模擬複雜真實對象的運行行為,對於測試應用程序的特定模塊如何與其他模塊交互非常有用。

如果你是開發團隊的一員,你的代碼通常也依賴於隊友編寫的代碼。但是,如果他們的代碼目前不可用怎麼辦——例如,你的隊友尚未完成編寫?或者,如果你需要的代碼需要其他難以設置的外部依賴項怎麼辦?如果由於你無法控制的其他因素而無法測試代碼怎麼辦?你會只是閒逛,什麼也不做,等待你的團隊完成或一切就緒嗎?當然不會!在本文中,我將說明如何編寫解決此依賴項問題的代碼。理想情況下,需要具備單元測試的一些背景知識,並且 SitePoint 上已經有 Michelle Saver 撰寫的關於單元測試的優秀入門文章。雖然本文不需要,但請查看我關於自動化數據庫測試的其他文章。

模擬對象的案例

你可能已經猜到,模擬對象可以解決我在引言中提到的棘手情況。但是什麼是模擬對象?模擬對像是替代實際對象的實際實現的替代對象。為什麼你想要一個替代對象而不是一個真實的對象?模擬對像用於單元測試,以在測試用例中模擬真實對象的運行行為。通過使用它們,你正在實現的對象的功能將更容易測試。以下是一些使用模擬對像有益的情況:

  1. 對象的一個或多個依賴項的實際實現尚未實現。假設你的任務是對數據庫中的一些數據進行一些處理。你可能會調用某種形式的數據訪問對像或數據存儲庫的方法,但是如果數據庫尚未設置怎麼辦?如果沒有任何數據可用(我遇到過太多次的情況)或者查詢數據庫的代碼尚未編寫怎麼辦?模擬數據訪問對象通過返回一些預定義的值來模擬真實的數據訪問對象。這使你免於設置數據庫、查找數據或編寫查詢數據庫的代碼的負擔。

  2. 對象的依賴項的實際實現取決於難以模擬的因素。假設你想按天統計 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 附帶的模擬框架。根據我的經驗,與使用手動編寫的模擬對象相比,使用模擬框架有一些好處:

  • 你可以偷懶。我發現這在處理具有許多抽象方法的抽像類時尤其如此。你可以只模擬抽像類或接口的某些方法。如果你手動執行此操作,則必須手動實現所有這些方法。它可以節省一些打字工作,並且還預先打包了類型提示;你只需要設置你需要的部分,而無需為每個測試用例維護一個新類。
  • 你可以編寫更簡潔的代碼。可讀性是這裡的關鍵。基於框架的模擬使你的測試更容易理解,因為你的模擬寫在測試中。你不需要向下滾動或在文件之間切換以查看在其他地方編寫的自寫模擬。如果你需要多次調用模擬對象並獲得不同的結果怎麼辦?使用基於框架的模擬,執行此操作所需的 if-else 樣板代碼已經被很好地封裝。因此,它更容易理解。

使用 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 時測試確實失敗。現在,考慮一下我們如何手動模擬一個對象和我們想要調用的方法。我們做了什麼?

  1. 第一步是識別要模擬的對象。在上面的分析示例功能中,我們模擬了 Facebook 庫。我們正在執行與第一步相同的操作。

  2. 現在我們已經定義了要模擬的類,我們必須知道要模擬的類中的方法,如果存在任何方法,則指定參數和返回值。我在大多數情況下使用的基本模板大致如下:

    1. 指定方法將被調用的次數(必需)。
    2. 指定方法名稱(必需)。
    3. 指定方法期望的參數(可選)。
    4. 指定返回值(可選)

讓我們將剛才提到的步驟應用到我們的示例測試中。

<?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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板