40秒でいうと: 数理最適化の実験を管理するソフトウェアを、AIを使いながら開発しています。このソフトウェアには、実験を始める前の状態を確認し、その結果を記録・報告する機能があります。
実験前の状態はいったんデータとして保存され、そこから必要な情報を取り出して報告を作る仕組みです。ところが、報告に載せる項目の一覧に漏れがあり、実験前の状態を確認するための重要な3項目が報告に含まれていませんでした。
これでは、報告を受け取った側は、実験の試行枠が消費されていないか、実行用の状態が作られていないかを確認できません。本来確認すべき条件が満たされているのか、報告だけでは判断できない状態になっていたのです。
しかも、この欠落を見つけるはずのソフトウェアの自動テストも通っていました。 報告を作るプログラムとテストが、同じ不完全な項目一覧を基準にしていたためです。
この記事で伝えたいのは、テストが通ったかどうかだけでなく、そのテストが何を「正しい」と判断しているのかを確かめることの大切さです。
元データにはあるのに、報告にはない
開発しているのは、工場スケジューリングの数理最適化をAIで支援・検証する個人研究用ツールです。実験を誤って次の段階へ進めないよう、実行前の準備状態を記録し、後続の結果報告へ引き継ぎます。
ある修復作業で、準備データには存在する次の3項目が、報告用データから抜けていると分かりました。
| 実行前に確認する状態 | 準備データ | 修復前の報告 |
|---|---|---|
| 事前調整(校正)の試行枠を消費した回数 | 0 |
項目なし |
| 試行用の作業領域が作られているか | false |
項目なし |
| 試行用の実行状態が作られているか | false |
項目なし |
ここでいう校正は本実験前の調整作業です。また、これは工場設備の安全性ではなく、研究用の実験を意図せず進めないための状態確認です。
3項目は元データの生成時点では保存されていました。欠けていたのは、その元データから報告用の項目を取り出す処理でした。
なぜテストは見逃したのか
修復前のテストには、実際に次の確認がありました。
self.assertEqual(
set(projection),
set(oh.PREPARATION_LOCAL_RESULT_FIELDS),
)
projection は報告用に取り出した項目、PREPARATION_LOCAL_RESULT_FIELDS は出力すべき項目の一覧です。
一見、必要な項目に漏れがないかを確かめています。しかし、報告を作る実装も、同じ項目一覧に従っていました。
一覧そのものに3項目が載っていなければ、報告からも消えます。そしてテストでも「一覧と一致している」と判定されます。
このテストが確認していたのは、実装が選んだ項目と出力が一致すること。元の準備データにある必須項目を、漏れなく引き継いだことまでは確かめられていませんでした。
どう直したか
修復では報告処理とテストの2ファイルだけを変更し、3項目を報告へ引き継いで値を確認するようにしました。
さらに、テスト側でこの3項目と期待値を明示的に指定しました。項目をわざと消す、値を変える、重複させる、型を間違える、といった場合に拒否することも確認しています。不正な状態なら、実行管理上の予約や結果の自動記録に進む前に停止します。
既存の一覧との一致チェック自体が無意味だったわけではありません。ただし、一覧の正しさまで同じ一覧で保証することはできなかったのです。
次から確認する3つのこと
- 期待する項目の根拠は何か。 実装の一覧だけでなく、元データや仕様と照らしたか。
- 項目を消すとテストが落ちるか。 正常な入力で通ることだけを確認していないか。
- 最後の受け渡しまで届くか。 元データ、変換後の報告、外部への記録前までを確認したか。
「テストが通る」は大切です。ただし、何を正解としてテストしたかを別に確認しないと、実装とテストが同じ漏れを共有してしまいます。テストの期待値を実装から独立して考えること自体は、目新しい話ではありません。それでも、自分の開発で実際に見逃した経験として、記録しておきたいと思いました。