プログラムテストの本について
プログラムのテストについて書かれている本(でタイトルには「テスト」と入っていないもの)5冊
- 『プログラミング作法』 第6章 テスト
- 『コードコンプリート(下)』 22章 デベロッパーテスト
- 『達人プログラマー』 34. テストしやすいコード、43. 容赦ないテスト
- 『珠玉のプログラミング』 コラム4 正しいプログラムを書く、コラム5 あと少しのこと
- 『ビューティフルコード』 7章 ビューティフル・テスト
『プログラミング作法』は具体的なテストのやり方がいろいろ書かれていて面白い。例えば
多くのプログラムでは、入力に含まれる何らかの性質が残される。wc(行数と語数と文字数をカウント)やsum(チェックサムの計算)などのツールを使えば、出力のサイズが同じかどうか、語数が同じかどうか、順序は変わっても同一のデータバイトが含まれているかどうかなどといった点を検証できる。
[中略]
バイトデータ出現頻度検査プログラムがあれば、データがきちんと保存されているかどうかチェックできるし、テキストしか含まれていないはずのファイルに非テキスト文字が存在するなどといった異常を見つけるのにも重宝する。
(『プログラミング作法』p.203)
場合によっては1つの答えを別々の2種類の方法によって計算できるケースがあるし、別の簡単なバージョンのプログラムを書いて、それが低速だが独立した比較対象として利用する手もある。
(『プログラミング作法』p.204)
回帰テストというのは、何か新しいバージョンを直前のバージョンと比較するテストシーケンスを実行することを指す。[中略]
このスクリプトは、さまざまな大量のテストデータファイルについて古いバージョン(old_ka)と新しいバージョン(new_ka)を実行し、出力が同一にならなかったデータファイルそれぞれについて文句を言う。[中略]for i in ka_data.* do old_ka $i > out1 new_ka $i > out2 if ! cmp -s out1 out2 then echo $i: BAD fi done(『プログラミング作法』pp.206-207)
入力と期待される出力を自分で提供する自給自足テストは、回帰テストを補完するテストとなる。
(『プログラミング作法』p.207)
今は、ここでいう自給自足テストが回帰テストと呼ばれることが多いような。
『コードコンプリート』は35章あるうちの1章だけどそれでも40ページ以上ある。
『達人プログラマー』にはテストを書いたり実行したりする上で必要な考え方やテストの仕方が書かれている。
「常に実用的なものになるとは限りませんが、各クラスやモジュールに自分自身の単体テストを組み込んでおくというのも良い考えです。
(『達人プログラマー』p.197)
こういったテストすべてを実行するためのデータはどうやって用意したらよいのでしょうか? データには実世界のデータと作り込んだデータの2種類しかありません。そしてこういったデータは、それぞれが異なった性質を持っているが故に異なったバグを検出することができるため、テストにはどちらも欠かせないものとなるのです。
(『達人プログラマー』p.246)
バグ発見時以降、どのような場合であっても例外なく、どんなものであっても、どれだけ開発者が「いや、こんな事はもう二度と起こりません」と訴えたとしても、自動化されたテストのチェック内容を修正して、その特定のバグを検出できるようにするべきなのです。
(『達人プログラマー』p.251)
『珠玉のプログラミング』 は、テストの仕方よりも表明を使って正しいプログラムを書くことに重点があるかもしれない。そういう点では『ライティングソリッドコード』2章 ASSERTと併読しても良いかも。ただassert(表明)という言葉が、Cのassertやjavaのassertのようなプログラム中に埋め込んで使う機構に対してでなく、ユニットテスト用の関数・メソッドの名前に使われているのを見ると、古典的な表明手法はあまり使われなくなっているのだろうかと思う。
『ビューティフルコード』7章 ビューティフル・テストは、『珠玉のプログラミング』でも扱われている二分探索プログラムをきちんとテストするにはどうすればいいかという内容。テスト対象がたったひとつでもどれだけのことが必要かという実例。
こんなテストの本が読んでみたい
ランダムデータを使ったテストについて
『xUnit Test Patterns』(未読)では、ランダムにデータを生成するテストは避けるべきといっているみたい。
テストカバレッジが増えるという理由でランダム値を使うのは良い考えに見えるかもしれない。残念ながらこのやり方は、テストカバレッジが把握しにくくなるしテストが繰り返しにくくなる。
(Chapter 16 Behavior Smell)
一方でランダムデータを使ったテストが有用だという話も見かける。『プログラミング作法』P.213とか『ビューティフルコード』p.100とか。『言語設計者たちが考えること』の中でバートランド・メイヤーもランダムにデータを生成して自動テストする話をしていた。ちょっと違うかもしれないけど、ちらっと見た『ビューティフルテスティング』の乱数ジェネレータのテストについての章も統計的テスト(繰り返しテストすると失敗する)の有用性を扱っているはず(原文はPDFで読めるみたい「Testing a Random Number Generator」)。
で結局のところどう考えれば良いのか。
- ○人間が書くよりもたくさんの入力をテストできる。
- ○人間が入力データを作ると入力データは単純なものになりがち(データの大きさや複雑さがある程度以上ないと発現しないバグの発見)。
- ×バグが発生しやすいデータ(境界値付近のデータや全てが同じ値といった特殊な組み合わせのデータ)についてのテスト
- ×テストが失敗したときにその失敗を再現できないかもしれない(対処:失敗したら自動でテストデータに追加されるとか)。
- 乱数のseedを固定することの有利・不利。固定ならテストの再現がしやすい。
説明用の例題
テストのやり方の説明に使う例題は
- プログラム本体を書くより、テストが簡単な問題(単純な比較関数、述語関数でテストの判定ができる問題)
- 外部リソース(ファイル、ネットワークなど)を扱わない問題
が多いような気がする。簡単な問題の方が説明のためには良いとか外部リソースを扱わない方が言語や環境にあまり依存しない説明ができるとか理由はあるのだろうけど、そうじゃない例題がもっとあっても良いように思う。
実際に例題にふさわしいか判らないけど考えてみる。
- ファイルを適当な場所(ゴミ箱)に移して、あとで復活させられる機能の実装(外部リソースを扱う例)。
- ディレクトリをタイムスタンプを保存したままコピーする(外部リソースを扱う例2。タイムスタンプを保存するは、ただのコピーだとglobのある言語では簡単過ぎる気がするので)。
- 安定なソート(目的のプログラムよりテストの方が難しそうな例。プログラム自体はバブルソートを書くだけ)。
- 単純なHTMLを簡易作成する機能。例えばRubyのCGIモジュールとかGaucheのhtml-liteのようなもの(プログラムよりテストが難しそうな例2。別に生成するのはHTMLでなくても構わない)。
- ライブラリのおかげで機能実装は難しくないけど意図どおり動作しているかのテストはそれほど簡単ではない問題(ネットワークプログラムとか)。
- Wikiエンジン、検索エンジン、トランスレータ、インタプリタ、コンパイラの作成。
- 浮動小数点を使う必要がある(けど数学的な知識はあまりいらない)問題。
- 実際のソフトウェアでテストをすり抜けてたバグやテストが書かれていなかった事例。『プログラム書法』が実際の本に載っていた下手なプログラムを使っていたように、正しくテストが書かれなかった事例を題材に使う。どういう事柄がテストをすり抜けやすいかのパターン収集にもなる。フリーソフトのChangeLogから採取できるかもしれない。
どうテストを書けばいいのかよく判らないような問題も提示されているといいかもしれない。例えば、パズルを解く問題のように事前に解が判らない問題(例えばモンテカルロ法を使って100クイーン問題の解の総数を推定したら2.6×10117ぐらいになったけど、このプログラムの正しさをいかに確かめるか)とか、ヒューリスティックに近似的な分類・推定をしたり探索したりする問題とか、スペル修正プログラムとか。