単体テストの書き方
単体テストとは、関数のような小さな部品を1つだけ取り出して、期待どおりの値を返すかを自動で確かめるテストです。Python には、そのための道具が最初から付いています。
一度書けば、コードを直すたびに数秒で全部を確かめ直せます。手で試す時間が、ほとんどなくなります。
このレッスンのゴール
メモ
アサーションを使って、単体テストを書くことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| アサーション | 期待する値と実際の値を比べる1行 | assert result == 360 |
| 準備、実行、確認 | テストを書く3つの段。英語では Arrange、Act、Assert | |
| テストランナー | テストを探して動かし、結果をまとめるプログラム | |
| unittest | Python に付いている、テストの標準の道具 | |
| 期待値 | 仕様から先に決めた、正しい結果の値 |
学生が入力するセルは、このプレビューでは空白です。
テストは準備、実行、確認の3段
単体テストは、いつも3つの段でできています。
準備で、入力の値を決めます。実行で、調べたい関数を呼びます。確認で、結果が期待した値かを比べます。
確認に使う1行がアサーションです。Python では assert の後ろに、成り立つはずの式を書きます。
3段で書いた最初のテスト
def total_price(price, count):
return price * count
# 準備: 値段と個数を決めます。
price = 120
count = 3
# 実行: 調べたい関数を呼びます。
result = total_price(price, count)
# 確認: 期待する値と比べます。
assert result == 360
print("合格です")
# 360 を 361 に変えて、もう一度実行しましょう。
端末は unittest、課題は assert
このレッスンでは、書き方を2つ使い分けます。
ラボの端末では、Python に付いている unittest を使います。課題の自動採点では、素の assert で書いた def test_...(): の形を使います。
2つは同じ考えで、書き方が違うだけです。
unittest でテストを動かす
unittest のテストは、TestCase を受け継ぐクラスに、名前が test_ で始まるメソッドとして書きます。
self.assertEqual(実際の値, 期待値) が、2つの値を比べます。
ファイル名を test_ で始めて、python3 -m unittest と打つと、テストランナーがすべてを探して動かします。
作るテストファイルの中身
import unittest
from shop import total_price
class TestShop(unittest.TestCase):
def test_three_pens(self):
self.assertEqual(total_price(120, 3), 360)
def test_zero_pens(self):
self.assertEqual(total_price(120, 0), 0)
if __name__ == "__main__":
unittest.main()
作業用のフォルダを作る
mkdir -p ~/testing/unitここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
そのフォルダへ移る
cd ~/testing/unitここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
調べる関数を書く
printf 'def total_price(price, count):\n return price * count\n' > shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストファイルを書く
printf 'import unittest\nfrom shop import total_price\n\n\nclass TestShop(unittest.TestCase):\n def test_three_pens(self):\n self.assertEqual(total_price(120, 3), 360)\n\n def test_zero_pens(self):\n self.assertEqual(total_price(120, 0), 0)\n\n\nif __name__ == "__main__":\n unittest.main()\n' > test_shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストを動かす
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
最後の行が OK なら全部合格
最後の行は OK と出ます。2つのテストが両方とも合格した、という意味です。
実際の画面には、その上に実行にかかった時間も出ます。時間は毎回違うので、ここでは最後の1行だけを見ています。
関数をわざと壊す
printf 'def total_price(price, count):\n return price + count # wrong\n' > shop.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
もう一度動かす
python3 -m unittest 2>&1 | grep AssertionErrorここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
失敗の知らせを読む
出力は、AssertionError: 123 != 360 と AssertionError: 120 != 0 の2行です。
左の数が実際の値、右の数が期待値です。120 と 3 を足して 123 になったと、すぐ分かります。
テストが2つとも落ちたので、最後の行は FAILED (failures=2) になります。
よくある間違い
注意
「関数を動かして出た値を、そのまま期待値にすればよい」と考える人が多くいます。それでは、間違った答えを正解として固めてしまいます。期待値は、仕様から先に決めましょう。