LaravelビギナーのためのPHPUnitを利用したユニットテスト入門
 
	Laravelでアプリケーションを構築することができるようになってきたので次は本格的なアプリケーション構築のためにLaravelでのテスト方法を理解したいという人またはLaravelではどのようにテストを行うかを知りたいという人向けの内容となっています。
LaravelではPHPのためのテストフレームワークであるPHPUnitで標準搭載されているのでLaravelインストール後から特別な設定なしで利用することができます。そのためPHPUnitについての知識が必要となります。本書では最初にPHPUnit単体の理解を深めるため単体での動作確認を行い、その後Laravelを利用したPHPUnitでのテスト方法について説明を行っていきます。
ユニットテストとは
Unit Testは日本語に訳すと単体テストといいます。単体テストは主に作成した各クラスが持つメソッドが意図通りに動作するのかを調べるために実施します。
PHPUnitなどのツールを利用したテストを経験したことがない人もコード記述後に実際にブラウザを利用して意図通りに動作しているのかテストを行っています。PHPUnitでは開発者が毎回ブラウザ上で操作しながらテストを行うのではなくテストの流れをコード化することでテストを自動化することができます。テストはコード化して保存されているためいつでも実行することができます。もし何かバグがあり修正を行ってもこれまでに保存したテストを実施することで修正によって他への影響がないかどうかのチェックを行うことができます。何か追加/修正を行う事にテストを実施することによってバグを減らしコードの品質を高めることできます。
本文書の読者の人の中には単体テストが何をするのかわからないという人もいると思いますので実際に単体テストを行うために環境構築を行っていきます。
PHPUnitでテストを行うためにはパッケージをインストールするためにパッケージ管理ツールのcomposerが必要となるためcomposerコマンドが実行できる環境が必要です。composerコマンドはLaravelをインストールするためにも必要です。
PHPでのテスト環境の構築
composerコマンドを利用してPHPUnitのパッケージをインストールします。パッケージをインストールできる環境を構築するために任意の場所に任意の名前のフォルダphp-unit-testを作成します。
% mkdir php-unit-test
% cd php-unit-test
composer.jsonファイルを作成するためにcomposer initコマンドを実行します。実行すると”Welcome to the Composer config generator”ということで対話的に質問が来ますが動作確認なのでそのまますべてEnterを押してください。実行後フォルダ内にcomposer.jsonファイルが作成されます。
composer.jsonファイルが作成できたらcomposerのオートローディングの設定を行います。オートローディングを設定することで名前空間を利用してPHPのクラスを複数のファイルに分けることができます。オートローディングを設定するためにcomposer.jsonにautoloadを追加してください。Appという名前空間にsrcフォルダを対応させています。
{
  "name": "mac/php-unit-test",
  "authors": [
    {
      "name": "XXX",
      "email": "XXXX"
    }
  ],
  "require": {},
  "autoload": {
    "psr-4": {
      "App\\": "src"
    }
  }
}
}
srcフォルダにファイルを保存するとAppという名前空間の下にクラスや関数を定義することができます。フォルダの階層構造のようなものだと考えてください。
PHPUnitのインストール
composer.jsonファイルが作成できたのでPHPUnitのインストールを行います。
% composer require --dev phpunit/phpunit
インストールが完了するとcomposer.lockファイルとvendorフォルダが作成されます。またオートローダーの情報がvendor¥composerの下にあるautoload_pse4.phpファイルに追加されます。
インストール後にPHPUnitのバージョンは以下のコマンドを実行することで確認することができます。本文書ではPHPUnitのバージョン9.5.9を利用しています。
% ./vendor/bin/phpunit --version
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
はじめてのテスト
作成したコードはsrcフォルダの下に保存します。テスト用のコードはtestsフォルダの中に記述するためsrc, testsの2つのフォルダを作成します。
srcフォルダの中にUser.phpファイルを作成します。UserクラスはfirstNameとlastNameという変数を持ち、getFullNameメソッドでフルネームを戻すだけのシンプルなコードです。
<?php
namespace App;
class User{
  
  public $firstName;
  
  public $lastName;
  public function getFullName(){
    return $this->firstName.' '.$this->lastName;
  }
}
tesstフォルダの下にUserTest.phpファイルを作成します。UserTest.phpという名前は任意の名前ですが通常はクラス名+Testとします。Testを付けるのは必須でTestをつけない場合はテストファイルとして認識してもらえないためテストを記述しても実行することができません。
作成したUserクラスを利用して単体テストを実行します。単体テストの目的はUserクラスが持つgetFullNameメソッドが正しく動作するか確認することです。
テストのコードをUserTest.phpファイルに記述します。行いたいテスト毎に関数を追加していきます。追加する関数にはテストの内容がわかるようにテスト名をつけます。テスト名には先頭にtest_を付ける必要があります。ファイル名にTestをつけたようにtest_をつけない場合はテストとして認識してもらえません。
初めてのテストはUserクラスのgetFullNameメソッドを実行すると正しいフルネームが戻ってくるかのテストなのでtest_return_full_nameという名前をつけています。PHPUnitを利用するのでTestCaseクラスを継承します。TestCaseの中にテストに利用するメソッドが含まれています。ここではassertSameというメソッドです。
<?php
use App\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase{
  public function test_return_full_name(){
    
    $user = new User;
    $user->firstName = 'John';
    $user->lastName =  'Doe';
    $result = $user->getFullName();
    $this->assertSame('John Doe', $result);
  }
}
assertSameメソッドで第一引数に設定した”John Doe”と$resultが一致するかどうか確認しています。assertXXXという関数がTestCaseに準備されているのでテストによって利用するメソッドを変えます。今回のテストではassertEqualsメソッドを利用することもできます。assertSameは型の一致も確認されますがassertEqualsでは型の一致を確認しないという違いがあります。Same, Equalsなどのassertの後の単語でおおよその動作を理解することができます。

UserTest.phpが作成できたらphpunitコマンドでテストを実行します。testsフォルダにあるテスト用ファイルが実行されます。OKと表示されればテストが成功したことを表しています。フォルダ名を誤ってtestにすると’Cannot open file ”test”‘と表示されます。
% ./vendor/bin/phpunit tests
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 00:00.014, Memory: 4.00 MB
OK (1 test, 1 assertion)
もしUserTest.phpファイルのfirstNameをJohnではなくJohに設定するとOKではなくFAILURESと表示され以下のようにエラーメッセージが表示されます。ExpectedとActualで実際の値と期待されている値やFailed asserting that two strings are identical.などのエラーメッセージによってどの部分に誤りがあるのかもわかります。
% ./vendor/bin/phpunit test
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
F                                                                   1 / 1 (100%)
Time: 00:00.009, Memory: 4.00 MB
There was 1 failure:
1) UserTest::test_return_full_name
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
-'John Doe'
+'Joh Doe'
/Users/mac/Desktop/php-unit-test/test/UserTest.php:17
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
テストに成功したか失敗したか文字だけではわかりづらいということで–colorオプションを利用することで成功したか失敗したかをわかりやすく表示することができます。
% ./vendor/bin/phpunit test --color
colorを利用することで成功した時はグリーン、失敗した時はレッドで表示されます。

テスト名にはtest_を先頭につけるといいましたがtest_をつけずに以下のように記述することでメソッドをtestとして認識してくれます。
 /** @test */
  public function return_full_name(){
    //略
説明済みですが先頭のtest_、/** @test */がない場合も実行しておきましょう。phpunitコマンドを実行するとWarningが表示されテストは行われません。UserTestにはテストが含まれていないというメッセージです。
1) Warning
No tests found in class "UserTest".
複数のテスト
Userクラスに関してその他にもテストを行いたい場合は別の関数を追加して行うことができます。関数を追加するためにUser.phpファイルに新たにfirstNameの文字列を返すメソッドを追加します。
public function getFirstNameCount(){
  return strlen($this->firstName);
}
UserTest.phpファイルに文字数を確認するテストreturn_first_name_charactor_countを追加します。UserTest.phpファイルの中に2つのテスト用のメソッドが含まれています。
<?php
use App\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase{
  /** @test */
  public function return_full_name(){
    
    $user = new User;
    $user->firstName = 'John';
    $user->lastName =  'Doe';
    $result = $user->getFullName();
    $this->assertEquals('John Doe', $result);
  }
  /** @test */
  public function return_first_name_charactor_count(){
    
    $user = new User;
    $user->firstName = 'John';
    $user->lastName =  'Doe';
    $result = $user->getFirstNameCount();
    $this->assertEquals(4, $result);
  }
phpunitコマンドでUserTest.phpのテストを実行するとメッセージから2回テストを実行して成功したことがわかります。
% ./vendor/bin/phpunit test --color
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
..                                                                  2 / 2 (100%)
Time: 00:00.012, Memory: 4.00 MB
OK (2 tests, 2 assertions)
setUp()関数
2つの関数を見るとUserインスタンスの作成とfirstNameとlastNameを入力する処理が全く同じです。このような場合はsetUp()関数を利用することでテストの前に共通処理を実行することができます。setUp()関数を使うと下記のように記述することができます。
<?php
use App\User;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase{
  protected $user;
  protected function setUp() :void
  {
    $this->user = new User;
    $this->user->firstName = 'John';
    $this->user->lastName =  'Doe';
  }
  /** @test */
  public function return_full_name(){
    
    $result = $this->user->getFullName();
    $this->assertEquals('John Doe', $result);
  }
  /** @test */
  public function return_first_name_charactor_count(){
  
    $result = $this->user->getFirstNameCount();
    $this->assertEquals(4, $result);
  }
}
setUp()関数の後に戻り値の型voidを設定しない場合は”PHP Fatal error: Declaration of UserTest::setUp() must be compatible with PHPUnit\Framework\TestCase::setUp(): void”のエラーメッセージが表示されテストが実行できないので注意が必要です。
setUp関数利用後にテストを実行するとテストの内容は全く変わっていないのでこれまでと同様にテストは成功します。
dataProvider(データプロバイダー)
dataProviderを利用することでテストの関数に対して値を渡すことができます。return_full_nameでは関数の内部でfirstNameとlastNameの設定を行なっていましたが、引数を使って値を渡すことができます。firstName、lastNameだけではなく期待される値である”John Doe”も一緒に渡すことができます。
配列で値を渡すことができるため下記のように記述することができます。配列に保存されている値を引数として渡すことができます。John, Doe, John Doeを3つの別々の引数として利用することができます。
public function nameProvider()
{
  return [
    ["John","Doe","John Doe"]
  ];
}
値を渡される関数ではテスト名の先頭からtestを省略した時と同様に下記のように記述し先頭に@をつけてdataProviderを記述しどのdataProviderから値をもらうのか記述しておく必要があります。今回はnameProviderという関数を作成したのでnameProviderと記述します。
/** 
 * @test 
 * @dataProvider nameProvider
 */
return_full_test関数には引数を設定します。nameProviderの中で配列に3つの値を設定したのでそれぞれの値を$firstName, $lastName, $expectとしてreturn_full_name関数に渡します。
/** 
 * @test 
 * @dataProvider nameProvider
 */
public function return_full_name($firstName, $lastName, $expect){
  $user = new User;
  $user->firstName = $firstName;
  $user->lastName = $lastName;
  
  $result = $user->getFullName();
  $this->assertEquals($expect, $result);
}
nameProviderから値が渡されテストを実行するとこれまでと同様にテストは行われテストの内容は変わらないのでOKになります。
この例ではあまり役には立ちませんがが他の名前の組み合わせ使ってテストを行いたい時に活用することができます。
複数の値を使ってテストを行いたい場合は配列に値を追加することで配列の数分テストを実施できるようになります。
public function nameProvider()
{
  return [
    ["John","Doe","John Doe"],
    ["Jane","Doe","Jane Doe"],
    ["John","Smith","John Smith"],
  ];
}

テストを実行し結果のメッセージを確認することで3件テストが実施されていることがわかります。
% ./vendor/bin/phpunit tests --color
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
...                                                                 3 / 3 (100%)
Time: 00:00.004, Memory: 4.00 MB
OK (3 tests, 3 assertions)
もし3つ目の配列にテストに失敗するデータを設定すると失敗したデータがどれなのかも確認することができます。下記ではwith data set #2が2となっていますが配列なので0から始めるので3つ目のデータに問題があることがわかります。また..Fという表示はステータスを表しており.(ドット)が成功でFはFailtureを表しています。3番目の配列を修正するとテストをパスします。同じ値になることを確認するためにassertSameを利用していますが異なる値であることをテストしたい場合にはassertNotSameを利用することもできます。
 % ./vendor/bin/phpunit tests --color
PHPUnit 9.5.9 by Sebastian Bergmann and contributors.
..F                                                                 3 / 3 (100%)
Time: 00:00.008, Memory: 4.00 MB
There was 1 failure:
1) UserTest::return_full_name with data set #2 ('John', 'Smith', 'John Smit')
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'John Smit'
+'John Smith'
/Users/mac/Desktop/php-unit-test-2/tests/UserTest.php:34
FAILURES!
Tests: 3, Assertions: 3, Failures: 1.
テストのデバッグ
もしvar_dumpなどを利用しても変数の中身が表示されない場合はphpunitコマンドに–debugオプションをつけて実行します。
 % ./vendor/bin/phpunit tests --colors --debug
var_dumpの中身を表示して実行を終わらせたい場合はdie関数も利用することができます。
die(var_dump($something));
テストをスキップしたい場合
テストをスキップしたい場合はmarkTestSkipped関数を利用することができます。
public function return_full_name()
{
  $this->markTestSkipped();
}
テストをスキップするとテストのステータスには”S”が表示され、 Skippedとしてカウントされます。Skippedとして表示されるのでスキップしたテストがあることを把握することができテストの実行忘れを防ぐことができます。
ここまででPHPUnit単体での利用方法の基礎を学ぶことができました。これからLaravelを使ってPHPUnitでのテストの方法を確認していきます。
LaravelでのPHPUnitの動作確認
Laravelの環境構築
任意の名のLaravelプロジェクトの作成を行ってください。ここではlaravel newコマンドを利用してlaravel_php_unit_testという名前のプロジェクトを作成しています。
% laravel new laravel_php_unit_test
テスト環境の確認
インストール完了後、プロジェクトフォルダにはtestsフォルダを確認することができます。testsフォルダの中にはFeature, UnitフォルダとCreateApplication.php, TestCase.phpファイルの2つがあります。
Unitフォルダには先程確認したように基本は1つのクラスに対して実施するテストを保存します。Featureフォルダには複数のクラスにまたがって処理を行うようなテストを保存します。
composer.jsonファイルを確認するとrequire-devにphpunitが含まれていることが確認できます。またPHPUnitの単体の動作確認に設定したオートローダーについては名前空間Testsとtestsフォルダが指定されていることが確認できます。
//略
"require-dev": {
        "facade/ignition": "^2.5",
        "fakerphp/faker": "^1.9.1",
        "laravel/sail": "^1.0.1",
        "mockery/mockery": "^1.4.2",
        "nunomaduro/collision": "^5.0",
        "phpunit/phpunit": "^9.3.3"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "Database\\Factories\\": "database/factories/",
            "Database\\Seeders\\": "database/seeders/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
//略
Unit、Featureフォルダには同名のExampleTest.phpが保存されています。ファイル名には必ずTestを含むということがここでも確認できます。UnitフォルダのExampleTest.phpファイルを開きます。TestCaseにはPHPUnit単体時に確認したものと同じPHPUnit\Framework\TestCaseが利用されていることがわかります。assertTrueは引数の値がtrueかfalseかを確認するメソッドです。trueであればテストはパスします。booleanを返す関数、booleanを持つ変数をテストする際に利用することができます。
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function test_example()
    {
        $this->assertTrue(true);
    }
}
FeatureフォルダのExampleTest.phpファイルを開くと利用しているTestCaseがUnit内のExampleTest.phpとは異なり名前空間の設定からtestsフォルダ内のTestCase.phpファイルが指定されていることがわかります。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function test_example()
    {
        $response = $this->get('/');
        $response->assertStatus(200);
    }
}
testsフォルダのTestCase.phpファイルを確認するとtraitでCreateApplication.phpファイルが利用されています。
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}
testsフォルダに保存されているフォルダ(Unit, Feature)とファイル(CreateApplication.php, TestCase.phpファイル)の内容と関係を確認することができました。
初めてのLaravelのテストを実行
Laravelでテストを実行してみましょう。利用するコマンドはPHPUnitの単体時と同じでphpunitです。vendor/binフォルダに保存されているphpunitを実行するためvendar/bin/phpunitを実行します。2つのテストが行われていることがわかります。実行されたテストはUnit, Featureフォルダに保存されているExampleTest.phpファイルの中に記述されている関数(test_example)です。
% ./vendor/bin/phpunit
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.
..                                                                  2 / 2 (100%)
Time: 00:00.286, Memory: 20.00 MB
OK (2 tests, 2 assertions)
FeatureフォルダのExampleTest.phpファイルのtest_example関数を確認します。
public function test_example()
{
    $response = $this->get('/');
    $response->assertStatus(200);
}
$this->get(‘/’)ではgetメソッドで”/(ルート)”にアクセスしていることを表します。web.phpファイルを開くと記述されている下記のルーティングへのアクセスを意味します。アクセスするとステータスコードが200が戻ってくるのでassertStatusで200が戻ることをテストしています。
Route::get('/', function () {
    return view('welcome');
});
本当にテストが動作しているのか確認のためgetメソッドでアクセスする場所を/(ルート)からルーティングに存在しない/aboutに変更します。
public function test_example()
{
    $response = $this->get('/about');
    $response->assertStatus(200);
}
変更後テストを実行すると失敗します。ステータスコードが200ではなく404のNot Foundが戻されるためです。テストが正しく動作していることが確認できました。
 % ./vendor/bin/phpunit
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.
.F                                                                  2 / 2 (100%)
Time: 00:00.257, Memory: 20.00 MB
There was 1 failure:
1) Tests\Feature\ExampleTest::test_example
Expected response status code [200] but received 404.
Failed asserting that 200 is identical to 404.
/Users/mac/Desktop/laravel_php_unit_test/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:177
/Users/mac/Desktop/laravel_php_unit_test/tests/Feature/ExampleTest.php:19
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
php artisan testコマンドによるテスト
Laravelではphpunitコマンドの他にphp artisan testコマンドを使ってテストを実行することができます。php artisan testコマンドではテストを実行したファイル名と実行した関数がわかります。関数名からtest_は省略されていますのでexmapleと表示されています。

テストが失敗した場合も確認しておきましょう。phpunitの場合とメッセージは同じですがphpunitコマンドの場合はassertが失敗した行番号のみ表示されていましたがこちらではコードの内容も表示されます。

phpunit.xmlファイル(設定ファイル)
PHPunitにも設定ファイルが存在し設定ファイルを利用することで細かな設定を行うことができます。PHPUnit単体でインストールする場合は自分で作成する必要がありますがLaravelではphpunit.xmlファイルがデフォルトで存在しており事前にいくつかの設定が行われています。
<?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"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <server name="DB_DATABASE" value=":memory:"/> -->
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>
PHPUnit単体の動作確認の場合はテストの結果メッセージにカラーをつけるためオプションで–colorを設定していましたがLaravelではphpunitを実行するとメッセージに自動でテストの結果にカラーがついています。それはオプションを設定しなくてもカラーで表示されるようにphpunit.xmlファイル内でcolorの設定を行なっているからです。phpunit.xmlファイルを見てcolorがあることを確認してください。color=trueからfalseまたは削除するとカラー表示されません。
その他にテストのファイル名には必ずTestを付けると説明しましたがこれもphpunit.xmlで設定されておりtestsuitesタグのsuffixの値を変更するとExampleTest.phpファイルも実行されなくなります。このルールもphpunit.xmlで変更することができます。
このようにphpunit.xmlファイルを使ってPHPUnitのデフォルトの動作をカスタマイズすることができます。
特定のテストファイル、関数を実行
phpunit, php artisan testを実行するとUnit, Featureの下にあるテストはすべて実行されていました。特定のファイル、関数だけを実行したい場合は–filterを利用します。
UnitフォルダのExmpleTest.phpファイルをExmple2Test.phpファイル等名前を変更してください。変更後はファイル内のクラス名の変更も忘れずに行なってください。
名前を変更後に–filterを利用してExampleTestを指定して実行するとExampleTest.phpファイルの中の関数のみ実行されます。
% ./vendor/bin/phpunit --filter ExampleTest
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 00:00.010, Memory: 8.00 MB
OK (1 test, 1 assertion)
–filter Exampleで実行するとExampleTest.php, Example2Test.phpどちらも実行されます。

Featureフォルダの下のテストのみ実行したい場合には–testsuiteオプションを利用することができます。
% ./vendor/bin/phpunit --testsuite=Feature 
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 00:00.099, Memory: 20.00 MB
OK (1 test, 1 assertion)
指定した–testsuiteの値はphpunit.xmlのtestsuiteタグのname属性の値と一致させる必要があります。LaravelでのPHPUnitの基礎が理解できたので少し実践的な内容に進んでいきます。
Laravel Breezeのテスト
Laravel8からはユーザ認証にLaravel Breezeを利用することができます。Laravel Breezeのパッケージにはユーザ登録やログインに関するテストが含まれているのでテスト経験がない人にとっては良い勉強材料になります。Laravel Breezeに含まれるテストを理解することで自作の機能にテストを実施したい時の参考として利用することができます。
Laravel Breezeのインストール
composerコマンドを利用してLaravel Breezeパッケージのインストールを行います。
% composer require laravel/breeze
パッケージのインストールが完了したら、php artisan breeze:installコマンドを実行してください。Laravel Breezeに関連するviewファイルやルートの追加、コントローラーファイルなどが作成されます。
%  php artisan breeze:install
Breeze scaffolding installed successfully.
Please execute the "npm install && npm run dev" command to build your assets.
この時にtestsフォルダの下のFeatureフォルダの下にAuthenticationTest.php, EmailVerficationTest.php, PasswordConfirmationTest.php, PasswordResetTest.php, RegistrationTest.phpファイルが作成されます。名前にTestが入っているのですべてLaravel Breezeの機能に関連するテストファイルです。
breeze:installをインストール後はJavaScript関係のライブラリをインストールしてビルドするためにnpm install && npm run devコマンドを実行します。
データベースの設定
ユーザ情報を保存するためにデータベースの作成を行う必要があります。簡易的に利用できるSQLiteデータベースを利用するためdatabaseフォルダにdatabase.sqliteファイルを作成します。
% touch database/database.sqlite

.envファイルを開いて環境変数DB_CONNECTIONの設定を行います。デフォルトではmysqlに設定されているのでsqliteに変更します。DB_CONNECTIONを残して先頭にDB_がついている他の変数は削除します。
DB_CONNECTION=sqlite
php artisan migrateコマンドを実行してテーブルを作成してください。
% php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.76ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (2.38ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (2.04ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (2.41ms)
UserクラスのUnitテスト
本文書の前半のPHPUnit単体で行ったUser.phpファイルの単体テストをLaravelではどのように行うかを確認していきます。テストファイルはコマンドを利用して作成することができるのでphp artisan make:testコマンドでUserTest.phpファイルを作成します。–unitオプションを付けることでtests¥UnitフォルダにUserTest.phpファイルが作成されます。–unitオプションをつけない場合はFeatureフォルダの中に作成されます。
% php artisan make:test UserTest --unit
Test created successfully.
作成されるUserTset.phpファイルは作成直後では下記が記述されています。
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
    /**
     * A basic unit test example.
     *
     * @return void
     */
    public function test_example()
    {
        $this->assertTrue(true);
    }
}
単体テストを行うために作成したユーザの名前の文字列の長さを戻すgetNameCountメソッドをApp\Models\User.phpファイルに追加します。
public function getNameCount(){
    return strlen($this->name);
}
UserクラスのgetNameCountのテストを行う際にユーザを作成する必要があります。テストを実行する場合はダミーデータを利用するためFactoryを利用してユーザを作成します。テストではユーザの名前の長さをテストするためnameについてはJohnを指定して作成を行っています。その他のemailなどの情報はUserFactory.phpファイルの定義に従って設定されます。
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Models\User;
// use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
    /** @test */
    public function return_name_charactor_count(){
        $user = User::factory()->create([
            'name' => 'John'
        ]);
        $result = $user->getNameCount();
        $this->assertEquals(4, $result);
        
    }
}
上記のテストをパスするためにはインポートするTestCaseをPHPUnit\Framework\TestCaseからTest\TestCaseに変更しています。Test\TestCaseはLaravel用のTestCaseです。
filterオプションを利用してUserTestファイルだけテストを実行するとテストはパスします。
% php artisan test --filter UserTest
PASS  Tests\Unit\UserTest
✓ return name charactor count
Tests:  1 passed
Time:   0.10s
ユーザの作成にFactoryを利用したこととインポートするTestCaseを変更した違いはありますがPHPUnit単体でのテストとほとんど変わらずテストを実施できることが確認できました。
Factoryを利用したことがない人もいると思いますのでUserFactory.phpファイルを確認しておきましょう。definitionメソッドを見るとダミーデータを作成する際の定義が記述されています。fakerライブラリを利用してダミーデータを作成しています。
public function definition()
{
    return [
        'name' => $this->faker->name(),
        'email' => $this->faker->unique()->safeEmail(),
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
}
データベースの管理ソフトなどを利用してデータベースにアクセスするとusersテーブルに作成したユーザが登録されているのが確認できます。nameは作成する際に’John’を指定していたので’John’になっていますがemailにはfakerによって作成されたデータが登録されています。

再度同じテストを実施すると新しいユーザが登録されます。このままの設定ではテストを行う事にユーザが増えていくことになります。10回テストを行うとnameが’John’のユーザが10名登録されることになります。

テストを実行後にデータベースをリフレッシュするためにRefreshDatabaseトレイトを利用することができます。
use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
// use PHPUnit\Framework\TestCase;
class UserTest extends TestCase
{
    use RefreshDatabase;
RefreshDatabaseをUserTest.phpに追加後に再度テストを実施するとusersテーブルの中身は空になります。テスト内でユーザを作成後、ユーザでテストを実施、ユーザ削除が行われます。

テスト用のデータベース
ここまでの設定ではdatabase.sqliteファイルにデータを実際に書き込んでRefreshDatabaseで中身を空にしています。phpunit.xmlファイルの設定で実際のデータベースではなくメモリ上のデータベースを利用してテストを行うことができます。DB_CONNECTIONとDB_DATABASEがデフォルトではコメントされているので以下のように設定を行ってください。これでデータベースの設定は完了です。
<php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="DB_CONNECTION" value="sqlite"/> //ここ
    <server name="DB_DATABASE" value=":memory:"/> //ここ
    <server name="MAIL_MAILER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
    <server name="TELESCOPE_ENABLED" value="false"/>
</php>
ユーザの登録のテスト
Userクラスで単体テストの動作確認をすることができたので実践的なテストを使って動作確認を行っていきます。Laravel Breezeをインストールした時に作成されたtests\FeatureフォルダのRegistrationTest.phpファイルを確認します。
namespace Tests\Feature;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RegistrationTest extends TestCase
{
    use RefreshDatabase;
    public function test_registration_screen_can_be_rendered()
    {
        $response = $this->get('/register');
        $response->assertStatus(200);
    }
    public function test_new_users_can_register()
    {
        $response = $this->post('/register', [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
}
RegistrationTest.phpファイルでは2つのテスト関数が設定されており一つ目のregistration_screen_can_be_renderedは登録画面が表示されているかのテストです。/registerにアクセスするとステータスコードの200が戻るかテストしています。
2つ目のテストでは”/register”に対してPOSTリクエストで登録するユーザのデータを送信しています。assertAuthenticatedでPOSTリクエストで作成されたユーザがLaravelで認証されているかテストを行い、その後RouteServiceProviderのHOMEにリダイレクトされるかのテストを行っています。
テスト内容の流れが理解できたので実際にテストを行ってみましょう。テストの結果はOKになっていますがassertionsは4になっています。コードを見る限りassertXXXは3つしかありません。どういうことなのでしょうか?
% ./vendor/bin/phpunit --filter RegistrationTest  
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.
..                                                                  2 / 2 (100%)
Time: 00:00.198, Memory: 28.00 MB
OK (2 tests, 4 assertions)
assertRedirectが含まれているIlluminate\Testing\TestReponse.phpファイルを見るとassertRedirectには2つのassertが含まれていることがわかります。リダイレクトが行われていることと引数でしたリダイレクト先の場所が一致しているかのテストです。1つのassertXXXの中に複数のassertionが含まれていることもあることがわかりました。
public function assertRedirect($uri = null)
{
    PHPUnit::assertTrue(
        $this->isRedirect(), 'Response status code ['.$this->getStatusCode().'] is not a redirect status code.'
    );
    if (! is_null($uri)) {
        $this->assertLocation($uri);
    }
    return $this;
}
RouteServideProviderのHOMEには/dashboardが設定されています。この値を変更するとテストは失敗します。
人によるテストの確認
一度テストコードを作成するとphp artisan testまたはphpunitコマンドを実行するだけで正常に動作するかどうかテストを実施することはできます。もしテストコードを作成していない場合は下記の流れでテストを行う必要があります。手で行うテストとコードで行うテストの違いを理解することができます。
php artisan serveコマンドで開発サーバを起動します。
% php artisan serve
Starting Laravel development server: http://127.0.0.1:8000
ブラウザでhttp://127.0.0.8000にアクセスします。初期画面が表示されたら右上のRegisterボタンをクリック。

ユーザ情報を入力

/dashboardにリダイレクトされた場合はテストはOK.しかしなにか問題がある場合には作成したユーザの削除またはデータベースのリフレッシュを手動で実行して一からユーザ登録の処理をやり直す必要があります。
この流れをテストとしてコードに記述していればphpunitまたはphp artisan testコマンド一つで動作確認を行うことができます。

ユーザのログインのテスト
ユーザのログインテストについてはAuthenticationTest.phpファイルの中に記述されています。RegistrationTest.phpのテストが理解できればAuthenticationTest.phpのテストの内容は簡単に理解することができます。下記の3つのテストを実施してます。
- /loginにアクセスしたらページが存在しステータスコード200が戻されるか
- 作成したユーザのメールアドレスとパスワードを利用したら認証が正常に行われるのか
- 作成したユーザと異なるパスワードを利用したら認証に失敗するのか
ファイルの中身を確認します。
namespace Tests\Feature;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthenticationTest extends TestCase
{
    use RefreshDatabase;
    public function test_login_screen_can_be_rendered()
    {
        $response = $this>get('/login');
        $response>assertStatus(200);
    }
    public function test_users_can_authenticate_using_the_login_screen()
    {
        $user = User::factory()>create();
        $response = $this>post('/login', [
            'email' => $user>email,
            'password' => 'password',
        ]);
        $this>assertAuthenticated();
        $response>assertRedirect(RouteServiceProvider::HOME);
    }
    public function test_users_can_not_authenticate_with_invalid_password()
    {
        $user = User::factory()>create();
        $this>post('/login', [
            'email' => $user>email,
            'password' => 'wrong-password',
        ]);
        $this>assertGuest();
    }
}
assertAuthenticatedは認証チェックがtrueになることを確認していますがassertGuestはその認証チェックがfalseになっているかをチェックしているかの違いです。
まとめ
PHPUnit単体でのテスト環境の構築から動作確認、PHPUnitを利用したテストと手動でのテストの違い、Laravel Breezeに利用されている実践的なテストの内容を確認することができたのでこれまでLaravelのテストを行ったことがなかった人もLaravelでのテストの基礎は理解できたかと思います。
自作したクラス、機能に対してどのようなメソッドをもっているのか機能をもっているのか作成した自分が一番理解しているのでどのようなテストを実施したいというのを思い浮かべたり言語化することは簡単だと思います。今後は思い浮かべたテストをどのようにコード化していくかを学習していく必要があります。





