iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
1
Software Development

看到 code 寫成這樣我也是醉了,不如試試重構?系列 第 26

建立 Eloquent

昨天在把 View 轉換成 Blade 時,會遇到一個重大的難題:我們沒有假資料建立方法可以方便地做自動化測試。

今天會來建立 Eloquent ,後面測試如果需要假資料就會比較容易。

建立 entity

首先 Eloquent 的設計是一個資料表,配一個 entity class ,設計如下:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected $table = 'order';

    public $timestamps = false;
}

class Product extends Model
{
    protected $table = 'product';

    public $timestamps = false;

    public function productCategory()
    {
        return $this->belongsTo(ProductCategory::class, 'category', 'id');
    }
}

class ProductCategory extends Model
{
    protected $table = 'product_category';

    public $timestamps = false;

    public function products()
    {
        return $this->hasMany(Product::class, 'category', 'id');
    }
}

接著把產生假資料的方法寫到 database/factories/ProductFactory.php

use Faker\Generator as Faker;

$factory->define(App\Product::class, function (Faker $faker) {
    return [
        // 略
    ];
});

$factory->define(App\ProductCategory::class, function (Faker $faker) {
    return [
        // 略
    ];
});

$factory->define(App\Order::class, function (Faker $faker) {
    return [
        // 略
    ];
});

測試如果一直建假資料的話,資料庫有可能會爆炸。但在爆炸之前,更為麻煩的問題是,因為資料庫會一直保存狀態,所以每次測試的狀態都無法確定,在這狀況下測試的結果就有可能失真。最常見也最困擾的狀況是:有時候測試會過,有時候不會過。

對於這個問題, Laravel 有提供兩種資料庫還原的方法,一個是 DatabaseMigrations ,另一種是 DatabaseTransactions

DatabaseMigrations 原理比較單純,它是執行測試前,會先 rollback ,再重新 migrate 。而 DatabaseTransactions 則是利用執行 beginTransaction() 時做測試,而在測試結束後 rollback() 還原資料庫內容。

很明顯地, DatabaseTransactions 肯定比較快,但它有先天上的限制:必須要同一條 connection 才有辦法取得測試狀態(transaction 狀態)的資料。但因為 Laravel 與原本專案硬幹的 SQL Builder 是使用不同的 connection ,所以只能選擇 DatabaseMigrations

實際撰寫的測試程式如下:

namespace Tests\Unit;

use App\ProductCategory;
use App\Shop\Shop;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;

class ShopTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * @test
     */
    public function shouldGetAllCategoryWhenSeedFactoryCategory()
    {
        /** @var ProductCategory $excepted */
        $excepted = factory(ProductCategory::class)->create();

        $target = new Shop(true);

        $actual = $target->allCategory();

        // FIXME: 因型態不同,先使用 equals
        $this->assertEquals($excepted->id, $actual[1]['id']);
        $this->assertSame($excepted->title, $actual[1]['title']);
    }
}

Browser 的測試範例如下:

use App\ProductCategory;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * @test
     */
    public function shouldSeeNewCategoryWhenCreateNewCategory()
    {
        /** @var ProductCategory $category */
        $category = factory(ProductCategory::class)->create();

        $this->browse(function (Browser $browser) use ($category) {
            $browser->visit('/')
                ->assertSee('產品分類')
                ->assertSee('未分類')
                ->assertSee($category->title);
        });
    }
}

也因為可以產生假資料,很多底層的單元測試都能開始寫了,如 Shop::oneCategory()

/**
 * @test
 */
public function shouldGetOneCategoryWhenSeedFactoryCategory()
{
    /** @var ProductCategory $excepted */
    $excepted = factory(ProductCategory::class)->create();

    $target = new Shop(true);

    $actual = $target->oneCategory($excepted->id);

    // FIXME: 因型態不同,先使用 equals
    $this->assertEquals($excepted->id, $actual['id']);
    $this->assertSame($excepted->title, $actual['title']);
}

今天寫的單元測試將會在明天派得上用場,請大家先看 GitHub PR 吧!

參考資料


上一篇
MVC 架構--View
下一篇
整合 Eloquent
系列文
看到 code 寫成這樣我也是醉了,不如試試重構?30

尚未有邦友留言

立即登入留言