不変テストはユニットテストを置き換えることができますか?
-
12-09-2019 - |
質問
プログラマーとして、私はTDDの哲学に心から購入し、書いた非自明のコードの広範な単体テストを作成する努力をしました。時々、この道は痛みを伴う可能性があります(複数のユニットテストの変更を引き起こす行動の変化、大量の足場が必要です)が、全体として、私はすべての変更後に実行できるテストなしでプログラムを拒否し、私のコードはバギーがはるかに少ないです結果。
最近、私はHaskellで遊んでおり、それは居住者のテストライブラリ、QuickCheckです。 TDDとは明らかに異なる方法では、QuickCheckはコードの不変性、つまり、入力のすべて(または実質的なサブセット)を保持する特定のプロパティに重点を置いています。簡単な例:安定した並べ替えアルゴリズムは、2回実行すると同じ答えを与える必要があります。出力が増加し、入力の順列などが必要です。その後、QuickCheckはこれらの不変剤をテストするためにさまざまなランダムデータを生成します。
少なくとも純粋な機能(つまり、副作用のない機能 - そして正しくock笑すると、汚れた関数を純粋な機能に変換できる場合)の場合、その不変テストは、それらの機能の厳格なスーパーセットとして単位テストに取って代わる可能性があります。各ユニットテストは、入力と出力で構成されています(命令的なプログラミング言語では、「出力」は関数のリターンだけでなく、変更された状態でもありますが、これをカプセル化できます)。おそらく、手動で作成したすべてのユニットテスト入力をカバーするのに十分なランダム入力ジェネレーターを作成することができます(そして、それはあなたが考えていなかったケースを生成するからです)。境界条件のためにプログラムにバグが見つかった場合、ランダム入力ジェネレーターを改善して、そのケースも生成します。
課題は、すべての問題に対して有用な不変剤を策定することが可能かどうかです。そもそも答えを計算するよりも正しいかどうかを確認する回答があると、それははるかに簡単です。不変剤について考えることは、アドホックテストケースよりもはるかに優れた複雑なアルゴリズムの仕様を明確にするのにも役立ちます。プログラムの以前のバージョンをモデル実装として使用するか、別の言語でのプログラムのバージョンを使用できます。など、最終的には、入力または出力を明示的にコーディングすることなく、以前のテストケースのすべてをカバーできます。
私は非常識になったのですか、それとも私は何かに取り組んでいますか?
解決
1年後、私は今、この質問に対する答えがあると思います: いいえ! 特に、ユニットテストは常に必要かつ有用であり、回帰テストにはバグレポートにテストが添付され、そのバグが戻ってこないようにコードベースに存在します。
ただし、入力がランダムに生成されたテストには、単位テストを置き換えることができると思います。命令コードの場合でも、「入力」は、必要な命令声明の順序です。もちろん、ランダムデータジェネレーターを作成する価値があるかどうか、そしてランダムデータジェネレーターに適切な分布を持つことができるかどうかは別の質問です。単体テストは、ランダムジェネレーターが常に同じ結果を与える縮退したケースです。
他のヒント
あなたが育てたのは、機能的なプログラミングにのみ適用される場合、非常に良い点です。命令的なコードでこれをすべて達成する手段を述べましたが、なぜそれが終わっていないのかについても触れました - それは特に簡単ではありません。
それがユニットテストに置き換えられないまさにその理由だと思います。それは、命令的なコードに簡単に適合しません。
疑わしい
これらの種類のテストについて聞いたことがありますが(使用していません)、2つの潜在的な問題が見られます。それぞれについてコメントが欲しいです。
誤解を招く結果
次のようなテストについて聞いたことがあります:
reverse(reverse(list))
等しいはずですlist
unzip(zip(data))
等しいはずですdata
これらが幅広い入力に当てはまることを知っておくのは素晴らしいことです。 ただし、関数が入力を返すだけで、これらのテストは両方とも渡されます。
あなたはそれを確認したいと思うように思えます。 reverse([1 2 3])
平等です [3 2 1]
少なくとも1つのケースで正しい動作を証明するために、 追加 ランダムデータを使用した一部のテスト。
複雑さをテストします
入力と出力の関係を完全に記述する不変テストは、関数自体よりも複雑になる可能性があります。複雑な場合は、バギーかもしれませんが、テストのテストはありません。
対照的に、優れたユニットテストは、読者として台無しにしたり誤解したりするには簡単すぎます。タイプミスだけがバグを作成することができます。 reverse([1 2 3])
平等に [3 2 1]
".
元の投稿で書いたものは、この問題を思い出させてくれました。これは、ループが正しいことを証明するために、ループの不変が何であるかについての未解決の質問です...
とにかく、私はあなたが正式な仕様でどれだけ読んだかはわかりませんが、あなたはその考えの線を下っています。 David Griesの本は、このテーマに関する古典の1つです。私はまだ、日々のプログラミングで迅速に使用するのに十分なコンセプトを習得していません。正式な仕様に対する通常の対応は、その困難で複雑であり、安全性の重要なシステムに取り組んでいる場合にのみ努力する価値があります。しかし、QuickCheckが使用できるものと同様のエンベロープテクニックの裏にあると思います。