3,000件近くのテストを作成しました。データはハードコーディングされており、コードの再利用はほとんどありません。この方法論は、私たちを尻に刺し始めました。システムが変更されると、壊れたテストの修正により多くの時間を費やすことになります。ユニット、統合、機能テストがあります。
私が探しているのは、管理しやすく保守可能なテストを書くための決定的な方法です。
フレームワーク
3,000件近くのテストを作成しました。データはハードコーディングされており、コードの再利用はほとんどありません。この方法論は、私たちを尻に刺し始めました。システムが変更されると、壊れたテストの修正により多くの時間を費やすことになります。ユニット、統合、機能テストがあります。
私が探しているのは、管理しやすく保守可能なテストを書くための決定的な方法です。
フレームワーク
回答:
それらはそうではないので、「壊れた単体テスト」と考えないでください。
これらは仕様であり、プログラムでサポートされなくなりました。
「テストの修正」ではなく、「新しい要件の定義」と考えてください。
テストでは、最初にアプリケーションを指定する必要があり、その逆ではありません。
動作することがわかっているまで、動作する実装があるとは言えません。テストするまで機能するとは言えません。
あなたを導くかもしれないいくつかの他のメモ:
Don't think of it as "fixing the tests", but as "defining new requirements".
あなたが説明することは実際にはそれほど悪いことではないかもしれませんが、あなたのテストが発見するより深い問題へのポインタ
システムが変更されると、壊れたテストの修正により多くの時間を費やすことになります。ユニット、統合、機能テストがあります。
コードを変更でき、テストが中断しない場合、それは私にとって疑わしいことです。正当な変更とバグの違いは、それが要求されているという事実だけであり、要求されたものはテストによって定義されます(TDDを想定)。
データはハードコーディングされています。
テストでハードコードされたデータは良いものです。テストは、証明としてではなく、改ざんとして機能します。計算が多すぎる場合、テストはトートロジーになります。例えば:
assert sum([1,2,3]) == 6
assert sum([1,2,3]) == 1 + 2 + 3
assert sum([1,2,3]) == reduce(operator.add, [1,2,3])
抽象化が高ければ高いほど、アルゴリズムに近づき、それにより、実際の実装とそれ自体を比較することに近づきます。
コードの再利用はほとんどありません
assertThat
テストを簡単に保つため、テストでのコードの最適な再利用はjUnitsのように「チェック」です。それに加えて、コードを共有するためにテストをリファクタリングできる場合、テストされる実際のコードも多すぎる可能性があるため、リファクタリングされたベースをテストするものにテストを減らします。
私もこの問題を抱えています。私の改善されたアプローチは次のとおりです。
単体テストが何かをテストする唯一の良い方法でない限り、単体テストを書かないでください。
単体テストの診断と修正にかかる時間のコストが最も低いことを認める用意ができています。これは彼らを貴重なツールにします。問題は、明らかなようにマイレージが変わる可能性があることですが、単体テストはコード量を維持するコストに見合わないことが多いということです。一番下に例を書いて、見てみましょう。
アサーションは、そのコンポーネントの単体テストと同等の場所で使用してください。アサーションには、デバッグビルド全体で常に検証されるという素晴らしい特性があります。したがって、テストの個別のユニットで「従業員」クラスの制約をテストする代わりに、システム内のすべてのテストケースを通じて従業員クラスを効果的にテストしています。アサーションには、ユニットテスト(最終的にはscaffolding / mocking / whateverが必要)ほどコード量を増加させないという優れた特性もあります。
誰かが私を殺す前に:本番ビルドはアサーションでクラッシュするべきではありません。代わりに、「エラー」レベルでログを記録する必要があります。
まだ考えていない人への注意として、ユーザーやネットワークの入力について何も主張しないでください。それは大きな間違いです™。
私の最新のコードベースでは、アサーションの明らかな機会が見られる場合はいつでも、ユニットテストを慎重に削除しています。これにより、全体的なメンテナンスのコストが大幅に削減され、私はずっと幸せになりました。
システム/統合テストを優先し、すべての主要なフローとユーザーエクスペリエンスに対してテストを実装します。コーナーケースはおそらくここにある必要はありません。システムテストでは、すべてのコンポーネントを実行して、ユーザー側での動作を検証します。そのため、システムテストは必然的に遅くなるので、重要なものを(それ以上でもそれ以下でも)書くと、最も重要な問題をキャッチできます。システムテストのメンテナンスオーバーヘッドは非常に低くなっています。
アサーションを使用しているため、各システムテストは同時に数百の「ユニットテスト」を実行することを覚えておくことが重要です。また、最も重要なものが複数回実行されることをかなり確信しています。
機能的にテストできる強力なAPIを作成します。APIが機能するコンポーネントを単独で検証することを難しくしている場合、機能テストは厄介であり(実際に直面して)意味がありません。優れたAPI設計により、a)テスト手順が簡単になり、b)明確で価値のある主張が生まれます。
機能テストは、特にプロセスの障壁を越えて1対多またはさらに悪いことに多対多で通信するコンポーネントがある場合に、正しく実行するのが最も難しいものです。単一のコンポーネントに接続されている入力と出力が多いほど、機能テストは難しくなります。実際にその機能をテストするには、そのうちの1つを分離する必要があるためです。
「単体テストを作成しない」という問題について、例を示します。
TEST(exception_thrown_on_null)
{
InternalDataStructureType sink;
ASSERT_THROWS(sink.consumeFrom(NULL), std::logic_error);
try {
sink.consumeFrom(NULL);
} catch (const std::logic_error& e) {
ASSERT(e.what() == "You must not pass NULL as a parameter!");
}
}
このテストの作成者は、最終製品の検証にまったく寄与しない7行を追加しました。a)誰もそこにNULLを渡してはならない(そのためアサーションを書く)か、b)NULLケースがいくつかの異なる振る舞いを引き起こすため、ユーザーはこれが起こることを決して見ないはずです。ケースが(b)の場合、実際にその動作を検証するテストを作成します。
私の哲学は、実装成果物をテストするべきではないというものになりました。テストするのは、実際の出力とみなせるもののみです。そうでなければ、単体テスト(特定の実装を強制する)と実装自体の間に2倍の基本的なコードを書くことを避ける方法はありません。
ここで、単体テストに適した候補があることに注意することが重要です。実際、単体テストが、何かを検証するための唯一の適切な手段であり、それらのテストを記述して維持することが非常に価値があるいくつかの状況ですらあります。私の頭の中で、このリストには、自明でないアルゴリズム、APIで公開されたデータコンテナー、および「複雑」に見える高度に最適化されたコード(別名「次の男がおそらくそれを台無しにする」)が含まれています。
あなたへの私の具体的なアドバイスは、次のとおりです。ユニットテストが壊れたら慎重に削除を開始し、「これは出力なのか、コードを無駄にしているのか」という質問を自問します。あなたはおそらくあなたの時間を無駄にしているものの数を減らすことに成功するでしょう。
あなたのユニットテストは魅力のように動作するように思えます。それは全体的な点の一種であるため、変更に対して非常に脆弱であることは良いことです。コードブレークテストの小さな変更により、プログラム全体でエラーの可能性を排除できます。
ただし、メソッドが失敗したり、予期しない結果をもたらすような条件についてのみテストする必要があることに注意してください。これにより、些細なことではなく真の問題がある場合、ユニットテストが「壊れる」傾向が高くなります。
あなたはプログラムを大きく再設計しているように思えますが。そのような場合は、必要なことをすべて行い、古いテストを削除して、後で新しいテストに置き換えます。ユニットテストの修復は、プログラムの根本的な変更のために修正しない場合にのみ価値があります。そうしないと、プログラムコードの新しく記述されたセクションに適用するには、テストの書き換えに専念しすぎていることに気付く場合があります。
私は他の人がもっと多くのインプットを持っていると確信していますが、私の経験では、これらはあなたを助ける重要なものです:
Gerard MeszarosのXUnitテストパターンを必ず見てください。テストコードを再利用し、重複を避けるための多くのレシピを含む素晴らしいセクションがあります。
テストが脆弱な場合は、ダブルをテストするのに十分な手段がないことも考えられます。特に、各ユニットテストの開始時にオブジェクトのグラフ全体を再作成すると、テストのArrangeセクションが大きくなり、かなりの数のテストでArrangeセクションを書き直さなければならない状況に陥ることがよくあります。最もよく使用されるクラスの1つが変更されました。モックとスタブは、関連するテストコンテキストを得るためにリハイドレートする必要があるオブジェクトの数を減らすことで、ここで役立ちます。
モックとスタブを使用してテストセットアップから重要でない詳細を取り出し、テストパターンを適用してコードを再利用すると、脆弱性が大幅に減少します。