直したバグは戻ってくる
回帰とは、一度直したバグが戻ってくることです。動いていた機能が、変更のあとで動かなくなることも回帰と呼びます。回帰テストは、1つのバグのために書くテストで、そのバグが静かに戻らないようにします。
このレッスンでは、お客様がバグを報告します。先にテストを書き、そのあとで直します。
このレッスンのゴール
メモ
バグの報告を、失敗するテストに変え、バグを直して、テストを残せる。
重要な言葉
| ことば | いみ |
|---|---|
| 回帰 | 直したバグが戻ること。動いていた機能が壊れること |
| 回帰テスト | 報告された1つのバグのために書き、ずっと残すテスト |
| バグ報告 | 利用者が見た間違いと、起こした入力 |
| 赤 | テストが失敗した実行 |
| 緑 | すべてのテストが通った実行 |
| 隣のケース | 報告の入力の隣の入力。修正が、バグを動かしただけではないと確かめる |
学生が入力するセルは、このプレビューでは空白です。
バグ報告をテストにする
店は、10000円以上の注文を1割引きにします。お客様が書きました。「ちょうど10000円使ったのに、割引がありませんでした。」
この報告に入力が入っています。10000円です。まずフォルダーと、欠陥のある今のコードを作りましょう。
レッスン用のフォルダーを作る
mkdir -p ~/regtestここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
フォルダーに入る
cd ~/regtestここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
今の店のコードを書く
printf 'def discount(total_yen):\n if total_yen > 10000:\n return total_yen // 10\n return 0\n' > shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
直す前にテストを書く
お客様の入力そのままで、報告をテストにします。名前は、お客様が見たことにすると、失敗の表示が報告のように読めます。
直す前に実行します。必ず失敗しなければなりません。壊れたコードで通るテストは、何も証明しません。
報告からテストを書く
printf 'import unittest\nfrom shop import discount\n\n\nclass DiscountReport(unittest.TestCase):\n def test_exactly_10000_yen_gets_a_tenth_off(self):\n self.assertEqual(discount(10000), 1000)\n' > test_shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して、失敗を見る
python3 -m unittest test_shop 2>&1 | grep -E "^(OK|FAILED|AssertionError)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストが示す所だけを直す
コードは > と書いていますが、決まりは「以上」です。>= に変えて、他は何も変えません。そのあと、テストをもう一度実行します。緑になるはずです。
大なりの記号を変える
sed -i 's|total_yen > 10000|total_yen >= 10000|' shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストをもう一度実行する
python3 -m unittest test_shop 2>&1 | grep -E "^(OK|FAILED|AssertionError)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
バグの隣のケースを足す
修正は、バグを消さずに動かしただけ、ということがあります。>= 10000 と書くつもりで、うっかり >= 9999 と書くと、9999円の注文まで割引になります。だから、報告の入力の両側の入力も試します。
例: 境界のまわりのケース
| 注文(円) | 受け取る割引 | そのケースがある理由 |
|---|---|---|
| 9999 | 0 | 1円足りない。割引はまだない |
| 10000 | 1000 | 報告: ちょうど境界 |
| 10001 | 1000 | 1円上。整数の円で切り捨て |
| 25000 | 2500 | ずっと上。割引に上限はない |
学生が入力するセルは、このプレビューでは空白です。
隣のテストを2つ足す
printf ' def test_one_yen_under_gets_nothing(self):\n self.assertEqual(discount(9999), 0)\n\n def test_over_the_line_gets_a_tenth(self):\n self.assertEqual(discount(25000), 2500)\n' >> test_shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
3つとも実行する
python3 -m unittest test_shop 2>&1 | grep -E "^(OK|FAILED|AssertionError)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストが修正を守ると確かめる
回帰テストの価値は、バグが戻ったときに出ます。不注意な変更のつもりで、わざとバグを戻し、テストが捕まえるのを見ます。そのあと、修正を戻します。
バグを戻す
sed -i 's|total_yen >= 10000|total_yen > 10000|' shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストを実行する
python3 -m unittest test_shop 2>&1 | grep -E "^(OK|FAILED|AssertionError)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
修正を戻す
sed -i 's|total_yen > 10000|total_yen >= 10000|' shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
もう一度テストを実行する
python3 -m unittest test_shop 2>&1 | grep -E "^(OK|FAILED|AssertionError)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
バグを直してから、通るテストを書いて終わりにする人がいます。そのテストは一度も赤になっていないので、欠陥を捕まえられるかわかりません。先に書き、失敗を見てから、直します。
さらに: 入力のない報告
入力のない報告もあります。「火曜日に合計がおかしかった」のような報告です。テストを書く前に、入力を見つけなければなりません。
再現できる入力を得るために、お客様に聞く質問を3つ挙げましょう。最初に聞くのはどれでしょうか。