こんにちは!
エンジニアになって2ヶ月、テストを書き出して1ヶ月半、
超初心者の私がLaravelテストを書いてつまずいた点を書いてみます。
「こんなの知ってるよ!」となると思いますが、
超初心者がつまずく所ってここなんだな、
という観点で見ていただけると嬉しいです。
##自己紹介
・エンジニアになって2ヶ月の超初心者(文系出身)
・前職は営業
・もちろんテスト書くのも初めて
・猫が好き
それでは、早速まとめてみようと思います!
##1.assertSee/assertDontSee
$res = $this->get("url");
$res->assertStatus(200)
->assertDontSee('ネコちゃん');
仕様変更に伴い、このテストが通らなくなりました。
####状況
・変更前
if文で分岐し、条件Aに当てはまるユーザーには「ネコちゃん」を表示。
上記のテストでは、条件Aに当てはまらないユーザーの時、
「ネコちゃん」が出ないことを確認していた。
・変更後
if文ではない箇所で、「ネコちゃん」という文面が追加された。
そのため、どんな条件のユーザーでも「ネコちゃん」が出るようになった。
どうにかして、if文で出し分けている方の「ネコちゃん」だけが
見えていないことを確認したい。
ただ、
assertDontSee('ネコちゃん')
にすると、通らない。
####解決策
$res = $this->get("url");
$res->assertStatus("200")
->assertDontSee('<li>ネコちゃん</li>')
->assertSee('ネコちゃん');
if文の方の「ネコちゃん」は、liタグに囲まれているため、
これを含めてassertDontSee
で調べると、うまくいきました!
####なぜ?
下記はテストを実行し、エラーが出てきた画面。
if文で出し分けているほうが、<li>ネコちゃん</li>
です。
<div class="fuga">ネコちゃん</div<\n
<div class="hoge">\n
<li>ネコちゃん</li>\n
</div>\n
Laravelドキュメントを見ると、assertSee
やassertDontSee
の定義は、
「指定したテキストが、ページ上に存在することを宣言します。」と書いてあります。
https://readouble.com/laravel/5.6/ja/dusk.html#assert-see
ページ上に存在する というのが何を表しているのか分からなかったのですが、
どうやらレスポンスとして返ってきたHTMLソースを見ているようでした。
そのため、タグを含めて調べるとうまくいきました。
####ちなみに・・・
上記のような場面以外でも、
例えば想定結果が「1」の時、assertSee("1")
なんかやっても
「1」なんかどこにでもありますよね。
そういった場合は下記のように、表示されている前後の文字列も含めて書いてあげると、
正確なAssertSee/assertDontSee
ができます。
//$hogeNumberはある処理の結果。今回は1の想定
$res = $this->get("url");
$res->assertSee("結果は".$hogeNumber."件です");
##2.リダイレクト後のテスト
バリデーションエラーの文言がきちんと出ているか調べるため、
リダイレクト後にassertSee
が効いているか見ようとしました。
最初に書いたコードがこちら。
$res->assertRedirect('url')
->assertSee("内容を入力してください。");
これを実行すると、下記のようにエラーが返ってきました。
Failed asserting that '<!DOCTYPE html>\n
<html>\n
<head>\n
<meta charset="UTF-8" />\n
<meta http-equiv="refresh" content="0;url=http://url" />\n
\n
<title>Redirecting to http://url</title>\n
</head>\n
<body>\n
Redirecting to <a href="http://url">http://url</a>.\n
</body>\n
</html>' contains "内容を入力してください。".
↓かなり省略して書くと、
Failed asserting that '(ここにレスポンス)'contains "内容を入力してください。".
=つまり、レスポンスの中に「内容を入力してください。」という文字が入っていない、と言っています。
####何が返っているのか?
なぜ上記だとできないのか。
リダイレクト処理では何が返ってくるのか、が鍵になります。
リダイレクトとはつまり・・・
HTTPヘッダにあるHTTPステータスコードにてリダイレクトの種類を伝え、Location:ヘッダで移動先を伝える
というもの。
つまり、リダイレクトして直接assertSee
しても、
リダイレクトのレスポンス(≒移動先を伝えているところ)しか見ていない、ということになります。
####解決策
$res = $this->post('url',['content' => '']);
$res->assertRedirect('url2');
$this->get('url2')
->assertSee("内容を入力してください。");
こうすれば、リダイレクト後にgetしていることになるので、
バリデーション文言がしっかり取れます!
##3.フィールド名の変更とテストの書き方
$res = $this->post('url', [
'content' => 'ニャ〜〜〜ン',
]);
$this->assertEquals("1", Hoge::count());
上記は、postした内容がきちんとDBに反映されているか見ているテストです。
ある時、フィールド名を変更したところ、上記がエラーになってしまいました。
フィールド名の変更は下記の通りです。
変更前
<textarea name="content">
変更後
<textarea name="hoge[content]">
※今回、hogeにのみ適用したいバリデーションルールがあったため、
このような実装にしました。
####解決策
$res = $this->post('url', [
'hoge' => [
'content' => 'ニャ〜〜〜ン',
]
]);
$this->assertEquals("1", Hoge::count());
この場合、hoge[content]
と渡すようになったので、
テストでpostする際もこの形で書いてあげる必要がありました。
当たり前なのですが、急にテストでエラーが出ると最初は戸惑いました。
##4.テスト名とアノテーション
みなさん、テストのメソッド名を日本語で書けることはご存知でしょうか?
イメージはこんな感じ。
public function ステータステスト()
{
$res = $this->get("url");
$res->assertStatus(200);
}
ただ、このままだとテストとして認識されません。
テストとして認識してもらうには、下記の宣言が必要となります。
/**
* @test
*/
つまり・・
/**
* @test
*/
public function ステータステスト()
{
$res = $this->get("url");
$res->assertStatus(200);
}
こう書くとうまくいきます。
これはアノテーションと言って、
かなりざっくり言うと
・「注釈」 という意味
・/** で始めて */ で終わる
・テスト実行時の振る舞いを決める
詳しくはこちらのドキュメントへ。
https://phpunit.readthedocs.io/ja/latest/annotations.html
このアノテーションを使うと、英語のテスト名の際、
Testと含まなくてもテストだと認識されます。
逆に言うと、このルールを知らずに
「日本語名のテスト追加したら、通った!」
「Testを含まないメソッド名のテスト追加したら、通った!」
と思っていたら・・・・
実際はテストと認識されていない(※↑テスト数が増えていない)だけだった・・!なんてことも。
(私は実際やらかして泣きそうになりました)
###ちなみに
他にもたくさんアノテーションはありますが、個人的には@group
が便利でした。
@group
とは、指定したテストのみを実行してくれるアノテーションで、
使う際はこのように書きます。
今回は、グループ名をtestgroup
とします。(ここはなんでもOK)
/**
* @group testgroup
*/
public function hogeTest()
{
//処理を書く
}
そして、実行時は下記のように書けばOK。
テストのファイルパス --group グループ名
値を入れるとこんな感じ。(あくまでイメージです。ファイルパスは環境によって異なります)
./phpunit tests/GroupTest.php --group testgroup
ここだけでエラーが起こってるんだ!といった場合や、
このテストだけ検証したいんだ!という場合にとても便利でした。
##5.Factory、RefreshDatabase、IDオートインクリメント
下記はつまずいたというより、やっていく中で発見したものです。
結論を言うと、
・RefreshDatabaseを使っても、IDのオートインクリメントはリセットされない。
ということを発見しました。
それでは、少しずつ見ていきます。
###RefreshDatabaseとは?
各テスト(メソッド)の後にDBをリセットしてくれるトレイトです。
(詳細は下記)
https://readouble.com/laravel/5.6/ja/database-testing.html
つまり、毎回テストした後、DBをまっさらにしてくれるので
DBの影響を受けずにテストを行うことができます。
使い方は、下記を書いてあげるとOKです。
use Illuminate\Foundation\Testing\RefreshDatabase;
class HogeTest extends TestCase
{
use RefreshDatabase;
public function testFuga()
{
//テストを書いていく
}
}
###Factoryとは?
Factoryを用いて、テスト用の疑似データを作ることができます。
使い方は下記のとおり。
$user = factory(User::class)->create();
もし、カラム名を指定してデータを入れたいのであれば、
$user = factory(User::class)->create([
'name' => 'てすとネコちゃん',
'email' => 'test_nyan@example.com'
]);
このように指定してデータを作成することができます。
ここで指定していないカラムは、factoryを作る際にできた
/database/factories/
以下の○○Factory.php
ファイルの中に
初期値を指定してあげましょう。
(今回はUserFactoryになっています。)
<?php
use Faker\Generator as Faker;
$factory->define(App\Daos\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'age' => '22',
'email' => 'firstmail@example.com',
];
});
この場合、ageは先程指定していなかったので、
このファイルで指定した初期値「22」が適用されます。
(ちなみにname
で使用している$faker
は、
任意のダミーデータを作成してくれるものです。)
###オートインクリメントの関係
それでは、ざっくり説明します。
public function hogeTest()
{
$users = factory(User::class ,2)->create();
var_dump($users[0]->id); //1
var_dump($users[1]->id); //2
var_dump($users->count()); //2
}
public function fugaTest()
{
$users = factory(User::class ,2)->create();
var_dump($users[0]->id); //3
var_dump($users[1]->id); //4
var_dump($users->count()); //2
}
上記を見ればわかるように、
・RefreshDatabaseのおかげでメソッドごとにDBはリセットされるため、
count()の結果は常に2になる
・ただし、DBのオートインクリメントはリセットされないため、
IDは連番になっている。
ということが分かります。
つまり、(冒頭でも述べましたが)
・RefreshDatabaseを使っても、オートインクリメントはリセットされない。
ということを発見しました。
「だから?」という感じではあるのですが、、、。
意外と周りにも知らない人がいたので書いてみました。
##最後に
かれこれ1ヶ月半テストをやってみて、
とても学ぶことが多かったです。
初心者なのでここってこういかないの!?と思うことが多々ありました。。。
少しでも私と同じような初心者の方の参考になれば幸いです!
また、ご指摘やアドバイスなどいただけるととても嬉しいです。
何卒よろしくお願い致しますm(_ _)m