iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
DevOps

自動化測試大作戰系列 第 24

情境題—文章瀏覽與評論(一)

  • 分享至 

  • xImage
  •  

Medium 清新閱讀版連結

今天已經是第鐵人賽第24天了!

在前面的23天,與大家分享了許多撰寫 PHPUnit 測試程式碼所需的知識,之後的文章就讓我們來來模擬一些情境題,並在這些情境題底下,實際去設計測試案例函數吧!

作為第一個情境題,我們就選「網站文章」來當作第一個挑戰吧!

這邊我們假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分。

使用案例

  1. 使用者可瀏覽文章清單。
  2. 使用者點選單一文章連結,進入單一文章頁,可觀看到該文章下之評論。
  3. 使用者新增評論後,可看到新的評論立即更新至畫面上。

依據以上的使用案例,我們可規畫出以下 API:

API 規畫

  1. 取得文章清單 GET /api/articles
  2. 取得單一文章內容 GET /api/articles/{id}
  3. 取得單一文章評論清單與內容 GET /api/articles/{id}/comments
  4. 使用者針對指定文章新增評論 POST /api/articles/comments

接著就來實作 API 吧!

API 實作

  • app/Http/Controllers/Api/ApiController.php

    <?php
    namespace App\Http\Controllers\Api;
    
    use App\Http\Controllers\Controller;
    use function response;
    
    class ApiController extends Controller
    {
        public function respondJson($data)
        {
            return response()->json([
                'data' => $data,
            ]);
        }
    
        public function respondNotFound()
        {
            return response()->json('', 404);
        }
    }
    
  • app/Http/Controllers/Api/ArticleController.php

    <?php
    
    namespace App\Http\Controllers\Api;
    
    use App\Models\Article;
    use App\Models\Comment;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    
    class ArticleController extends ApiController
    {
        public function index(Request $request)
        {
            $articles = Article::all();
    
            return $this->respondJson($articles);
        }
    
        public function show($id)
        {
            $article = Article::find($id);
    
            if (empty($article)) {
                return $this->respondNotFound();
            }
    
            return $this->respondJson($article);
        }
    
        public function comments(Request $request, $id)
        {
            $article = Article::find($id);
    
            if (empty($article)) {
                return $this->respondNotFound();
            }
    
            $comments = $article->comments;
    
            return $this->respondJson($comments);
        }
    
        public function storeComment(Request $request, $id)
        {
            $user = Auth::user();
    
            $article = Article::find($id);
    
            if (empty($article)) {
                return $this->respondNotFound();
            }
    
            $data = [
                'article_id' => $article->id,
                'content' => $request->input('comment'),
                'user_id' => $user->id,
            ];
    
            $comment = new Comment($data);
            $comment->save();
    
            return $this->respondJson($comment);
        }
    }
    
  • app/Models/Article.php

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Database\Eloquent\Model;
    
    class Article extends Model
    {
        use HasFactory;
    
        protected $fillable = [
            'content',
        ];
    
        public function comments()
        {
            return $this->hasMany(Comment::class);
        }
    }
    
  • app/Models/Comment.php

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Database\Eloquent\Model;
    
    class Comment extends Model
    {
        use HasFactory;
    
        protected $fillable = [
            'content',
            'user_id',
            'article_id',
        ];
    
        public function article()
        {
            return $this->belongsTo(Article::class);
        }
    }
    
  • app/Models/User.php

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Factories\HasFactory;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Illuminate\Notifications\Notifiable;
    use Laravel\Sanctum\HasApiTokens;
    
    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
    
        /**
         * The attributes that are mass assignable.
         *
         * @var array<int, string>
         */
        protected $fillable = [
            'name',
            'email',
            'password',
        ];
    
        /**
         * The attributes that should be hidden for serialization.
         *
         * @var array<int, string>
         */
        protected $hidden = [
            'password',
            'remember_token',
        ];
    
        /**
         * The attributes that should be cast.
         *
         * @var array<string, string>
         */
        protected $casts = [
            'email_verified_at' => 'datetime',
        ];
    }
    
  • routes/api.php

    <?php
    
    use Illuminate\Support\Facades\Route;
    use App\Http\Controllers\Api\ArticleController;
    
    Route::prefix('articles')->group(function() {
        Route::get('', [ArticleController::class, 'index'])
            ->name('article.list');
        Route::get('/{id}', [ArticleController::class, 'show'])
            ->where('id', '[0-9]+')
            ->name('article.one');
        Route::get('/{id}/comments', [ArticleController::class, 'comments'])
            ->where('id', '[0-9]+')
            ->name('article.one.comments');
        Route::post('/{id}/comments', [ArticleController::class, 'storeComment'])
            ->middleware('auth:api')
            ->where('id', '[0-9]+')
            ->name('article.one.comments.store');
    });
    
  • database/migrations/2014_10_12_000000_create_users_table.php

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('users', function (Blueprint $table) {
                $table->id();
                $table->string('name');
                $table->string('email')->unique();
                $table->timestamp('email_verified_at')->nullable();
                $table->string('password');
                $table->rememberToken();
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('users');
        }
    };
    
  • database/migrations/2022_10_02_174939_create_articles_table.php

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('articles', function (Blueprint $table) {
                $table->id();
                $table->text('content');
                $table->integer('page_views');
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('articles');
        }
    };
    
  • database/migrations/2022_10_08_172525_create_comments_table.php

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('comments', function (Blueprint $table) {
                $table->id();
                $table->integer('user_id');
                $table->integer('article_id');
                $table->text('content');
                $table->timestamps();
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('comments');
        }
    };
    

前置準備

這邊我們要準備的是各 Model 的 Factory 類別,以及批次產生測試資料的 Seeders:

  1. User Factory

    <?php
    
    namespace Database\Factories;
    
    use Illuminate\Support\Str;
    use Illuminate\Database\Eloquent\Factories\Factory;
    
    class UserFactory extends Factory
    {
        /**
        * Define the model's default state.
        *
        * @return array
        */
        public function definition(): array
        {
            return [
                'name' => $this->faker->name,
                'email' => $this->faker->safeEmail,
                'email_verified_at' => $this->faker->dateTime(),
                'password' => bcrypt($this->faker->password),
                'remember_token' => Str::random(10)
            ];
        }
    }
    
  2. Article Factory

    <?php
    
    namespace Database\Factories;
    
    use Illuminate\Database\Eloquent\Factories\Factory;
    
    /**
     * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Article>
     */
    class ArticleFactory extends Factory
    {
        /**
         * Define the model's default state.
         *
         * @return array<string, mixed>
         */
        public function definition()
        {
            return [
                'content' => $this->faker->text,
                'page_views' => 0,
            ];
        }
    }
    
  3. Comment Factory

    <?php
    
    namespace Database\Factories;
    
    use Illuminate\Database\Eloquent\Factories\Factory;
    
    /**
     * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Comment>
     */
    class CommentFactory extends Factory
    {
        /**
         * Define the model's default state.
         *
         * @return array<string, mixed>
         */
        public function definition()
        {
            return [
                'content' => $this->faker->text,
            ];
        }
    }
    
  4. User Seeder

    <?php
    
    namespace Database\Seeders;
    
    use App\Models\User;
    use App\Models\UserLog;
    use Illuminate\Database\Seeder;
    
    class UserSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run()
        {
            User::factory()
                ->count(10)
                ->create();
        }
    }
    
  5. Article Seeder

    <?php
    
    namespace Database\Seeders;
    
    use App\Models\Article;
    use App\Models\Comment;
    use App\Models\User;
    use Illuminate\Database\Seeder;
    
    class ArticleSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run()
        {
            $articles = Article::factory()
                ->count(10)
                ->create();
    
            $users = User::all();
    
            foreach ($articles as $article) {
                $commentCount = random_int(1, 5);
    
                for ($i = 0; $i < $commentCount; $i ++) {
                    $user = $users->random();
    
                    Comment::factory()
                        ->create([
                            'user_id' => $user->id,
                            'article_id' => $article->id,
                        ]);
                }
            }
        }
    }
    

到這邊為止,我們已經把測試目標準備好了,明天我們就來針對各使用案例來寫測試吧!


上一篇
Coverage:覆蓋率報告
下一篇
情境題—文章瀏覽與評論(二)
系列文
自動化測試大作戰31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言