テスト駆動開発の進め方
テスト駆動開発(TDD)とは、コードを書く前に、失敗するテストを先に書く開発のやり方です。赤、緑、整理の3つの段を、小さく何度も回します。
いつも動くテストが手元にあるので、コードを変えても怖くありません。書いた直後にバグが見つかるので、直す手間も小さく済みます。
このレッスンのゴール
メモ
失敗するテストを先に書き、通るコードを書き、整理することができる。
キーワード
| ことば | いみ |
|---|---|
| テスト駆動開発 | テストを先に書いて、コードを少しずつ育てるやり方。TDD とも呼ぶ |
| 赤(レッド) | 書いたばかりのテストが、失敗している状態 |
| 緑(グリーン) | すべてのテストが通っている状態 |
| リファクタリング | 動きを変えずに、コードを読みやすく整えること |
| 回帰 | 直したはずの所や、動いていた所が、また壊れること |
学生が入力するセルは、このプレビューでは空白です。
赤、緑、整理を小さく回す
1回目は赤です。まだない機能のテストを1つだけ書き、失敗するのを確かめます。
2回目は緑です。そのテストが通る、いちばん簡単なコードを書きます。
3回目は整理です。テストが通ったまま、コードを読みやすくします。そして、次のテストへ進みます。
時間の文字列を分に直す関数を育てる
to_minutes("45m") が 45 を、to_minutes("2h") が 120 を返す関数を、ラボで育てます。
動かす結果は、順に 4 回出ます。最初は FAILED (failures=1)、次に OK、また FAILED (failures=1)、最後に OK です。赤、緑、赤、緑の4つが、上から順に出れば成功です。
作業用のフォルダを作る
mkdir -p ~/testing/tddここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
そのフォルダへ移る
cd ~/testing/tddここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
中身のない関数を置く
printf 'def to_minutes(text):\n return 0\n' > duration.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
最初のテストを書く
printf 'import unittest\nfrom duration import to_minutes\n\n\nclass TestDuration(unittest.TestCase):\n def test_minutes_only(self):\n self.assertEqual(to_minutes("45m"), 45)\n' > test_duration.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
赤: 失敗を確かめる
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
緑: 通る最小のコード
printf 'def to_minutes(text):\n return int(text[:-1])\n' > duration.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
緑: 通ったか確かめる
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
次のテストを足す
printf '\n def test_hours_only(self):\n self.assertEqual(to_minutes("2h"), 120)\n' >> test_duration.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
赤: また失敗する
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
緑: 時間も分も読む
printf 'def to_minutes(text):\n number = int(text[:-1])\n if text[-1] == "h":\n return number * 60\n return number\n' > duration.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
緑: 両方通る
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
2回目の赤が教えたこと
1回目の緑のコードは、int(text[:-1]) だけでした。45 のテストには十分でした。
次のテストを足すと、2 が返って、期待の 120 と違うので赤になりました。テストが、足りない所を教えてくれたのです。
4回目で緑に戻り、2つのテストが両方とも通っています。
よくある間違い
注意
「テストは、コードを書いたあとに書けばよい」と考える人が多くいます。先に失敗を見ていないと、そのテストが本当に働くのか分かりません。最初から通るテストは、間違いがあっても通ってしまうかもしれません。
整理はテストが守ってくれる
リファクタリングは、動きを変えずにコードを整えることです。数に名前を付けるのも、その1つです。
テストが全部通っていれば、整理のあとも通る限り、動きは変わっていません。コードを直して前の機能が壊れることを回帰と呼び、テストはそれを見張ります。
整理の前と後を比べる
TAX_PERCENT = 10
# 整理の前: 10 という数が、式に埋まっています。
def with_tax_before(price):
return price + price * 10 // 100
# 整理のあと: 名前を付けたので、意味が読めます。
def with_tax_after(price):
return price + price * TAX_PERCENT // 100
# 整理の前と後で、答えが同じかを確かめます。
for price in [0, 500, 1000, 1980]:
print(price, with_tax_before(price) == with_tax_after(price))
# TAX_PERCENT を 8 に変えると、前と後の答えが食い違います。理由を考えましょう。