3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NIJIBOXAdvent Calendar 2018

Day 15

【Laravel/PHPUnit】テスト通ったのにエラー!初心者にありがちな「テストケースの想定漏れ」実例3選

Last updated at Posted at 2018-12-14

tadoumaです。
早いものでエンジニアになって5ヶ月、
最近、テストが通ったのに検証でエラーや不具合が起こることが増えました。
今回は、初心者に起こりがちな「テストの想定漏れ」について書いてみようと思います!

#目次
1.はじめに 想定漏れについて
2.想定漏れパターン3選
3.最後に

#1.はじめに 想定漏れについて
なぜテストが通っても検証環境でエラーになるのか。
それは「自分が書いたテストの想定範囲が足りなかった」からです。
特に初心者はまだ蓄積しているパターンが少ないため、想定が甘いことが多々あります。(ありました。。。)
今回の記事では、自分が実際にやらかした「想定漏れ」について紹介していきます。

#2.想定漏れパターン3選

##2-1.Carbonでの時間指定でハマった

###なにが起こったのか
登録日から3日経過したユーザーに対し、リマインドメールを送信するバッチを作成した時、
それは起こりました。

登録日から3日前のユーザーを擬似的に作ります。
3日前はもちろん便利なCarbonで作成。

$test_user = factory(User::class)->create(['created_at' => Carbon::today()->subDays(3)]);
スクリーンショット 2018-12-01 20.03.25.png

テスト通った〜〜〜〜!(ΦωΦ)

が、
検証環境でメールが届いてない、だと・・・?

###なぜ起こったのか

単純明快!
0:00以外の時間を想定していなかった
そのため、日付のカウントが1日ずれてしまっていました。

例)
今日の日付を12/15 0:00とした時、、、
A︰テストデータ(12/12 0:00)の場合
12/15  0:00と比較した時、その差は3日
B︰DBに実際にある日付(12/12 0:01)の場合
12/15 0:00と比較した時、その差は2日と23時間59分

そのため、パターンBの場合はカウントした日付が2日になってしまい、送られないのです。

今回、テストデータはCarbonでの作成のため、
全て0:00の日付で作成されていて気づかなかったという・・・。

###対処法
実コードは、取得した日付について、時間を0:00にするよう修正。

$created_at = Carbon::parse($user->created_at)->startOfDay();

テストは、0:00以外の時間を設定するよう変更。

Carbon::setTestNow(Carbon::create(2018, 12, 15, 0, 0, 1));

これで無事、日付のカウントが動くようになりました。
この日付の感覚、初心者だと最初はなかなか掴みづらい気がします。

##2-2.null,0,削除パターン

###なにが起こったのか

まず前提はこちら。
・メール送信バッチ
・ユーザーはアイテムを保持する
・アイテムには期限がある
・ユーザーは削除可能である
・アイテムの期限が切れそうなユーザーに「アイテムを使ってください」とメールを送る

テストでは、
・アイテムを保持するユーザー
・アイテムを保持しないユーザー
この2つを作り、それぞれ送られる/送られないをテストしました。

スクリーンショット 2018-12-01 20.03.25.png

テスト通った〜〜〜〜!ヾ(ΦωΦ)/

と、思いきや。。。。。
またもや検証環境でメールが届いてない・・・?
エラーメッセージ、Trying to get property of non-object
が出てしまいました・・・

###なぜ起こったのか
ユーザーが削除された場合を考えていなかった。

お察しの通りです。
(ユーザーが削除されたらアイテムも消そうよ、というツッコミは一旦置いておいて。。。)

今回、こんな感じでメールアドレスを取得しようとしていました。

$item->user->email;

期限間近のアイテムを取得

そのアイテムの持ち主を取得

持ち主のメールアドレスを取得

ま〜当然ユーザーいなかったら、nullからemailを取ろうとしているので無理ですね!
すみませんでした!!!

###対処法

実コードは、$userがなかったときの処理を追加。

if(empty($user)){
    return false;
}

テストでは、削除されたユーザーを入れておく。

$test_user->delete();

これで完了!
私は「nullだったら」「削除されたら」「0だったら」といった想定を忘れて
検証環境でエラーになることが多かったです。。。

##2-3.否定アサーションと接続詞

###なにが起こったのか
送られてはいけない条件のユーザーに、メールが送られていないことをテストしていた時、、、

$no_send_user1$no_send_user2は、どちらも「送られてはいけない」条件のユーザー

Mail::assertNotQueued(TestMail::class, function($mail) use ($no_send_user1, $no_send_user2){
    return $mail->hasTo($no_send_user1->email) and
    $mail->hasTo($no_send_user2->email);
});

はい、
スクリーンショット 2018-12-01 20.03.25.png

テスト通った〜〜〜〜!ฅ(* ΦωΦ *) ฅ

と、思った時期が私にもありました。
しかし実際にはメールが送られてしまっていたのです・・・・
原因は単純だったので置いておいて、「なぜこれが検知できなかったか」で考えてみます。

###なぜ起こったのか
not A and B と、not A or Bを誤認していた。
あ、こいつ文系だなって感じですね。
ただ私これ、専攻の英語でも勉強してた・・・・・。

平たく言うと、
not A and B→部分否定
(Aでない または Bでない)
not A or B→全否定
(Aでない かつ Bでない)
これを取り違えていたのです。

上記のコードの場合、
$no_send_user1$no_send_user2
どちらか一方に送られていなければ、それでTrueを返していた、ということ。

つまり、$user1$user2 どちらにも送られていないことを確認したい場合、
andではなくorで繋げなければいけなかったのです。。

###対処法
上記で言っちゃいましたが、orで繋げることです。

Mail::assertNotQueued(TestMail::class, function($mail) use ($no_send_user1, $no_send_user2){
    return $mail->hasTo($no_send_user1->email) or
    $mail->hasTo($no_send_user2->email);
});

知らないと一度はやりそうなミス。。

##3.最後に
自分の想定範囲が甘いと、
せっかくのテストが意味を成さなくなってしまいます。

では、どうすれば想定漏れがなくなるのか?
【初心者が気をつけること】としては、
・他の人のテストコードを読み込む。必ず「ここまで想定する?ここまで場合分けする!?」って思うことがあるはず。
・テストケースを作成する際、場合分けした後に「もし○○だったら」を思いつく限り書いてみる
(今回だと、「もしユーザーが削除されていたら」を考えつけていればエラーは起こらなかった)

簡単ですが、このあたりが対処法として考えられるかと思います。
私も今後「もし○○だったら」を考えられるよう、日々精進していこうと思いました。

今回の記事が少しでもなにかの参考になれば幸いです!

3
0
0

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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?