はじめに
前回の記事「テストコードって何?」では、テストコードの基礎知識についてまとめました。
今回はいよいよ実際にテストコードを書いてみます。
まず簡単なテストコードで仕組みを体験したうえで、自分のアプリ(子ども予防接種スケジューラー)の機能に合わせたテストコードを書いていきたいと思います。
1. テストコードの書き方(準備 → 実行 → 確認)
テストコードの基本的な構造は「準備 → 実行 → 確認」の3ステップです。
| ステップ | 英語表記 | 内容 |
|---|---|---|
| 準備 | Arrange | テストに必要なデータを用意する |
| 実行 | Act | テストしたい処理を実行する |
| 確認 | Assert | 結果が期待通りかチェックする |
2. まず簡単なテストコードで仕組みを体験する
テストファイルの作成
コンテナ内で以下を実行してテストファイルを作成しました。
php artisan make:test SampleTest
tests/Feature/SampleTest.php が作成されます。
テストコードを書く
<?php
namespace Tests\Feature;
use Tests\TestCase;
class SampleTest extends TestCase
{
// テスト① 足し算が正しく計算できるか
public function test_足し算が正しく計算できる()
{
$result = 1 + 1;
$this->assertEquals(2, $result);
}
// テスト② 文字列が正しいか
public function test_文字列が正しい()
{
$name = 'テストちゃん';
$this->assertEquals('テストちゃん', $name);
}
// テスト③ 意図的に失敗させてみる
public function test_これは失敗する()
{
$result = 1 + 1;
$this->assertEquals(3, $result); // 2なのに3と期待→失敗!
}
}
テストを実行する
php artisan test --filter SampleTest
実行結果
FAIL Tests\Feature\SampleTest
✓ 足し算が正しく計算できる
✓ 文字列が正しい
⨯ これは失敗する
FAILED Tests\Feature\SampleTest > これは失敗する
Failed asserting that 2 matches expected 3.
Tests: 1 failed, 2 passed (3 assertions)
結果の読み方
| 結果 | 内容 |
|---|---|
| ✓ 足し算が正しく計算できる | 合格✅ |
| ✓ 文字列が正しい | 合格✅ |
| ⨯ これは失敗する | 失敗❌(意図的に失敗させた) |
エラーメッセージの意味:
Failed asserting that 2 matches expected 3.
「2なのに3を期待した」→ だから失敗した、という意味です。
これで「準備 → 実行 → 確認」の3ステップが実際に動くことを体験できました!
3. 自分のアプリのテストコードを書く
① 子ども管理機能のテスト(ChildTest.php)
テストファイルの作成
php artisan make:test ChildTest
テストコードを書く
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ChildTest extends TestCase
{
use RefreshDatabase;
// テスト① 子どもを正常に登録できるか
public function test_子どもを正常に登録できる()
{
// ① 準備:テスト用のユーザーを作成する
$user = User::factory()->create();
// ② 実行:子ども登録を試みる
$response = $this->actingAs($user)->post('/children', [
'nickname' => 'テストちゃん',
'birth_date' => '2023-01-01',
]);
// ③ 確認:登録できたか確認する
$response->assertRedirect(route('children.index'));
$this->assertDatabaseHas('children', [
'nickname' => 'テストちゃん',
]);
}
// テスト② 必須項目未入力でバリデーションエラーになるか
public function test_必須項目未入力でバリデーションエラーになる()
{
// ① 準備:テスト用のユーザーを作成する
$user = User::factory()->create();
// ② 実行:何も入力せずに登録を試みる
$response = $this->actingAs($user)->post('/children', []);
// ③ 確認:バリデーションエラーが出るか確認する
$response->assertSessionHasErrors(['nickname', 'birth_date']);
}
}
テストを実行してみて気づいたこと
最初のテストではエラーが2つ発生しました。
| エラー | 原因 | 学び |
|---|---|---|
| リダイレクト先が違う |
/childrenではなくroute('children.index')だった |
実際のコードを確認することが大切 |
| バリデーションエラーが出ない |
birthdateではなくbirth_dateだった |
フィールド名は実際のコードと一致させる |
実際のコントローラーを確認して修正することで解決しました。
テストコードは実際のコードをしっかり読まないと書けないということがわかりました。
実行結果
PASS Tests\Feature\ChildTest
✓ 子どもを正常に登録できる
✓ 必須項目未入力でバリデーションエラーになる
Tests: 2 passed (6 assertions)
② 家族共有機能のテスト(FamilyGroupTest.php)
前回の手動テストで発見・修正したバグ(重複参加)が再発しないかをテストコードで確認しました。
テストファイルの作成
php artisan make:test FamilyGroupTest
ファクトリーの作成
テスト用のダミーデータを作るために FamilyGroupFactory を作成しました。
php artisan make:factory FamilyGroupFactory --model=FamilyGroup
ファクトリーとは?
テスト用のダミーデータを簡単に作るための仕組みです。本番のデータを使わずにテストできるため、本番データが壊れる心配がありません。
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class FamilyGroupFactory extends Factory
{
public function definition(): array
{
return [
'owner_id' => User::factory(),
'invite_code' => strtoupper(Str::random(8)),
];
}
}
テストコードを書く
<?php
namespace Tests\Feature;
use App\Models\FamilyGroup;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class FamilyGroupTest extends TestCase
{
use RefreshDatabase;
// テスト① 正しい招待コードでグループに参加できるか
public function test_正しい招待コードでグループに参加できる()
{
// ① 準備:オーナーとメンバーを作成する
$owner = User::factory()->create();
$member = User::factory()->create();
$group = FamilyGroup::factory()->create([
'owner_id' => $owner->id,
]);
// ② 実行:招待コードで参加する
$response = $this->actingAs($member)->post('/family/join', [
'invite_code' => $group->invite_code,
]);
// ③ 確認:参加できたか確認する
$response->assertRedirect(route('family_groups.index'));
$this->assertCount(1, $group->fresh()->members);
}
// テスト② 同じグループに重複参加できないか
public function test_同じグループに重複参加できない()
{
// ① 準備:オーナーとメンバーを作成する
$owner = User::factory()->create();
$member = User::factory()->create();
$group = FamilyGroup::factory()->create([
'owner_id' => $owner->id,
]);
// ② 実行:同じ招待コードで2回参加を試みる
$this->actingAs($member)->post('/family/join', [
'invite_code' => $group->invite_code,
]);
$response = $this->actingAs($member)->post('/family/join', [
'invite_code' => $group->invite_code,
]);
// ③ 確認:2回目は「すでに参加しています。」エラーが出るか確認する
$response->assertSessionHas('error', 'すでに参加しています。');
$this->assertCount(1, $group->fresh()->members);
}
}
実行結果
PASS Tests\Feature\FamilyGroupTest
✓ 正しい招待コードでグループに参加できる
✓ 同じグループに重複参加できない
Tests: 2 passed (5 assertions)
手動テストで発見・修正したバグが再発しないことをテストコードで確認できました!
テストコードを書いてみて気づいたこと
テストコードは実際のコードをしっかり読む必要がある
フィールド名やリダイレクト先など、実際のコードと一致していないとテストが通りません。テストコードを書く過程で、自分のコードをあらためて読み返す機会になりました。
手動テストで発見したバグをテストコードで再確認できた
以前手動テストで発見・修正した重複参加バグが、テストコードでも正しく検証できました。これがテストコードの大きなメリットです。修正後に再発しないかを自動で確認できます。
エラーメッセージが手がかりになる
テストが失敗した時のエラーメッセージを読むことで、どこが間違っているかがわかりました。エラーを怖がらずに読む習慣がついてきました。
ファクトリーの存在を知った
テスト用のダミーデータを作るファクトリーという仕組みを初めて使いました。本番データを汚さずにテストできる仕組みがしっかり用意されていることがわかりました。
まとめ
| ファイル | テスト内容 | 結果 |
|---|---|---|
SampleTest.php |
足し算・文字列・意図的な失敗 | ✅ |
ChildTest.php |
子ども登録・バリデーション | ✅ |
FamilyGroupTest.php |
グループ参加・重複参加防止 | ✅ |
テストコードを実際に書いてみて、「動いているから大丈夫」ではなく「テストコードで証明できて初めて大丈夫」という意識に変わりました。
今後は他の機能のテストコードも少しずつ増やしていきたいと思います。