同値分割と境界値分析
同値分割と境界値分析とは、限られた数のテストケースで、できるだけ多くの欠陥を見つけるための選び方です。入力を仲間に分け、仲間の境目を調べます。
バスの運賃は、12歳未満が100円、12歳以上が200円とします。年齢は何百通りもあります。全部を試さずに、どの年齢を試せばよいでしょうか。
このレッスンのゴール
メモ
同値分割と境界値で、テストケースを選ぶことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 同値クラス | プログラムが同じように扱う入力の仲間 | 0から11歳 |
| 同値分割 | 入力を同値クラスに分け、各クラスから代表を選ぶこと | |
| 境界値 | クラスの境目にある値と、その両隣の値 | 11と12 |
| テストケース表 | 入力と期待する結果を、1行ずつ並べた表 | |
| 総当たり | 考えられる入力をすべて試すこと |
学生が入力するセルは、このプレビューでは空白です。
全部は試せないから仲間に分ける
1つの数を入れる関数でも、試せる数は無数にあります。総当たりは、できません。
そこで、プログラムが同じように扱う入力を1つの同値クラスにまとめます。クラスの中のどの値でも、結果の種類は同じはずです。
クラスごとに代表を1つ選んで試すのが、同値分割です。
例: 運賃の同値クラス
| クラス | 年齢の範囲 | 代表の例 | 期待する運賃 |
|---|---|---|---|
| 子ども | 0から11歳 | 5歳 | 100円 |
| おとな | 12歳以上 | 30歳 | 200円 |
学生が入力するセルは、このプレビューでは空白です。
代表の年齢で試す
def fare(age):
if age < 12:
return 100
return 200
# 仲間ごとの代表の年齢を1つずつ選びました。
for age in [5, 30]:
print(age, fare(age))
# 代表を 8 と 40 に変えても、同じ答えになるか確かめましょう。
バグは境目に集まる
「12歳未満」を書くときは、< と <= を間違えやすいものです。1つ違うだけで、12歳の運賃が変わります。
このような「1つずれる」間違いは、クラスの境目で起きます。だから、境目の値と、その両隣を必ず試します。これが境界値分析です。
例: 注文数が1から10個の境界値
| 試す値 | 位置 | 期待する結果 |
|---|---|---|
| 0 | 下の境目のすぐ外 | 受け付けない |
| 1 | 下の境目 | 受け付ける |
| 2 | 下の境目のすぐ内 | 受け付ける |
| 9 | 上の境目のすぐ内 | 受け付ける |
| 10 | 上の境目 | 受け付ける |
| 11 | 上の境目のすぐ外 | 受け付けない |
学生が入力するセルは、このプレビューでは空白です。
作業用のフォルダを作る
mkdir -p ~/testing/boundaryここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
そのフォルダへ移る
cd ~/testing/boundaryここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
境目を間違えた関数を書く
printf 'def fare(age):\n if age <= 12:\n return 100\n return 200\n' > fare.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
4つのケースを書く
printf 'import unittest\nfrom fare import fare\n\n\nclass TestFare(unittest.TestCase):\n def test_child(self):\n self.assertEqual(fare(5), 100)\n\n def test_eleven(self):\n self.assertEqual(fare(11), 100)\n\n def test_twelve(self):\n self.assertEqual(fare(12), 200)\n\n def test_adult(self):\n self.assertEqual(fare(30), 200)\n' > test_fare.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
テストを動かす
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
落ちたのは境目の12歳だけ
最後の行は FAILED (failures=1) です。4つのうち、落ちたのは1つだけです。
代表の5歳と30歳は合格しました。落ちたのは、境目の12歳です。次のコマンドで、理由が読めます。
落ちた理由を見る
python3 -m unittest 2>&1 | grep AssertionErrorここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
100 != 200 は何を言うか
出力は AssertionError: 100 != 200 の1行です。左が実際の運賃、右が期待した運賃です。
12歳に100円が返りました。<= のせいで、12歳が子どもに入っていたからです。真ん中の値だけでは、見つかりませんでした。
`<` に直す
printf 'def fare(age):\n if age < 12:\n return 100\n return 200\n' > fare.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
もう一度動かす
python3 -m unittest 2>&1 | tail -n 1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
「真ん中の値を試せば、十分だ」と考える人が多くいます。バグは境目に集まります。5歳と30歳は合格しても、12歳で落ちることがあります。境目と両隣を、必ず入れましょう。
ケースは表にして残す
テストケースは、入力と期待する結果を並べた表にまとめます。表の1行が、テスト1つになります。
表を見れば、何を試して、何をまだ試していないかが一目で分かります。次のコードは、表を1行ずつ読んで、合格か不合格かを出します。
表を読んで試すコード
def fare(age):
if age < 12:
return 100
return 200
# テストケース表: 年齢と、期待する運賃の組です。
cases = [[0, 100], [11, 100], [12, 200], [13, 200], [65, 200]]
for case in cases:
age = case[0]
expected = case[1]
actual = fare(age)
if actual == expected:
print(age, "合格")
else:
print(age, "不合格", actual)
# 表に [10, 100] を足して、もう一度実行しましょう。