動作チェックスクリプト
このレッスンでできるようになること
家計簿の主要な関数が正しく動いているかを、assert で自動的に確かめるスクリプトを書けるようになります。
手で確かめるのは続きません
ここまで家計簿を何度も書き換えてきました。集計を内包表記に直し、保存を JSON にし、辞書を Expense クラスにしました。そのたびに画面に出して目で確かめていたはずです。
この確認のしかたには2つの弱点があります。1つは、直した場所と関係ないところが壊れても気付けないことです。もう1つは、面倒なのでだんだんやらなくなることです。
そこで確認そのものをコードにします。走らせるだけで全部見てくれるなら、書き換えるたびに実行できます。
assert は前提が崩れたら止まる
第5章で出てきた assert を思い出します。
Python
assert sum([12, 8, 5]) == 25, "歩いた距離が合いません"条件が真なら何も起きません。偽なら AssertionError が投げられ、後ろのメッセージが添えられます。「ここは必ずこうなっているはず」を1行で書ける道具です。
落ちて終わらせずに、結果を集める
assert をそのまま並べると、最初の1つが失敗した時点でプログラムが止まります。1件目が壊れているせいで、2件目以降が通るかどうか分からないまま終わってしまいます。
そこで AssertionError を捕まえて、次のチェックに進みます。
Python
def check(label, body):
try:
body()
except AssertionError as e:
print(f"NG {label} {e}")
return False
print(f"OK {label}")
return Truebody は関数そのものを受け取ります。第4章でやった「関数は値として渡せる」がここで効いています。呼び出しの結果ではなく、呼び出す前の関数を渡すのがポイントです。渡すときは check("合計金額", check_total) のように、括弧を付けずに名前だけを書きます。
try が最後まで通ったときだけ print("OK ...") に進むので、else を使わずにこの形でも書けます。finally を足せば、成功でも失敗でも最後にやりたい後始末を書けます。
何をチェックするか
チェックは「よくある入力」だけでは足りません。次の3種類をそろえておくと、たいていの崩れを拾えます。
- ふつうの入力 — 4件の支出の合計が想定どおりか
- 端の入力 — 空のリストを渡しても落ちずに0を返すか
- 境目の入力 — 1000円以上を高額とするなら、ちょうど1000円が入るか
最後に「何件中何件が通ったか」を出しておくと、目で1行ずつ追わなくても状態が分かります。ここまで全部、第4章と第5章の道具だけで書けています。
期待する値を入力から決める
演習では集計関数を作り直さず、結果を確かめる assert だけを書きます。assert False は未完成の印で、ひな形を動かすと NG が表示されます。4つのチェック関数の6行を、期待値と比較する条件に置き換えます。
Counter は辞書と比較できます。次は散歩した距離の集計を確かめる例です。
Python
from collections import Counter
walking = Counter({"朝": 3, "夕": 2})
assert walking == {"朝": 3, "夕": 2}, "時間帯別の距離が違います"
assert len(Counter()) == 0, "空の集計ではありません"リストの比較では要素の順序も確かめられます。境目の確認には、条件を満たさない直前の値と、境目ちょうどの値、直後の値を用意します。
Python
durations = [29, 30, 31]
long_walks = [minutes for minutes in durations if minutes >= 30]
assert long_walks == [30, 31], "30分ちょうどが含まれていません"演習には1000円の前後のデータを用意してあります。通常の抽出と境目の抽出を別々に確かめましょう。1つのチェック関数に2つの assert があっても、結果はその関数について1行表示されます。
要件
- check_total で total_amount(EXPENSES) が 6130 になることを1行で確かめる
- check_category で用意済みの totals が 食費4050、交通費480、教養1600 の辞書と等しいことを1行で確かめる
- check_empty で total_amount([]) が0になることと、category_totals([]) の要素数が0になることを2行で確かめる
- check_expensive で expensive(EXPENSES, 1000) が スーパー、技術書 の順のリストになることを1行で確かめる
- check_expensive で expensive(boundary, 1000) が 境目、直後 の順のリストになることを1行で確かめる。1000円ちょうどを含み、999円は含まない
- assert の失敗メッセージは自由に書いてよい。完成済みの関数と実行部分はそのまま使う
- 完成時は 合計金額 / カテゴリ別集計 / 空のリスト / 高額の抽出 の順で4行の OK を表示し、最後に 4件中4件OKでした と表示する