LoginSignup
30

More than 5 years have passed since last update.

超初心者がLaravelテストでつまずいた点を書いてみた

Last updated at Posted at 2018-10-03

こんにちは!
エンジニアになって2ヶ月、テストを書き出して1ヶ月半、
超初心者の私がLaravelテストを書いてつまずいた点を書いてみます。
「こんなの知ってるよ!」となると思いますが、
超初心者がつまずく所ってここなんだな、
という観点で見ていただけると嬉しいです。

自己紹介

・エンジニアになって2ヶ月の超初心者(文系出身)
・前職は営業
・もちろんテスト書くのも初めて
・猫が好き

それでは、早速まとめてみようと思います!

1.assertSee/assertDontSee

AssertSee.php

$res = $this->get("url");
$res->assertStatus(200)
    ->assertDontSee('ネコちゃん');

仕様変更に伴い、このテストが通らなくなりました。

状況

・変更前
    if文で分岐し、条件Aに当てはまるユーザーには「ネコちゃん」を表示。
    上記のテストでは、条件Aに当てはまらないユーザーの時、
    「ネコちゃん」が出ないことを確認していた。

・変更後
    if文ではない箇所で、「ネコちゃん」という文面が追加された。
    そのため、どんな条件のユーザーでも「ネコちゃん」が出るようになった。

どうにかして、if文で出し分けている方の「ネコちゃん」だけが
見えていないことを確認したい。
ただ、
assertDontSee('ネコちゃん')にすると、通らない。

解決策

AssertSeeResolve.php
$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ドキュメントを見ると、assertSeeassertDontSeeの定義は、
「指定したテキストが、ページ上に存在することを宣言します。」と書いてあります。
https://readouble.com/laravel/5.6/ja/dusk.html#assert-see

ページ上に存在する というのが何を表しているのか分からなかったのですが、
どうやらレスポンスとして返ってきたHTMLソースを見ているようでした。
そのため、タグを含めて調べるとうまくいきました。

ちなみに・・・

上記のような場面以外でも、
例えば想定結果が「1」の時、assertSee("1")なんかやっても
「1」なんかどこにでもありますよね。

そういった場合は下記のように、表示されている前後の文字列も含めて書いてあげると、
正確なAssertSee/assertDontSeeができます。

AssertSeeResolve.php
//$hogeNumberはある処理の結果。今回は1の想定
$res = $this->get("url");
$res->assertSee("結果は".$hogeNumber."件です");

2.リダイレクト後のテスト

バリデーションエラーの文言がきちんと出ているか調べるため、
リダイレクト後にassertSeeが効いているか見ようとしました。
最初に書いたコードがこちら。

RedirectTest.php
$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しても、
リダイレクトのレスポンス(≒移動先を伝えているところ)しか見ていない、ということになります。

解決策

RedirectTestResolve.php
$res = $this->post('url',['content' => '']); 
$res->assertRedirect('url2');
$this->get('url2')
    ->assertSee("内容を入力してください。");

こうすれば、リダイレクト後にgetしていることになるので、
バリデーション文言がしっかり取れます!

3.フィールド名の変更とテストの書き方

FieldName.php
$res = $this->post('url', [
            'content' => 'ニャ〜〜〜ン',
        ]);
$this->assertEquals("1", Hoge::count());

上記は、postした内容がきちんとDBに反映されているか見ているテストです。
ある時、フィールド名を変更したところ、上記がエラーになってしまいました。

フィールド名の変更は下記の通りです。

変更前

<textarea name="content">

変更後

<textarea name="hoge[content]">

※今回、hogeにのみ適用したいバリデーションルールがあったため、
このような実装にしました。

解決策

FieldNameResolve.php
$res = $this->post('url', [
    'hoge' => [
        'content' => 'ニャ〜〜〜ン',
    ]
]);
$this->assertEquals("1", Hoge::count());

この場合、hoge[content]と渡すようになったので、
テストでpostする際もこの形で書いてあげる必要がありました。

当たり前なのですが、急にテストでエラーが出ると最初は戸惑いました。

4.テスト名とアノテーション

みなさん、テストのメソッド名を日本語で書けることはご存知でしょうか?
イメージはこんな感じ。

JapaneseTestName.php
public function ステータステスト()
{
    $res = $this->get("url");
    $res->assertStatus(200);
}

ただ、このままだとテストとして認識されません。
テストとして認識してもらうには、下記の宣言が必要となります。


/**
 * @test
 */

つまり・・

JapaneseTestName.php
/**
 * @test
 */
public function ステータステスト()
{
    $res = $this->get("url");
    $res->assertStatus(200);
}

こう書くとうまくいきます。

これはアノテーションと言って、
かなりざっくり言うと

・「注釈」 という意味
・/** で始めて */ で終わる
・テスト実行時の振る舞いを決める

詳しくはこちらのドキュメントへ。
https://phpunit.readthedocs.io/ja/latest/annotations.html

このアノテーションを使うと、英語のテスト名の際、
Testと含まなくてもテストだと認識されます。

逆に言うと、このルールを知らずに
「日本語名のテスト追加したら、通った!」
「Testを含まないメソッド名のテスト追加したら、通った!」
と思っていたら・・・・

スクリーンショット 2018-10-01 21.51.45.png

実際はテストと認識されていない(※↑テスト数が増えていない)だけだった・・!なんてことも。
(私は実際やらかして泣きそうになりました)

ちなみに

他にもたくさんアノテーションはありますが、個人的には@groupが便利でした。

@groupとは、指定したテストのみを実行してくれるアノテーションで、
使う際はこのように書きます。
今回は、グループ名をtestgroupとします。(ここはなんでもOK)

GroupTest.php
/**
 * @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です。

HogeTest.php
use Illuminate\Foundation\Testing\RefreshDatabase;

class HogeTest extends TestCase
{
    use RefreshDatabase;

    public function testFuga()
    {
         //テストを書いていく
     }
}

Factoryとは?

Factoryを用いて、テスト用の疑似データを作ることができます。
使い方は下記のとおり。

FactoryTest.php
$user = factory(User::class)->create();

もし、カラム名を指定してデータを入れたいのであれば、

FactoryTest.php
$user = factory(User::class)->create([
             'name' => 'てすとネコちゃん',
             'email' => 'test_nyan@example.com'
        ]);

このように指定してデータを作成することができます。

ここで指定していないカラムは、factoryを作る際にできた
/database/factories/以下の○○Factory.phpファイルの中に
初期値を指定してあげましょう。
(今回はUserFactoryになっています。)

UserFactory.php
<?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は、
任意のダミーデータを作成してくれるものです。)

オートインクリメントの関係

それでは、ざっくり説明します。

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を使っても、オートインクリメントはリセットされない。
ということを発見しました。

「だから?」という感じではあるのですが、、、。
意外と周りにも知らない人がいたので書いてみました。

最後に

かれこれ1ヶ月半テストをやってみて、
とても学ぶことが多かったです。

初心者なのでここってこういかないの!?と思うことが多々ありました。。。
少しでも私と同じような初心者の方の参考になれば幸いです!

また、ご指摘やアドバイスなどいただけるととても嬉しいです。
何卒よろしくお願い致しますm(_ _)m

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30