在前面的篇幅中,與大家分享了許多撰寫 PHPUnit 測試程式碼所需的知識,之後的文章就讓我們來來模擬一些情境題,並在這些情境題底下,實際去設計測試案例函數吧!
作為第一個情境題,我們就選「網站文章」來當作第一個挑戰吧!
這邊我們假設網站是採前後端分離的設計,因此我們就專注在測試 API 的部分。
依據以上的使用案例,我們可規畫出以下 API:
GET /api/articles
GET /api/articles/{id}
GET /api/articles/{id}/comments
POST /api/articles/comments
接著就來實作 API 吧!
<?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);
}}
<?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);
}}
<?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);
}}
<?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);
}}
<?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',
];}
<?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');
});
<?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');
}};
<?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_vdatabase/migrations/2022_10_02_174939_create_articles_table.phpiews');
$table->timestamps();
}); } /** * Reverse the migrations. * * @return void */
public function down() {
Schema::dropIfExists('articles');
}};
<?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:
<?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)
]; }}
<?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,
]; }}
<?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,
]; }}
<?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();
}}
<?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,
]); } } }}
到這邊為止,我們已經把測試目標準備好了,下一篇我們就來針對各使用案例來寫測試吧!
如果您喜歡這篇文章,歡迎加入追蹤以接收新文章通知 😄