使用樁件 (Stub) 解決 Laravel 單元測試中的依賴

本文是我在實踐後的一點總結,不免有不妥之處。若有幸得大神路過,還望不吝賜教,小弟在此謝過了!php

很早就知道有單元測試的概念,也曾嘗試過,可是一直對單元測試的概念和方法,比較模糊。在聽了 @vimac 大神的講堂 PHP單元測試與測試驅動開發 後,慢慢地對單元測試和 PHPUnit 的認識清晰了起來,也開始慢慢地去實踐單元測試。html

Laravel 中的依賴

咱們都知道,Laravel 使用了 IoC,各個模塊之間也所以解耦了。而正是由於這一點,咱們在 Laravel 中編寫單元測試的時候,變得更加輕鬆了。數據庫

舉個栗子

考慮如下場景。咱們在開發中,可能會在控制器和模型之間加一個 Repository 來處理數據。那麼咱們的 Controller 就會依賴 Respository。利用 Laravel 的 IoC,咱們能夠定義一個 Service Provider 來集中將 Respository 注入到容器中。vim

假設咱們如今有這樣一個 Repository,裏面記錄了商品的信息,咱們想要在 Controller 中獲取某件商品信息,而後執行一些業務邏輯。segmentfault

Class GoodRepository
{
    public function getGoodById($goodId)
    {
        // TODO: Get good by its id.
    }
}

class GoodController extends Controller
{
    public function show($id, GoodRepository $goodRepository)
    {
        // TODO: Do something with good info from that repository.
    }
}

// In route/api.php
Route::get('/api/good/{id}', 'GoodController@show');

// Create a RepositoriesServiceProvider in Provider/RepositoriesServiceProvider.php。
// And inject the GoodRepository into Container.
class RepositoriesServiceProvider extends ServiceProvider
{
    public function boot()
    {
    
    }
    
    public function register()
    {
        $this->app->singleton(GoodRepository::class);
    }
}

好了,咱們能夠發現,GoodController 是依賴 GoodRepository 的,而 GoodRepository 是依賴數據庫中的數據的。但是咱們在作單元測試的時候,但願儘量少的產生依賴。因此,咱們應該但願可以掌控 GoodRepository 所返回的數據。api

在 Laravel,提供了 $this->get('/path/to/route'); 的方法來對 HTTP 請求進行測試。這個測試必然會涉及到剛纔所提到的那些依賴,如何解決這個依賴的問題,咱們能夠請出咱們的主角————樁件。app

樁件

將對象替換爲(可選地)返回配置好的返回值的測試替身的實踐方法稱爲上樁(stubbing)。ide

這是 PHPUnit 文檔上 的解釋。那個人理解呢,所謂的樁件,就是模擬一個依賴的類的行爲,使得這個行爲所作的事情在咱們本身的掌控之中。好比上面的這種狀況,咱們但願模擬 GoodRepositorygetGoodById 方法返回與真正的返回結構相同的值,而不須要依賴外部數據源。單元測試

在 Laravel 中使用樁件

咱們經過 Service Provider 註冊了 GoodRepository 單例,那麼按照這個思路,咱們在寫單元測試的時候,就能夠將咱們定義的樁件,註冊爲 GoodRepository 單例。測試

class GoodControllerTest extends TestCase
{
    public function testShow()
    {
        $data = []; // The data returns from GoodRepository::getGoodById.
        
        $stub = $this->createMock(GoodRepository::class);
        
        $stub->method('getGoodById')->will($this->returnValue($data));
        
        $this->app->singleton(GoodRepository::class, function () use ($stub) {
            return $stub;
        });
        
        $response = $this->get('/api/good/1');
        
        // Some assertions.
    }
}

咱們經過在這裏將樁件 $stub 用單例模式註冊給了 Container,在調用 $this->get('/api/good/1'); 時本來在 Controller 中的 GoodRepository 依賴就變成了咱們自定義的樁件 $stub。咱們將 $data 定義爲和返回值相同的結構,註冊到樁件中。這樣,全部的數據都在咱們可控的範圍了。

若是咱們在這裏不使用樁件,而是直接依賴外部(數據庫)中的數據,那麼若是 id 爲 1 的數據被刪除了,咱們是否是就要改爲 2 了呢?咱們是否是就要從新計算數據了匹配斷言了呢?這樣的測試,可靠性便大大下降。

後記

任何一個可靠的系統,單元測試都是必不可少的。慶幸的是,PHPUnit 幫咱們提供了好用的單元測試。本文所講的,也只是 PHPUnit 的九牛一毛。而我本身也在慢慢摸索慢慢實踐中。與君共勉。

最後仍是推薦去聽一下 @vimac 的講堂 PHP單元測試與測試驅動開發,受益不淺。

相關文章
相關標籤/搜索