iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
0
Software Development

如何一步步實踐TDD (測試驅動開發)系列 第 23

TDD 實戰 D9:Laravel 關聯式資料庫

每則貼文都會有一個貼文者,因此我們要來修改 posts 資料表 與 Post Model、以及新增貼文的路由。

  • 關於 如何使用範例程式碼,請參考 TDD 實戰 D1
    • 本篇版本包含:5c

需登入的貼文路由

新增貼文的路由應該要在登入後才能造訪,知道是誰進行貼文。

紅燈:testRejectIfNotAuth

UI 的測試同樣是要使用 Dusk 來進行,測試如果在未登入的狀態,會跳轉到登入的頁面:

// tests/Browser/PostFormTest.php

class PostFormTest extends DuskTestCase
{
    use DatabaseMigrations;
    
    public function testRejectIfNotAuth()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/post/form')
                    ->assertPathIs('/login');
        });
    }
}

綠燈

需要的登入驗證,Laravel 已經幫我們寫好了,只要在需要的路由定義後加上 middleware->('auth')

各種 Middle Ware (中介層) 的定義在 app/Http/Kernel.php,而其中的 authapp/Http/Middleware/Authenticate.php

Route::get('/post/form', function () {
    return view('post_form');
})->middleware('auth');

重構

其實不算重構,只是順便讓我們的表單美觀一點,套用 Laravel 原本就寫好的模板。

<!-- resources/views/post_form.blade.php -->

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Post Something</div>

                <div class="card-body">

                    <form method="post" action="/post">
                        @csrf
                        <input type="text" size="50" name="post_text">
                        <input type="submit" value="送出貼文">
                    </form>

                </div>

            </div>
        </div>
    </div>
</div>
@endsection

新增貼文

紅燈:testPostByFormIfAuth

// tests/Browser/PostFormTest.php

public function testPostByFormIfAuth()
{
    $user = factory(User::class)->create([
        'email' => 'taylor@laravel.com',
    ]);

    $this->browse(function ($browser) use ($user) {
        $browser->loginAs($user)
                ->visit('/post/form')
                ->type('post_text', "a testing post")
                ->press('送出貼文');
    });

    $this->assertDatabaseHas('posts', [
        'post_text' => "a testing post"
    ]);
}

這邊看起來做了比較多事情,來整理一下:

  1. 資料表中,創建一筆假資料。
  2. 利用 Dusk 的 loginAs() 直接進入登入狀態。
  3. 到表單中輸入資料、貼文
  4. 確認資料表的確新增了剛剛的貼文內容

綠燈

這裡是原本在 TDD 實戰 D6:Laravel POST 方法 與 表單 中我們就已經完成的,只是之前我們還沒學會 Dusk。

讓我們繼續今天的主軸:修改資料表。

Post 關聯至 User

紅燈:testPostByFormIfAuth

即使是需要更動到資料表,我們依舊可以從編寫測試開始。

來更改剛剛的測試,改成要新增的貼文中,需要有 user_id 欄位。

並且我們加入兩個測試的使用者,新增的貼文必須是關聯到正確的 user_id

    public function testPostByFormIfAuth()
    {
        factory(User::class)->create();
        
        $second_user = factory(User::class)->create([
            'email' => 'taylor@laravel.com',
        ]);
        $this->assertSame(2, $second_user->id);

        $this->browse(function ($browser) use ($second_user) {
            $browser->loginAs($second_user)
                    ->visit('/post/form')
                    ->type('post_text', "a testing post")
                    ->press('送出貼文');
        });

        $this->assertDatabaseHas('posts', [
            'post_text' => "a testing post",
            'user_id' => $second_user->id
        ]);
    }

綠燈

測試那邊只修改了一點點,產品程式這邊要修改的就不少了。

我們從資料表的 Migration 開始改起:

$ php artisan make:migration add_user_to_posts_table --table=posts
// database/migrations/5_add_user_to_posts_table.php

class AddUserToPostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->unsignedBigInteger('user_id')->default(0);
            $table->foreign('user_id')->references('id')->on('users');
        });
    }
}

我們新增了一個 user_id 欄位,由於 posts 資料表本來就存在,因此需要加上 default() 特性,讓新舊資料不會產生衝突,每個欄位在 Laravel 中預設都是 Non nullable 的,在 migrate 時會把舊資料的新欄位都加上預設值。

foreign() 設定參照到哪個資料表,也就是將 user_id 參照到 users 表的 id 欄位。

Post Model

設定 Post Model 會參照到 User Model [1]。

// app/Post.php

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(App\User);
    }
}

User Model

User Model 則是設定擁有這些 Post Model。

class User extends Authenticatable
{
    ...
    
    public function posts()
    {
        return $this->hasMany(App\Post);
    }
}

PostController

最後是處理新增貼文的控制器,加上要儲存 user_id。

// app/Http/Controllers/PostController.php

use Illuminate\Support\Facades\Auth;

class PostController extends Controller
{
    public function insertPost(Request $request)
    {
        $post = new Post;
        $post->post_text = $request->input('post_text');
        $post->user_id = Auth::id(); // user_id
        $post->save();
    }
}

( $ git checkout 5c )

這裡透過 Auth::id() 函式來幫忙,可以直接取得目前已登入的使用者 id。

完成!

我們加入了 Post 與 User 之間的關聯,以及透過 Dusk 來加入了新增貼文的測試。

$ php artisan dusk 

測試順利執行完畢,讚!

然而就在我們準備開心的收工時,赫然發現!

$ ./vendor/bin/phpunit

竟然跳出了一堆錯誤訊息!

不用慌張,這是因為我們剛剛更動的程式碼,影響到了之前寫好的測試。

再一次感受到 TDD 的好處,當我們破壞到之前的程式時,測試們會馬上告訴我們 (regression test),讓我們明天就來著手改寫。


附註

  1. Migration 的用途是更新資料表,而 Post 與 User Model 則是 Laravel Eloquent 用來簡化操作資料表的物件,兩者需要各自編寫。

上一篇
TDD 實戰 D8:Laravel UI 測試 (Dusk)
下一篇
TDD 實戰 D10:Laravel (Regression Test)
系列文
如何一步步實踐TDD (測試驅動開發)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言