はじめに
Laravelでは、PHPUnitが標準で組み込まれているので、プロジェクトのセットアップ完了後、すぐにテストコードを書き始めることができます。この記事では、Featureテストを使用したWeb APIのテストコードの実装と、Unitテストを使用したデータベースのテストコードの実装について紹介します。
FeatureとUnitの使い分け
testsディレクトリの配下に、Featureディレクトリと、Unitディレクトリがあり、ここにテストコードを配置していきます。
 FeatureにはControllerやRouting、Middlewareの機能テストを実装し、それ以外の単体テストをUnitに実装します。
 
phpunit.xml
にこの2つのテストスイートが定義済みのため、テストを実行すると、両方のテストコードが実装されます。さらにテストスイートを分解したい場合は、
phpunit.xml
を編集することで、テストスイートを増やすことができます。
今回は、図書貸し出し機能を例に、テストコードについて説明していきます。
テスト用データベースの準備
自動テストでは、テスト中に何度かデータベースをリフレッシュするため、テスト専用のデータベースを用意しておきましょう。
 
phpunit.xml
で、
<php>
タグの中に、
<server>
タグを使用すると、テスト時にenvファイルの設定を上書きすることができます。
 今回は、同ホスト内に別のデータベースを用意したため、 
DB_DATABASE
のvalueを、テスト用のデータベースに変更します。
|   1 2 3 4 5 6 7 8 9 10 11 12 13  |  <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"          bootstrap="vendor/autoload.php"          colors="true">             ・・・省略・・・     <php>         <server name="APP_ENV" value="testing"/>         ・・・省略・・・         <!-- 以下を追記 -->         <server name="DB_DATABASE" value="test_db"/>     </php> </phpunit>  |  
これで、テストを実行したときは、テスト用のデータベースが使用されるようになります。
Featureテスト
Featureテストを使用して、Web APIのテストコードを実装したいと思います。
テスト対象のコード
まずは、対象となるマイグレーション、コントローラ、ルーティングを実装します。
マイグレーション
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36  |  <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateBooksTable extends Migration {     /**      * Run the migrations.      *      * @return void      */     public function up()     {         Schema::create('books', function (Blueprint $table) {             $table->id();             $table->string('author');             $table->string('title');             $table->string('description');             $table->integer('status')->default(\App\Book::Available);             $table->integer('rent_count')->default(0);             $table->timestamps();         });     }     /**      * Reverse the migrations.      *      * @return void      */     public function down()     {         Schema::dropIfExists('books');     } }  |  
ルーティング(api.php)
|   1 2 3 4 5 6  |  <?php use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; Route::resource('books', 'BookController');  |  
コントローラ(BookController.php)
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  |  <?php namespace App\Http\Controllers; use App\Book; use Illuminate\Http\Request; class BookController extends Controller {     public function index()     {         return Book::all();     }     public function store(Request $request)     {         $book = new Book();         $book->title = $request->title;         $book->author = $request->author;         $book->description = $request->description;         $book->save();         return $book;     } }  |  
モデル(Book.php)
|   1 2 3 4 5 6 7 8 9 10  |  <?php namespace App; use Illuminate\Database\Eloquent\Model; class Book extends Model { }  |  
テストコードの実装
まず、テストコードのファイルを生成します。
|   1  |  $ php artisan make:test BookTest  |  
tests/Feature
ディレクトリに、テストコードの雛形が作成されます。ここにテストコードを実装していきます。
 
/api/books
へのリクエストのテストコードです。
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  |  <?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class BookTest extends TestCase {     use RefreshDatabase;     public function testFetchBook()     {         $response = $this->get('/api/books');         $response->assertStatus(200);     } }  |  
$this->get()
メソッドを使用することで、第一引数のパスにリクエストすることができます。
 その他にも、
$this->post()
でPOSTリクエストも可能です。その場合は、第二引数で、配列でパラメータを付与します。
|   1 2 3 4 5 6 7 8 9 10  |      public function testCreateBook()     {         $response = $this->post('/api/books', [             'title' => 'Laravel入門',             'author' => 'Lara太郎',             'description' => 'Laravel入門書です。'         ]);         $response->assertStatus(201);     }  |  
テスト結果の検証
アサーションで検証することができます。
ステータスコードの検証
assertStatus()
メソッドを使用します。
|   1  |  $response->assertStatus(200); // ステータスコードが200であること  |  
JSONのパラメータ判定
assertJsonFragment()
メソッドを使用すると、引数に渡したJSONが、レスポンスに含まれているか検証します。
|   1 2 3 4 5  |  $response->assertJsonFragment([     'title' => 'Laravel入門',     'author' => 'Lara太郎',     'description' => 'Laravel入門書です。' ]);  |  
JSONの完全一致を検証する場合は、
assertJson()
メソッドを使用します。
テストの実行
次のコマンドでテストを実行できます。
|   1  |  $ php artisan test  |  
テストに成功すると、次のように出力されます。
|   1 2 3 4 5 6 7 8 9 10 11  |  $ php artisan test    PASS  Feature\BookTest   ✓ fetch book   ✓ create book    PASS  Feature\ExampleTest   ✓ basic test   Tests:  6 passed   Time:   0.73s  |  
テストに失敗すると、失敗箇所が表示されるため、実装を修正して、再度テストを行いましょう。
Unitテスト
Unitテストを使用して、モデルの単体テストを実装したいと思います。
テスト対象の実装
先ほどのBookモデルに、本の貸し出し・返却用のメソッドを追加します。
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  |  <?php namespace App; use Illuminate\Database\Eloquent\Model; class Book extends Model {     public const Available = 1;     public const LoanedOut = 2;     public function checkOut() {         $this->increment('rent_count', 1);         $this->status = self::LoanedOut;         $this->save();     }     public function returnBook() {         $this->status = self::Available;         $this->save();     } }  |  
Factory
Factoryは、ダミーレコードを作成できる機能です。引数にFakerが入るため、ダミーデータをランダムに生成することも可能です。
 今回は、あらかじめ貸し出す本を登録しておくため、Factoryを使用して、booksテーブルにインサートしていきます。
 まず、Factoryファイルを作成します。
|   1  |  $ php artisan make:factory BookFactory --model=Book  |  
--model
オプションで、ファクトリで生成するモデルを指定します。ここで指定したモデルが、ファクトリ内で定義されます。
 このモデルクラスを指定しないと、ファクトリでデータを生成しようとしても、エラーとなるため、必ず指定しましょう。
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  |  <?php /** @var \Illuminate\Database\Eloquent\Factory $factory */ use App\Book; use Faker\Generator as Faker; $factory->define(Book::class, function (Faker $faker) {     return [         'author' => $faker->name,         'title' => $faker->title.$faker->randomNumber().$faker->time(),         'description' => $faker->paragraph,         'status' => $faker->numberBetween(1, 2),         'rent_count' => $faker->randomNumber()     ]; });  |  
カラム名をキーとして、値を設定していきます。 
$faker
を使用している箇所は、それぞれランダムな値が入ります。
 Fakerの主なメソッドを紹介します。
- 
randomNumber()
ランダムな正数値を生成します。 - 
numberBetween(min, max)
min〜maxの間のランダムな正数値を生成します。 - 
email
ランダムなメールアドレスを生成します。 - 
phoneNumber
ランダムな電話番号を生成します。 - 
name
ランダムな人物名を生成します。 - 
sentence()
ランダムな1文を生成します。 - 
paragraph()
ランダムな1段落分の文章を生成します。 
そのほかにも、様々なフォーマットがあります。
 参考: Faker (Github)
単体テストの実装
単体テストのファイルを作成します。 
--unit
オプションを付けると、 
tests/Unit
ディレクトリに作成されます。単体テストの場合は、Featureと区別を付けるために、このオプションを使用した方が良いでしょう。
|   1  |  $ php artisan make:test BookTest --unit  |  
テストコードを実装します。
|   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53  |  <?php namespace Tests\Unit; use App\Book; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class BookTest extends TestCase {     use RefreshDatabase;     protected function setUp(): void     {         parent::setUp();         // BookFactoryを使用してbookを100レコード用意する         for ($i = 0; $i < 100; $i++) {             factory(Book::class)->create();         }     }     /**      * 本の貸し出しテスト      */     public function testCheckOut()     {         $book = Book::where('status', Book::Available)->first();         $count = $book->rent_count;         $book->checkOut();         // 貸し出し回数が、元の+1になっていることを確認         $this->assertEquals($book->rent_count, $count + 1);         // ステータスが貸し出し中になっていることを確認         $this->assertEquals($book->status, Book::LoanedOut);     }     /**      * 本の返却テスト      */     public function testReturnBook()     {         $book = Book::where('status', Book::Available)->first();         $count = $book->rent_count;         $book->returnBook();         // 返却時は、貸し出しが変化していないことを確認         $this->assertEquals($book->rent_count, $count);         // ステータスが利用可能になっていることを確認         $this->assertEquals($book->status, Book::Available);     } }  |  
setUp()
メソッドは、各テストが始まる前に毎回呼ばれます。 
RefreshDatabase
トレイトを使用しているため、テスト実施ごとにデータベースの全レコードが削除されるため、ここでFactoryを使用してデータを投入します。
 ここでは使用していませんが、
tearDown()
メソッドは、テスト終了時に毎度呼ばれるメソッドで、各テスト後の後処理に使用します。
 
testCheckOut()
メソッドと
testReturnBook()
メソッドが、今回実装したテストです。
assertEquals()
メソッドを使用して、実行結果を検証します。
テストの実行
Featureテストと同じく、次のコマンドでテストを実行できます。
|   1  |  $ php artisan test  |  
さいごに
いかがでしたか。Laravelでは初めからPHPUnitを使用することができるので、手軽にテストコードを書き始めることができました。適切なテストコードを書いて、システムの品質・保守性向上に繋げたいと思います。