laravel (sail)でテストしてまっか? (2) assertSee系のテスト
ファイルがアップロードされていない場合のテスト
想定
この場合、ファイルがアップロードされていない時の使用は以下の通り
Simple Uploaderと表示されている
Uploaded Filesが表示されている
No files uploaded yet.が表示されている
Uploadボタンが表示されている
などが考えられる。まあ、書いてみよう。
最初のテストの作成
% ./vendor/bin/sail artisan make:test Uploader/IndexTest
INFO Test [tests/Feature/Uploader/IndexTest.php] created successfully.
このようにmake:testする。すると
<?php
namespace Tests\Feature\Uploader;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class IndexTest extends TestCase
{
/**
* A basic feature test example.
*/
public function test_example(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
このようなファイルが生成されるはずだ。これは何気に前回みたExampleと全く変わらない。
イニシャルビューのテスト
class IndexTest extends TestCase
{
public function test_display_initial_screen(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
まず、テスト関数はtest…から初まる必要がある。あるいはphpdocにそれっぽいディレクティブを与えるんだけど面倒なのでtestから初めましょう。
実際のところ、testから初まっていればどういう名前でもいい。実際に
class IndexTest extends TestCase
{
public function testデーター無しで最初に起動した画面(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
これもアリ
実行すると
% ./vendor/bin/sail test
PASS Tests\Unit\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the application returns a successful response 0.26s
PASS Tests\Feature\Uploader\IndexTest
✓ データー無しで最初に起動した画面 0.02s
Tests: 3 passed (3 assertions)
Duration: 0.36s
などとなる。とはいえそれは流石にやんちゃなのでやめておくとしよう
public function test_initial_screen_without_data(): void
{
$response = $this->get('/');
$response->assertStatus(200);
}
文字が見えている事を保証するとassertSee
Simple Uploaderと表示されている
Uploaded Filesが表示されている
No files uploaded yet.が表示されている
Uploadボタンが表示されている
を保証したいのであった、ではそれをダラダラ書いていこう
public function test_initial_screen_without_data(): void
{
$response = $this->get('/');
$response->assertStatus(200);
$response->assertSee('Simple Uploader');
$response->assertSee('Uploaded Files');
$response->assertSee('No files uploaded yet.');
$response->assertSee('Upload');
}
ただし、冷静に考えるまでもなく、これは、いろんな意味で問題しかないテストである、わかりますか?
問題の修正
まず、assertSeeでのUIのテストは、文言変更に脆弱なので、本当にその文字が出て欲しいものを確実に保証したい所だけに仕掛けるべきである。
と自問自答みたいな話をしちゃったけど、本稿はチュートリアルなのでUIのテストをやってみるというのが今回の課題である
…の前にまだviewを出してなかったのでそれを見ていこう
resources/views/uploaders/index.blade.php
<x-uploader-layout>
<div class="max-w-2xl mx-auto py-10 px-6 bg-white rounded-lg shadow-md">
<h1 class="text-2xl font-semibold text-gray-700 mb-5">
<a href="{{ route('uploaders.index') }}" class="hover:underline">Simple Uploader</a>
</h1>
<x-auth-session-status class="mb-4" :status="session('status')" />
<form method="POST" action="{{ route('uploaders.store') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
<div>
<x-input-label for="file" :value="__('File')" class="block text-sm font-medium text-gray-700" />
<div class="mt-1 flex items-center">
<input type="file" id="file" class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-violet-50 file:text-violet-700
hover:file:bg-violet-100" name="file" required autofocus />
</div>
<x-input-error :messages="$errors->get('file')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button>
{{ __('Upload') }}
</x-primary-button>
</div>
</form>
<div class="mb-8">
<h2 class="text-xl font-semibold text-gray-600 mb-3">Uploaded Files</h2>
<div class="overflow-x-auto">
<table class="min-w-full leading-normal">
<thead>
<tr>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
{{ __('File Name') }}
</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
{{ __('Size') }}
</th>
<th class="px-5 py-3 border-b-2 border-gray-200 bg-gray-100"></th>
</tr>
</thead>
<tbody>
@forelse($uploadedFiles as $file)
<tr>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<div class="flex items-center">
<div class="ml-3">
<p class="text-gray-900 whitespace-no-wrap">
<a href="{{ Storage::url('uploaded_files/'. $file->saved_name) }}" class="text-blue-600 hover:text-blue-900">{{ $file->saved_name }}</a>
</p>
</div>
</div>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap">
{{ $file->size }}
</p>
</td>
<td class="px-5 py-5 border-b border-gray-200 bg-white text-sm text-right">
<form method="POST" action="{{ route('uploaders.destroy', $file->id) }}" onsubmit="return confirm('{{ __('Are you sure you want to delete this file?') }}')">
@csrf
@method('DELETE')
<x-danger-button>{{ __('Delete') }}</x-danger-button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="px-5 py-5 border-b border-gray-200 bg-white text-sm">
<p class="text-gray-900 whitespace-no-wrap text-center">
{{ __('No files uploaded yet.') }}
</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</x-uploader-layout>
通常この
この部分は今
<h1 class="text-2xl font-semibold text-gray-700 mb-5">
<a href="{{ route('uploaders.index') }}" class="hover:underline">Simple Uploader</a>
</h1>
このようになっているが、本来はconfigの
'name' => env('APP_NAME', 'Laravel'),
これがセットされている事が理想である。従ってジブンならこのようにテストを書く
これはConfigをそのままチェックするのではなく発想を切り替えて
public function test_initial_screen_without_data(): void
{
// ユニークなアプリケーション名を生成
$uniqueAppName = 'TestApp' . \Str::random();
config(['app.name' => $uniqueAppName]);
$response = $this->get('/');
$response->assertStatus(200);
$response->assertSee($uniqueAppName);
このように捨てconfig値をセットし、それが出てくるかどうかを検査するのであるが、ただ、これはtrueになる。なぜならlayoutが
<title>{{ config('app.name', 'Laravel') }}</title>
このようになっているからだ。つまりhtml全体ではtitleにapp.nameが埋めこまれて必ず表れてくるので、assertSeeは必ずtrueになる。
これをもうちょい改良し
public function test_initial_screen_without_data(): void
{
// ユニークなアプリケーション名を生成
$uniqueAppName = 'TestApp' . \Str::random();
config(['app.name' => $uniqueAppName]);
$response = $this->get('/');
$response->assertStatus(200);
$response->assertSeeInOrder([$uniqueAppName, $uniqueAppName]);
とすることで上から順順にapp titleが2回出る事を保証している。これで失敗するテストが書ける(あえて失敗させている)
' contains "TestAppWhKgpvAFuZtOQaE3" in specified order..
at tests/Feature/Uploader/IndexTest.php:26
22▕
23▕ config(['app.name' => $uniqueAppName]);
24▕ $response = $this->get('/');
25▕ $response->assertStatus(200);
➜ 26▕ $response->assertSeeInOrder([$uniqueAppName, $uniqueAppName]);
27▕
28▕ // $response->assertSee('Uploaded Files');
29▕ // $response->assertSee('No files uploaded yet.');
30▕ // $response->assertSee('Upload');
Tests: 1 failed, 3 passed (5 assertions)
Duration: 0.38s
このように失敗から始めて、あとで修正するという手法がある。修正してみよう
resources/views/uploaders/index.blade.php
<h1 class="text-2xl font-semibold text-gray-700 mb-5">
{{--
<a href="{{ route('uploaders.index') }}" class="hover:underline">Simple Uploader</a>
--}}
<a href="{{ route('uploaders.index') }}" class="hover:underline">{{ config('app.name') }}</a>
</h1>
(実際はコメントアウトすら不要だろう)
% ./vendor/bin/sail test
PASS Tests\Unit\ExampleTest
✓ that true is true 0.01s
PASS Tests\Feature\ExampleTest
✓ the application returns a successful response 0.26s
PASS Tests\Feature\Uploader\IndexTest
✓ application name is not default 0.01s
✓ initial screen without data 0.01s
Tests: 4 passed (5 assertions)
Duration: 0.38s
このようになる
ただしここで気をつけるのは「app.nameが2回表示されること」しかチェックしていないから、なんか適当な所に出現しているとpassしてしまう。タグの中身とかもう少し厳密にチェックをかけてもいいんだけど、実際にはこれくらいでいいと思う。
テストで完全なものを作るのは限界があるし、本来はここはなるべく労力をかけたくない(というか現実の工数としてそこまで余裕が無い事が多い)というトレードオフであるからコンピューターのテストはある程度通す事を前提として書いて、なんか通らなくなったら調べるくらいのノリでやりたい。最終的には人力のテストは不可欠なのであり、自動化されたテストとは相互に補完する関係であるべきである。
UIの変更テストに囚われすぎるな
最初の方に書いときましたよね。テストを書きはじめのころはうれしくなってこのようなUIのチェックを多数仕掛けがちだが、featureテストは実際のところUIをチェックするのはおまけみたいなもんであるから、ここをガッチリきめるのは実は微妙なのだ。しかし前も書いたように今回は初級ドキュメントなのでそういうのも含めていろいろ書いていくよ。これが必要かどうかは後で読んだ皆さんが判断してください。
ちなみに一番ダメなのは無駄なテストを仕掛けすぎた事によりテストが何か沢山通らなくなったのを「ま、あれは対したテストじゃねえから通んなくてもいいか〜」とかいって放置した状態である。そんなんなら最初から書くだけ時間の無駄だからむしろテストなんて書かない方が生産性が高い。
その他のテスト
Simple Uploaderと表示されている→ configのapp.nameが正しく表示されているUploaded Filesが表示されている
No files uploaded yet.が表示されている
Uploadボタンが表示されている
そして次の「Uploaded Filesが表示されている」であるが
全体的に考えてこれ、英語でわざわざ書いてますやんか。ただ、viewは
<h2 class="text-xl font-semibold text-gray-600 mb-3">Uploaded Files</h2>
となっている。ところがこれは「アップロードされたファイル」になるかもしれない
たとえばlaravel langを入れてみる
% ./vendor/bin/sail composer require laravel-lang/lang
そうすると、言語ファイルが大量に入ってくる
config/app.php で
'locale' => 'ja',
などして
artisan lang:update
などすると
% ./vendor/bin/sail artisan lang:update
INFO Collecting translations...
LaravelLang\Lang\Plugin ........................................................ 14ms DONE
INFO Storing changes...
en.json ......................................................................... 2ms DONE
en/auth.php ..................................................................... 1ms DONE
en/pagination.php ............................................................... 0ms DONE
en/passwords.php ................................................................ 0ms DONE
en/validation.php ............................................................... 4ms DONE
ja.json ......................................................................... 1ms DONE
ja/auth.php ..................................................................... 0ms DONE
ja/pagination.php ............................................................... 0ms DONE
ja/passwords.php ................................................................ 0ms DONE
ja/validation.php ............................................................... 5ms DONE
と入ってくる。このja.jsonに
"Uploaded Files": "アップロードされたファイル",
など書いて、viewを
<h2 class="text-xl font-semibold text-gray-600 mb-3">{{ __('Uploaded Files')}}</h2>
とすると
こうなりますわな。つまり
$response->assertSee('Uploaded Files');
こういうテストはあっさり失敗するわけだ
' [UTF-8](length: 3517) contains "Uploaded Files" [ASCII](length: 14).
at tests/Feature/Uploader/IndexTest.php:28
24▕ $response = $this->get('/');
25▕ $response->assertStatus(200);
26▕ $response->assertSeeInOrder([$uniqueAppName, $uniqueAppName]);
27▕
➜ 28▕ $response->assertSee('Uploaded Files');
29▕ // $response->assertSee('No files uploaded yet.');
30▕ // $response->assertSee('Upload');
31▕ }
32▕ }
こういうのに対応したければ
$response->assertSee(__('Uploaded Files'));
こういう風にしておく、とか、まあいろいろ考える必要がある。
public function test_initial_screen_without_data(): void
{
// ユニークなアプリケーション名を生成
$uniqueAppName = 'TestApp' . \Str::random();
config(['app.name' => $uniqueAppName]);
$response = $this->get('/');
$response->assertStatus(200);
$response->assertSeeInOrder([$uniqueAppName, $uniqueAppName]);
$response->assertSee(__('Uploaded Files'));
$response->assertSee(__('No files uploaded yet.'));
}
まあこんな感じで、viewも
{{ __('No files uploaded yet.') }}
こうなってるなら
こういう風にしたとて
% ./vendor/bin/sail test
PASS Tests\Unit\ExampleTest
✓ that true is true
PASS Tests\Feature\ExampleTest
✓ the application returns a successful response 0.28s
PASS Tests\Feature\Uploader\IndexTest
✓ application name is not default 0.01s
✓ initial screen without data 0.02s
Tests: 4 passed (7 assertions)
Duration: 0.40s
ちゃんとテストは通るということになる。
次回は
で、さすがにUIにassertSee()しまくってても意味ないのでもうちょっとfeatureテストのコアになる部分を見ていこう。
ちなみにassertSeeはbreezeのテストでは一切書かれていない。繰り返しになるがFeatureテストでは文言の不一致のテストにとらわれすぎると大抵崩壊する。ただ、これもどーしても譲れない場所とかに関してはやった方がいいときもあるから、この辺は正に経験、センスが問われる所であろう。
この記事が気に入ったらサポートをしてみませんか?