入社して2ヶ月の超初心者が、Laravelテストでつまずいた点を話します
[markdown]
こんにちは!
ニジボックスに7月末より入社している@tadoumaです。
エンジニアになって2ヶ月、テストを書き出して1ヶ月半、
超初心者の私がLaravelテストを書いてつまずいた点を書いてみます。
こんなの知ってるよ!となるかもしれませんが、、
温かい目で見守ってください。
自己紹介
・エンジニアになって2ヶ月の超初心者(文系出身で、プログラミングに触れた経験ほぼなし)
・前職は営業
・もちろんテスト書くのも初めて
・猫が好き
それでは、早速まとめてみようと思います!
目次
1.assertSee/assertDontSee
2.リダイレクト後のテスト
3.フィールド名の変更とテストの書き方
4.テスト名とアノテーション
5.Factory、RefreshDatabase、IDオートインクリメント
6.最後に
それでは、1つずつ見ていきましょう!
目次
1.assertSee/assertDontSee
“`
php:AssertSee.php
$res = $this->get(“url”);
$res->assertStatus(200)
->assertDontSee(‘ネコちゃん’);
“`
仕様変更に伴い、このテストが通らなくなりました。
状況
・変更前
if文で分岐し、条件Aに当てはまるユーザーには「ネコちゃん」を表示。
上記のテストでは、条件Aに当てはまらないユーザーの時、
「ネコちゃん」が出ないことを確認していた。
・変更後
if文ではない箇所で、「ネコちゃん」という文面が追加された。
そのため、どんな条件のユーザーでも「ネコちゃん」が出るようになった。
どうにかして、if文で出し分けている方の「ネコちゃん」だけが
見えていないことを確認したい。
ただ、
`assertDontSee(‘ネコちゃん’)` にすると、通らない。
解決策
“`php:AssertSeeResolve.php
$res = $this->get(“url”);
$res->assertStatus(“200”)
->assertDontSee(‘
‘)
->assertSee(‘ネコちゃん’);
“`
if文の方の「ネコちゃん」は、liタグに囲まれているため、
これを含めて`assertDontSee`で調べると、うまくいきました!
なぜ?
下記はテストを実行し、エラーが出てきた画面。
if文で出し分けているほうが、`
`です。
“`
\n
\n
“`
Laravelドキュメントを見ると、`assertSee`や`assertDontSee`の定義は、
「指定したテキストが、ページ上に存在することを宣言します。」と書いてあります。
ページ上に存在する というのが何を表しているのか分からなかったのですが、
どうやらレスポンスとして返ってきたHTMLソースを見ているようでした。
そのため、タグを含めて調べるとうまくいきました。
ちなみに・・・
上記のような場面以外でも、
例えば想定結果が「1」の時、`assertSee(“1”)`なんかやっても
「1」なんかどこにでもありますよね。
そういった場合は下記のように、表示されている前後の文字列も含めて書いてあげると、
正確な`AssertSee/assertDontSee`ができます。
“`php:AssertSeeResolve.php
//$hogeNumberはある処理の結果。今回は1の想定
$res = $this->get(“url”);
$res->assertSee(“結果は”.$hogeNumber.”件です”);
“`
2.リダイレクト後のテスト
バリデーションエラーの文言がきちんと出ているか調べるため、
リダイレクト後に`assertSee`が効いているか見ようとしました。
最初に書いたコードがこちら。
“`php:RedirectTest.php
$res->assertRedirect(‘url’)
->assertSee(“内容を入力してください。”);
“`
これを実行すると、下記のようにエラーが返ってきました。
“`
Failed asserting that ‘\n
\n
\n
\n
\n
\n
\n
Redirecting to http://url.\n
\n
‘ contains “内容を入力してください。”.
“`
↓かなり省略して書くと、
`Failed asserting that ‘(ここにレスポンス)’contains “内容を入力してください。”.`
=つまり、レスポンスの中に「内容を入力してください。」という文字が入っていない、と言っています。
何が返っているのか?
なぜ上記だとできないのか。
リダイレクト処理では何が返ってくるのか、が鍵になります。
リダイレクトとはつまり・・・
>HTTPヘッダにあるHTTPステータスコードにてリダイレクトの種類を伝え、Location:ヘッダで移動先を伝える
というもの。
つまり、リダイレクトして直接“`assertSee“`しても、
リダイレクトのレスポンス(≒移動先を伝えているところ)しか見ていない、ということになります。
解決策
“`php:RedirectTestResolve.php
$res = $this->post(‘url’,[‘content’ => ”]);
$res->assertRedirect(‘url2’);
$this->get(‘url2’)
->assertSee(“内容を入力してください。”);
“`
こうすれば、リダイレクト後にgetしていることになるので、
バリデーション文言がしっかり取れます!
3.フィールド名の変更とテストの書き方
“`php:FieldName.php
$res = $this->post(‘url’, [
‘content’ => ‘ニャ〜〜〜ン’,
]);
$this->assertEquals(“1”, Hoge::count());
“`
上記は、postした内容がきちんとDBに反映されているか見ているテストです。
ある時、フィールド名を変更したところ、上記がエラーになってしまいました。
フィールド名の変更は下記の通りです。
変更前
“`
変更後
“`
※今回、hogeにのみ適用したいバリデーションルールがあったため、
このような実装にしました。
解決策
“`php:FieldNameResolve.php
$res = $this->post(‘url’, [
‘hoge’ => [
‘content’ => ‘ニャ〜〜〜ン’,
]
]);
$this->assertEquals(“1”, Hoge::count());
“`
この場合、`hoge[content]`と渡すようになったので、
テストでpostする際もこの形で書いてあげる必要がありました。
当たり前なのですが、急にテストでエラーが出ると最初は戸惑いました。
4.テスト名とアノテーション
みなさん、テストのメソッド名を日本語で書けることはご存知でしょうか?
イメージはこんな感じ。
“`php:JapaneseTestName.php
public function ステータステスト()
{
$res = $this->get(“url”);
$res->assertStatus(200);
}
“`
ただ、このままだとテストとして認識されません。
テストとして認識してもらうには、下記の宣言が必要となります。
“`php:
/**
* @test
*/
“`
つまり…。
“`php:JapaneseTestName.php
/**
* @test
*/
public function ステータステスト()
{
$res = $this->get(“url”);
$res->assertStatus(200);
}
“`
こう書くとうまくいきます。
これはアノテーションと言って、
かなりざっくり言うと
>・「注釈」 という意味
・/** で始めて */ で終わる
・テスト実行時の振る舞いを決める
詳しくはPHPUnitのドキュメントへ。
このアノテーションを使うと、英語のテスト名の際、
Testと含まなくてもテストだと認識されます。
逆に言うと、このルールを知らずに
「日本語名のテスト追加したら、通った!」
「Testを含まないメソッド名のテスト追加したら、通った!」
と思っていたら…。
実際はテストと認識されていない(※↑テスト数が増えていない)だけだった…!なんてことも。
(私は実際やらかして泣きそうになりました)
ちなみに
他にもたくさんアノテーションはありますが、個人的には`@group`が便利でした。
`@group`とは、指定したテストのみを実行してくれるアノテーションで、
使う際はこのように書きます。
今回は、グループ名を`testgroup`とします。(ここはなんでもOK)
“`php:GroupTest.php
/**
* @group testgroup
*/
public function hogeTest()
{
//処理を書く
}
“`
そして、実行時は下記のように書けばOK。
`テストのファイルパス –group グループ名`
値を入れるとこんな感じ。(あくまでイメージです。ファイルパスは環境によって異なります)
`./phpunit tests/GroupTest.php –group testgroup`
ここだけでエラーが起こってるんだ!といった場合や、
このテストだけ検証したいんだ!という場合にとても便利でした。
5.Factory、RefreshDatabase、IDオートインクリメント
下記はつまずいたというより、やっていく中で発見したものです。
結論を言うと、
**・RefreshDatabaseを使っても、IDのオートインクリメントはリセットされない。**
ということを発見しました。
それでは、少しずつ見ていきます。
RefreshDatabaseとは?
各テスト(メソッド)の後にDBをリセットしてくれるトレイトです。
(詳細はドキュメントにて)
つまり、毎回テストした後、DBをまっさらにしてくれるので
DBの影響を受けずにテストを行うことができます。
使い方は、下記を書いてあげるとOKです。
“`php:HogeTest.php
use Illuminate\Foundation\Testing\RefreshDatabase;
class HogeTest extends TestCase
{
use RefreshDatabase;
public function testFuga()
{
//テストを書いていく
}
}
“`
Factoryとは?
Factoryを用いて、テスト用の疑似データを作ることができます。
使い方は下記のとおり。
“`php:FactoryTest.php
$user = factory(User::class)->create();
“`
もし、カラム名を指定してデータを入れたいのであれば、
“`php:FactoryTest.php
$user = factory(User::class)->create([
‘name’ => ‘てすとネコちゃん’,
‘email’ => ‘test_nyan@example.com’
]);
“`
このように指定してデータを作成することができます。
ここで指定していないカラムは、factoryを作る際にできた
`/database/factories/`以下の`○○Factory.php`ファイルの中に
初期値を指定してあげましょう。
(今回はUserFactoryになっています。)
“`php:UserFactory.php
define(App\Daos\User::class, function (Faker $faker) {
return [
‘name’ => $faker->name,
‘age’ => ’22’,
‘email’ => ‘firstmail@example.com’,
];
});
“`
この場合、ageは先程指定していなかったので、
このファイルで指定した初期値「22」が適用されます。
(ちなみに`name`で使用している`$faker`は、
任意のダミーデータを作成してくれるものです。)
オートインクリメントの関係
それでは、ざっくり説明します。
“`php:AutoIncrement.php
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を使っても、オートインクリメントはリセットされない。**
ということを発見しました。
「だから?」という感じではあるのですが、、、
意外と周りにも知らない人がいたので書いてみました。
6.最後に
入社して2ヶ月、テスト始めて1ヶ月半。
全く何もわからなくって、最初の頃は家で号泣(悔し泣き)したりしていましたが、
振り返ってみると本当に本当に学びが多かったと感じます。
厳しくも優しく接してくださった@niisan-tokyoさん、
いつも女神のように優しい(男性の)@fudoさん
本当にありがとうございました!
初心者目線で「え〜なんで〜!?」と思ったところをまとめた記事でしたが、いかがでしたでしょうか。
私と同じような初心者の方には、少しでも参考になればいいなと思いますし、
経験者の方には「初心者にはこう見えているんだな〜」という参考になれば、と思っています。
読んでいただきありがとうございました!
[/markdown]