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)]);
テスト通った〜〜〜〜!(ΦωΦ)
が、
検証環境でメールが届いてない、だと・・・?
###なぜ起こったのか
単純明快!
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つを作り、それぞれ送られる/送られないをテストしました。
テスト通った〜〜〜〜!ヾ(ΦωΦ)/
と、思いきや。。。。。
またもや検証環境でメールが届いてない・・・?
エラーメッセージ、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);
});
テスト通った〜〜〜〜!ฅ(* ΦωΦ *) ฅ
と、思った時期が私にもありました。
しかし実際にはメールが送られてしまっていたのです・・・・
原因は単純だったので置いておいて、「なぜこれが検知できなかったか」で考えてみます。
###なぜ起こったのか
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.最後に
自分の想定範囲が甘いと、
せっかくのテストが意味を成さなくなってしまいます。
では、どうすれば想定漏れがなくなるのか?
【初心者が気をつけること】としては、
・他の人のテストコードを読み込む。必ず「ここまで想定する?ここまで場合分けする!?」って思うことがあるはず。
・テストケースを作成する際、場合分けした後に「もし○○だったら」を思いつく限り書いてみる
(今回だと、「もしユーザーが削除されていたら」を考えつけていればエラーは起こらなかった)
簡単ですが、このあたりが対処法として考えられるかと思います。
私も今後「もし○○だったら」を考えられるよう、日々精進していこうと思いました。
今回の記事が少しでもなにかの参考になれば幸いです!