雖然並非每個人都在這樣做,但測試你的應用程序是作為開發人員最基本的部分之一。單元測試是最常見的測試。通過單元測試,你可以檢查一個類是否完全按照你的預期那樣運行。有時,你在你的應用程序中使用的是第三方服務,很難設置好所有內容來進行單元測試。這正是模擬發揮作用的時候。
關鍵要點
模擬對像只不過是創建一個替代對象,它在單元測試中替換真實對象。如果你的應用程序嚴重依賴依賴注入,模擬是可行的方法。
模擬對象可能有幾個原因:
運行單元測試時,你可能正在使用 PHPUnit。 PHPUnit 帶有一些默認的模擬功能,如文檔中所示。你可以在 Jeune Asuncion 撰寫的這篇文章中閱讀更多關於模擬的常規信息以及 PHPUnit 的模擬功能。
在本文中,我們將深入探討由 Pádraic Brady 創建的庫 Mockery。我們將創建一個溫度類,該類將注入當前不存在的天氣服務。
讓我們從設置項目開始。我們從包含以下內容的 composer.json 文件開始。這將確保我們擁有 mockery 和 PHPUnit。
<code>{ "name": "sitepoint/weather", "license": "MIT", "type": "project", "require": { "php": ">=5.3.3" }, "autoload": { "psr-0": { "": "src/" } }, "require-dev": { "phpunit/phpunit": "4.1.*", "mockery/mockery": "0.9.*" } }</code>
我們還創建一個名為 phpunit.xml 的 PHPUnit 配置文件
<phpunit> <testsuite name="SitePoint Weather"> <directory>./tests</directory> </testsuite> <listeners> <listener class="\Mockery\Adapter\Phpunit\TestListener" file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/> </listeners> </phpunit>
定義這個監聽器很重要。如果沒有監聽器,如果方法 once()
、twice()
和 times()
使用不正確,則不會引發錯誤。稍後將詳細介紹。
我還創建了 2 個目錄。 src 目錄用於保存我的類,tests 目錄用於存儲我們的測試。在 src 目錄中,我創建了路徑 SitePointWeather。
我們首先創建 WeatherServiceInterface。我們不存在的天氣服務將實現此接口。在這種情況下,我們只提供一個方法,該方法將為我們提供攝氏溫度。
<code>{ "name": "sitepoint/weather", "license": "MIT", "type": "project", "require": { "php": ">=5.3.3" }, "autoload": { "psr-0": { "": "src/" } }, "require-dev": { "phpunit/phpunit": "4.1.*", "mockery/mockery": "0.9.*" } }</code>
因此,我們有一個服務可以為我們提供攝氏溫度。我想獲得華氏溫度。為此,我創建了一個名為 TemperatureService 的新類。此服務將注入天氣服務。除此之外,我們還定義了一個方法,該方法將攝氏溫度轉換為華氏溫度。
<phpunit> <testsuite name="SitePoint Weather"> <directory>./tests</directory> </testsuite> <listeners> <listener class="\Mockery\Adapter\Phpunit\TestListener" file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/> </listeners> </phpunit>
我們已經準備好設置單元測試了。我們在 tests 目錄中創建一個 TemperatureServiceTest 類。在這個類中,我們創建方法 testGetTempFahrenheit()
,它將測試我們的華氏方法。
此方法中要做的第一步是創建一個新的 TemperatureService 對象。就在我們這樣做的時候,我們的構造函數將請求一個實現了 WeatherServiceInterface 的對象。由於我們還沒有這樣的對象(我們也不想要),我們將使用 Mockery 為我們創建一個模擬對象。讓我們看看完成後的方法是什麼樣的。
namespace SitePoint\Weather; interface WeatherServiceInterface { /** * 返回摄氏温度 * * @return float */ public function getTempCelsius(); }
我們首先創建模擬對象。我們告訴 Mockery 我們想要模擬哪個對象(或接口)。第二步是描述將在此模擬對像上調用的方法。在 shouldReceive()
方法中,我們定義將調用的方法的名稱。
我們定義此方法將調用的次數。我們可以使用 once()
、twice()
和 times(X)
。在這種情況下,我們預計它只會調用一次。如果未調用或調用次數過多,單元測試將失敗。
最後,我們在 andReturn()
方法中定義將返回的值。在這種情況下,我們返回 25。 Mockery 還具有 andReturnNull()
、andReturnSelf()
和 andReturnUndefined()
等返回方法。如果這是你的預期,Mockery 也能夠拋出異常。
我們現在有了模擬對象,可以創建我們的 TemperatureService 對象並像往常一樣進行測試。 25 攝氏度是 77 華氏度,因此我們檢查是否從我們的 getTempFahrenheit()
方法中收到 77。
如果你在你的根目錄中運行 vendor/bin/phpunit tests/
,你將從 PHPUnit 獲得綠燈,表明一切都很完美。
上面的例子相當簡單。沒有參數,只是一個簡單的調用。讓我們讓事情變得複雜一些。
假設我們的天氣服務還有一個方法可以在確切的小時獲取溫度。我們將以下方法添加到我們當前的 WeatherServiceInterface。
namespace SitePoint\Weather; class TemperatureService { /** * @var WeatherServiceInterace $weatherService 保存天气服务 */ private $weatherService; /** * 构造函数。 * * @param WeatherServiceInterface $weatherService */ public function __construct(WeatherServiceInterface $weatherService) { $this->weatherService = $weatherService; } /** * 获取当前华氏温度 * * @return float */ public function getTempFahrenheit() { return ($this->weatherService->getTempCelsius() * 1.8000) + 32; } }
我們想知道,晚上 0:00 到 6:00 之間的平均溫度是多少。為此,我們在 TemperatureService 中創建一個新方法來計算平均溫度。為此,我們從 WeatherService 中檢索 7 個溫度併計算平均值。
<code>{ "name": "sitepoint/weather", "license": "MIT", "type": "project", "require": { "php": ">=5.3.3" }, "autoload": { "psr-0": { "": "src/" } }, "require-dev": { "phpunit/phpunit": "4.1.*", "mockery/mockery": "0.9.*" } }</code>
讓我們看看我們的測試方法。
<phpunit> <testsuite name="SitePoint Weather"> <directory>./tests</directory> </testsuite> <listeners> <listener class="\Mockery\Adapter\Phpunit\TestListener" file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/> </listeners> </phpunit>
我們再次模擬接口,並定義將調用的方法。接下來,我們定義此方法將調用的次數。我們在前面的示例中使用了 once()
,現在我們使用 times(7)
來指示我們期望此方法被調用 7 次。如果該方法沒有被精確調用 7 次,則測試將失敗。如果你沒有在 phpunit.xml 配置文件中定義監聽器,你將不會收到關於此的通知。
接下來,我們定義 with()
方法。在 with
方法中,你可以定義你期望的參數。在這種情況下,我們期望 7 個不同的小時。
最後,我們有 andReturn()
方法。在這種情況下,我們指示了 7 個返回值。如果你定義的返回值較少,則每次都會重複最後一個可用的返回值。
當然,Mockery 可以做更多的事情。有關完整的指南和文檔,我建議你查看 Github 頁面。
如果你對上面項目的代碼感興趣,你可以查看這個 Github 頁面。
使用 PHPUnit,你已經可以模擬對象了。但是,你也可以像上面示例中解釋的那樣使用 Mockery。如果你正在對你的類進行單元測試,並且你不想讓任何其他類影響你的測試,mockery 可以輕鬆地幫助你。如果你真的想進行功能測試,最好看看你是否可以集成真正的測試。你目前是否正在使用 PHPUnit 模擬並考慮切換到 Mockery?你想在後續文章中看到更多更大的 Mockery 示例嗎?請在下面的評論中告訴我。
Mockery 是一個強大而靈活的 PHP 模擬對象框架,用於單元測試。它被設計為 PHPUnit 模擬對像功能的直接替代品。 Mockery 允許開發人員隔離被測代碼並創建測試替身,這些測試替身模擬複雜對象的行為。這在單元測試中至關重要,因為它確保被測代碼不依賴於任何外部因素或狀態。
要安裝 Mockery,你需要擁有 Composer,這是一個 PHP 的依賴項管理器。你可以通過運行命令 composer require --dev mockery/mockery
來安裝 Mockery。安裝後,你可以在測試文件中通過在測試拆卸方法中調用 Mockery::close()
來設置 Mockery,以清理模擬對象。
在 Mockery 中創建模擬對像很簡單。你可以使用 mock()
方法來創建一個模擬對象。例如,$mock = Mockery::mock('MyClass');
將創建一個 MyClass 的模擬對象。
在 Mockery 中,你通過將方法鏈接到模擬對象來定義期望。例如,$mock->shouldReceive('myMethod')->once()->andReturn('mocked value');
此代碼告訴 Mockery 預期“myMethod”將被調用一次,並且應該返回“mocked value”。
在 Mockery 中,模擬是我們可以在其上設置期望的對象,而存根是預先編程了響應的模擬對象。當響應是唯一重要的事情時,通常使用存根,而當測試交互本身時,則使用模擬。
不建議直接測試私有方法,因為它違反了封裝原則。但是,如果需要,你可以使用 Mockery 中的 shouldAllowMockingProtectedMethods()
方法來允許模擬受保護和私有方法。
如果要模擬的類具有帶參數的構造函數,可以將它們作為數組傳遞給 mock()
方法。例如,$mock = Mockery::mock('MyClass', [$arg1, $arg2]);
將將 $arg1 和 $arg2 傳遞給 MyClass 的構造函數。
Mockery 提供了一種使用 alias:
前綴模擬靜態方法的方法。例如,$mock = Mockery::mock('alias:MyClass');
將創建一個可以用來對 MyClass 的靜態方法設置期望的模擬對象。
你可以在測試拆卸方法中使用 Mockery::close()
方法來驗證是否已滿足所有期望。如果任何期望未滿足,Mockery 將引發異常。
你可以使用 andThrow()
方法設置模擬對像以拋出異常。例如,$mock->shouldReceive('myMethod')->andThrow(new Exception);
將在調用“myMethod”時拋出異常。
以上是用嘲笑嘲笑您的測試依賴性的詳細內容。更多資訊請關注PHP中文網其他相關文章!