はじめに
「ようこそ・・・『テストの世界』へ・・・」
本記事では、テストコードをまだ書いたことのないプログラミング初学者向けに
「今日からテストを書いてみようかな」
と思ってもらえるよう、チュートリアル形式で簡単なテストの流れを説明します。
題材はLaravelですが、他のフレームワークでも同じようなことはできると思います。
注:本記事は、以前に私がQiitaに投稿したLaravel5.8のテストチュートリアルをLaravel6で動作するよう改訂したものです。
目次
前提
本記事の対象者
- Laravel初心者で、
- テストをまだ書いたことの無い人
- テストで何ができるのか知らない人
- テストに興味はあるが忙しくてどう書けば良いのかまだ調べられていない人
本記事で取り扱うこと
- ごく簡単なHTTPテストの書き方
本記事で取り扱わないこと
- テストの全般的な話(利点、注意点など)
- テストケースの作り方
- CIツールによるテスト自動化
環境
- Laravel 6.4.x
簡単な画面(HTTPレスポンス)のテスト
まずは、Laravelを触ったことのある人なら一度は見たことのある?Welcome画面をテストしてみます。
これが正常に表示されることを、目視ではなくテストコードで確認してみましょう。
Laravelをインストールすると、既にtests/Feature/ExampleTest.php
という、テストコードのサンプルが存在します。
tests/Feature/ExampleTest.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 testBasicTest() { $response = $this->get('/'); $response->assertStatus(200); } }
このExampleTest
は、Tests\TestCase
を継承しており、テストに関する様々なメソッドが使えます。
$response = $this->get('/')
で、'/'
にアクセスした(GETリクエストした)結果のレスポンス(注1)が$response
に代入されます。
注1: 正確には\Illuminate\Foundation\Testing\TestResponse
が代入されます。
そして、$response->assertStatus(200)
で、そのレスポンスが正常であること(ステータスコードが200 OK
であること)をチェックしています。
ステータスコードについては以下を参考にしてください。
このExampleTest
を実行するには、Laravelのルートフォルダで以下コマンドを実行してください。
$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
すると、以下の結果が表示されました。
PHPUnit 8.4.3 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 405 ms, Memory: 16.00 MB OK (1 test, 1 assertion)
OK、と表示されています。
つまり、'/'
へアクセスした(GETリクエストした)結果、正常にレスポンスが返ってきた(HTTPレスポンスステータスコードが200 OK
だった)ということです。
これをテストコードで確認することができました。
https://shonansurvivors.booth.pm/items/3096761
ビューのテスト
ただ、このテストでわかったのは、正常にレスポンスが返ってきた、ということまでです。
Welcome画面が表示されたのかどうかまでテストできていません。
現状のルーティングを見ると、'/'
へアクセスすると、'welcome'
というビューが表示されることがわかります。
routes/web.php
<?php // 略 Route::get('/', function () { return view('welcome'); });
welcome
というビューは、具体的にはresources/views/welcome.blade.php
というビューのテンプレートです。
このresources/views/welcome.blade.php
こそがあのWelcome画面なので、このテンプレートが使われているかどうかをテストで確認してみます。
tests/Feature/ExampleTest.php
に以下のコードを追加します。
tests/Feature/ExampleTest.php
<?php // 略 public function testBasicTest() { $response = $this->get('/'); $response->assertStatus(200) ->assertViewIs('welcome'); // 追加 }
このようにassertViewIs
メソッドで、どんなビューが使われたのかをテストすることができます。
修正後のExampleTest
を実行してみます。
$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php // 略 OK (1 test, 2 assertions)
こちらも結果はOKとなりました。
あのWelcome画面が表示されていることを、テストコードで確認することができました。
さらにassertSee
メソッドを使うと、表示(注2)されている文字列もテストできます。
注2: 正確にはhtml内にその文字列が含まれているかをテストします。htmlのタグなども確認の対象になります。
Welcome画面にLaravel
の文字が表示されていることを、テストコードで確認してみましょう。
tests/Feature/ExampleTest.php
に以下のコードを追加します。
tests/Feature/ExampleTest.php
<?php // 略 public function testBasicTest() { $response = $this->get('/'); $response->assertStatus(200) ->assertViewIs('welcome') ->assertSee('Laravel'); // 追加 }
修正後のExampleTest
を実行してみます。
$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php // 略 OK (1 test, 3 assertions)
こちらも結果はOKとなりました!
ログイン中かどうかを絡めたテスト
ここから先は、ユーザーがログイン中かそうでないかによって結果が異なる画面をテストしていきます。
laravel/uiライブラリからユーザー登録画面やログイン画面等を作成できるので、これを実行します。
$ php artisan migrate $ composer require laravel/ui $ php artisan ui vue --auth $ npm install $ npm run dev
上記コマンドの詳細については、以下の記事を参考にしてください。
Welcome画面にユーザー登録画面とログイン画面へのリンクが追加されました。
以下、テストではなく人力でユーザー登録画面にアクセスし、ユーザー登録を行なっています。
ユーザー登録が完了すると、以下のホーム画面へ遷移します。なお、ログイン画面からログインした場合も、このホーム画面へ遷移します。
ホーム画面は、ログイン中の時のみ表示されます。
ログインしていない状態で直接/home
へアクセスすると、ホーム画面は表示されず、ログイン画面へリダイレクトされます。
この「ホーム画面は、ログイン中の時のみ表示される」ということをテストコードで確認してみましょう。
ホーム画面の表示は、php artisan make:auth
コマンドによって作成された、app\Http\Controlles\HomeController
で行われています。
このHomeController
をテストするためのHomeControllerTest
を作成します。
以下コマンドを実行すると、テストの雛形を作成できます。
$ php artisan make:test HomeControllerTest
作成されたテストの雛形の内容は、以下になります。
tests/Feature/HomeControllerTest.php
<?php namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class HomeControllerTest extends TestCase { /** * A basic feature test example. * * @return void */ public function testExample() { $response = $this->get('/'); $response->assertStatus(200); } }
このtests/Feature/HomeControllerTest.php
を以下の通り編集します。
tests/Feature/HomeControllerTest.php
<?php // 略 public function testExample() { $response = $this->get('/home'); // 変更(ホーム画面のパスに変更) $response->assertStatus(200) ->assertViewIs('home') // 追加(ここでの'home'は、ホーム画面で使われているビュー名) ->assertSee('You are logged in!'); // 追加(ホーム画面で表示されているメッセージ) }
このHomeControllerTest
を、以下コマンドで実行します。
$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php
すると、以下の通り、OKではなくFAILURES!となりました。
PHPUnit 8.4.3 by Sebastian Bergmann and contributors. F 1 / 1 (100%) Time: 1.15 seconds, Memory: 16.00 MB There was 1 failure: 1) Tests\Feature\HomeControllerTest::testExample Expected status code 200 but received 302. Failed asserting that false is true. /var/www/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:183 /var/www/tests/Feature/HomeControllerTest.php:20 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Expected status code 200 but received 302.
と出力されています。
ログイン中でない状態でアクセスしたので、リダイレクトを示す302
のステータスコードが返ってきた、ということです。
そこで、ログインした状態を作り出してテストしてみます。
DBには、先ほど人力でユーザー登録画面にアクセスして登録したユーザーデータが存在するので、テストではこのユーザーを使うことにします。
# select id, name from users; id | name ----+-------------------- 1 | shonansurvivors (1 rows)
tests/Feature/HomeControllerTest.php
を以下の通り編集します。
tests/Feature/HomeControllerTest.php
<?php namespace Tests\Feature; use App\User; // 追加 use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class HomeControllerTest extends TestCase { public function testExample() { $response = $this ->actingAs(User::find(1)) // 追加 ->get(route('home')); $response->assertStatus(200) ->assertViewIs('home') ->assertSee('You are logged in!'); } }
actingAs
メソッドで、そのユーザーとしてログイン済の状態になります。
また、User::find(1)
で、DBのusersテーブルからid
が1
であるユーザーを取ってきています。
改めてHomeControllerTest
を、以下コマンドで実行します。
$ ./vendor/bin/phpunit ./tests/Feature/ExampleTest.php
すると、OKとなりました。
OK (1 test, 3 assertions)
ログイン中の状態で/home
へアクセスすると、
- ステータスコードが
200
となること home
というビューが使われていることYou are logged in!
が、html中に存在すること
が確認できました。
このようにログイン中かどうかが絡む機能も、テストコードで確認できます。
テストに必要なDBのデータを自動で準備する
今回はたまたま開発環境のDBにユーザーのデータが存在し、テストではこれを利用してログインを行いましたが、 テストに必要なDBのデータ準備もコードで自動化してみます。
Laravelではテスト用のDBのデータを作るのに、ファクトリというものを使う方法があります。
database/factories/UserFactory.php
が、ユーザーデータを作るためのファクトリです。
database/factories/UserFactory.php
<?php use App\User; use Faker\Generator as Faker; use Illuminate\Support\Str; $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // password 'remember_token' => Str::random(10), ]; });
このファクトリの詳細については本記事では触れませんが、これを使うといい感じにダミーのデータを作ってくれます。
ファクトリについてもっと知りたい方は、以下記事を参考にしてください。
このファクトリを使うよう、tests/Feature/HomeControllerTest.php
を以下の通り変更します。
tests/Feature/HomeControllerTest.php
<?php namespace Tests\Feature; use App\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class HomeControllerTest extends TestCase { public function testExample() { $user = factory(User::class)->create(); // 変更(ファクトリでユーザーデータを作成) $response = $this ->actingAs($user) // 変更(ファクトリで作ったユーザーデータでログイン中状態を作る) ->get(route('home')); $response->assertStatus(200) ->assertViewIs('home') ->assertSee('You are logged in!'); } }
ファクトリで作成されたユーザーにて、ログイン中の状態を作っています。
以下コマンドでHomeControllerTest
を実行すると、結果はOKとなりました。
$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php // 略 OK (1 test, 3 assertions)
ただ、これだとHomeControllerTest
実行のたびにDBにダミーのユーザーデータが1件また1件...と作られてしまいます。
sample=# select id, name from users; id | name ----+-------------------- 1 | shonansurvivors 2 | Dr. Jayce Wiegand (2 rows)
2件目がファクトリで作られたダミーのユーザーデータです。
これに対しては、ひとつのやり方として、テスト用のDatabaseTransactions
を使うという方法があります。
これを使うと、テストの実行後にDBをロールバックし、テスト実行中にDBに作ったデータが残らなくなります。
tests/Feature/HomeControllerTest.php
で、DatabaseTransactions
を使うようにします。
tests/Feature/HomeControllerTest.php
<?php namespace Tests\Feature; use App\User; use Illuminate\Foundation\Testing\DatabaseTransactions; // 追加 use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase; class HomeControllerTest extends TestCase { use DatabaseTransactions; // 追加 public function testExample() { // 以下変更点なしにつき省略
以下コマンドでHomeControllerTest
を実行すると、結果は引き続きOKとなりますが・・・
$ ./vendor/bin/phpunit ./tests/Feature/HomeControllerTest.php // 略 OK (1 test, 3 assertions)
テスト終了後のDBを見ると、ユーザーデータはテスト開始前時点から増えてはいません(2件のまま)でした。
sample=# select id, name from users; id | name ----+-------------------- 1 | shonansurvivors 2 | Dr. Jayce Wiegand (2 rows)
これでDBにダミーデータが残ることなく、何度でもテストを実行できます。
最後に
以上で、本記事のテスト体験ツアーは終了です。
テストでどのようなことができるか、イメージはつかめたでしょうか。
本記事をきっかけに、テストを書き始めていただければ幸いです。
「納期は守る」「品質も確保する」 「両方」やらなくちゃあならないってのが「エンジニア」のつらいところだな