優れた単体テストの条件とは?[閉まっている]
質問
皆さんのほとんどは多くの自動テストを作成しており、単体テストの際によくある落とし穴に遭遇したことがあると思います。
私の質問は、将来の問題を避けるためにテストを書く際の行動規則に従っていますか?もう少し詳しく言うと:とは何ですか 優れた単体テストの特徴 あるいはテストはどうやって書くのですか?
言語に依存しない提案を歓迎します。
解決
ソースを接続することから始めましょう - JUnit を使用した Java でのプラグマティックな単体テスト (C#-Nunit を使用したバージョンもあります。でも私はこれを持っています..ほとんどの場合、不可知論的です。推奨。)
良いテストとは次のようなものでなければなりません 旅行 (この頭字語は十分に粘着力がありません。本に載っていたカンニングペーパーのプリントアウトを持っているので、これが正しいことを確認するために引っ張り出さなければなりませんでした。)
- 自動 :テストの呼び出しと結果の PASS/FAIL の確認は自動的に行われる必要があります。
- 徹底的に:カバレッジ;バグはコード内の特定の領域に集中する傾向がありますが、すべての主要なパスとシナリオを必ずテストしてください。未テストの領域を知る必要がある場合はツールを使用する
- 繰り返し可能:テストでは毎回同じ結果が得られるはずです。毎回。テストは制御不可能なパラメータに依存すべきではありません。
- 独立した:とても重要です。
- テストは次のことを行う必要があります 一つだけのことをテストする 一度に。複数のアサーションは、すべて 1 つの機能/動作をテストしている限り問題ありません。テストが失敗した場合、問題の場所を正確に特定する必要があります。
- テスト お互いに依存すべきではない - 孤立しました。テストの実行順序についての仮定はありません。セットアップ/ティアダウンを適切に使用して、各テストの前に「白紙の状態」を確保する
プロ:長期的には、本番環境と同程度のテスト コード (それ以上ではないにしても) が作成されることになるため、テスト コードに対しても同じ適切な設計基準に従ってください。意図が明らかな名前を持つ適切に因数分解されたメソッドクラス、重複なし、適切な名前のテストなど。
優れたテストも実行されます 速い. 。実行に 0.5 秒以上かかるテスト。取り組む必要がある。テスト スイートの実行にかかる時間が長くなります。実行頻度が低くなります。開発者は実行の間にこっそりと変更を加えようとします。何かが壊れたら..どの変更が原因であるかを特定するにはさらに時間がかかります。
2010 年 8 月の更新:
- 読みやすい :これはプロフェッショナルの一部であると考えられますが、どれだけ強調してもしすぎることはありません。アシッドテストでは、チームのメンバーではない人を見つけて、その人にテスト対象の動作を数分以内に理解するよう依頼します。テストは実稼働コードと同じように保守する必要があるため、手間がかかっても読みやすくしてください。テストは対称的 (パターンに従う) かつ簡潔である (一度に 1 つの動作をテストする) 必要があります。一貫した命名規則を使用してください (例:TestDox スタイル)。テストを「付随的な詳細」で煩雑にすることは避けてください。ミニマリストになる。
これら以外に、他のほとんどのガイドラインは、利益の低い作業を削減するガイドラインです。例えば「自分が所有していないコードをテストしないでください」(例:サードパーティの DLL)。ゲッターとセッターをテストしないでください。費用対効果の比率または欠陥の確率に注目してください。
他のヒント
- 巨大なテストを書かないでください。 「単体テスト」の「ユニット」が示すように、それぞれを次のようにします。 原子 そして 孤立した できるだけ。必要に応じて、一般的なユーザー環境を手動で再作成するのではなく、モック オブジェクトを使用して前提条件を作成します。
- 明らかに機能するものをテストしないでください。 サードパーティ ベンダーのクラス、特にコードを作成するフレームワークのコア API を提供するクラスのテストは避けてください。たとえば、ベンダーの Hashtable クラスに項目を追加するテストを行わないでください。
- コードカバレッジツールの使用を検討する NCover など、まだテストしていないエッジ ケースを発見するのに役立ちます。
- テストを書いてみる 前に 実装。 テストは、実装が準拠する仕様のようなものだと考えてください。参照。また、テスト駆動開発のより具体的な分野である動作駆動開発も含まれます。
- 一貫性を保ちましょう。 コードの一部に対してのみテストを作成する場合、それはほとんど役に立ちません。チームで働いていて、他のメンバーの一部または全員がテストを書かない場合も、あまり役に立ちません。自分自身と他の全員にその重要性を納得させてください(そして 時間の節約 テストのプロパティ) を使用するか、気にしないでください。
ここでの回答のほとんどは、実際にテスト自体を記述する (方法) というよりも、単体テストのベスト プラクティス (いつ、どこで、なぜ、何を) 全般に対処しているようです。質問は「方法」の部分についてかなり具体的であるように思えたので、私が会社で実施した「茶色のバッグ」のプレゼンテーションから抜粋したこれを投稿しようと思いました。
Womp のライティング テストの 5 つの法則:
1.長くて説明的なテスト メソッド名を使用します。
- Map_DefaultConstructorShouldCreateEmptyGisMap()
- ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
- Dog_Object_Should_Eat_Homework_Object_When_Hungry()
2.テストを アレンジ/アクト/アサートスタイル.
- この組織戦略は、 しばらく前から存在しており、 いろんなこと、はじめに 最近、"AAA"の頭字語の これを伝えるための素晴らしい方法です。すべてのテストの一貫性を AAAスタイルにより、読みやすくなり、 保つ。
3.アサーションには必ず失敗メッセージを含めてください。
Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element
processing events was raised by the XElementSerializer");
- ランナー アプリケーションで何が失敗したかを明確にする、シンプルですがやりがいのある練習です。メッセージを提供しない場合、通常は失敗の出力に「期待されたのは true でしたが、false でした」のようなメッセージが表示されるため、何が問題なのかを見つけるために実際にテストを読みに行かなければなりません。
4.テストの理由をコメントする – ビジネス上の前提は何ですか?
/// A layer cannot be constructed with a null gisLayer, as every function
/// in the Layer class assumes that a valid gisLayer is present.
[Test]
public void ShouldNotAllowConstructionWithANullGisLayer()
{
}
- 当たり前のことのように思えるかもしれませんが、この 実践は誠実さを守る そうでない人からのあなたのテストの テストの背後にある理由を理解する そもそもですね。私はたくさん見てきました テストは削除または変更され、 完全に問題ありませんでした。 その人は理解していなかった テストが 確認。
- テストが些細な場合、またはメソッド 名前は十分に説明的です。 を残すことが許される場合があります コメントオフします。
5.すべてのテストでは、接触するリソースの状態を常に元に戻す必要があります。
- 可能な限りモックを使用して、 実際のリソースを扱う。
- クリーンアップはテストで行う必要があります レベル。テストには、 実行順序への依存。
これらの目標を念頭に置いてください (Meszaros の書籍 xUnit Test Patterns から抜粋)
- テストはリスクを軽減するべきであって、リスクを減らすべきではない それを紹介します。
- テストは簡単に実行できる必要があります。
- テストは、次のように保守が容易である必要があります。 システムはそれらを中心に進化します
これを簡単にするために次のようなことを行います。
- テストが失敗するのは、次の理由によるものです。 理由は1つ。
- テストでは 1 つのことだけをテストする必要があります
- テストの依存関係を最小化する (いいえ データベース、ファイル、UI への依存関係 など)
xUnit フレームワークとの統合テストも実行できることを忘れないでください。 ただし、統合テストと単体テストは分離してください
テストは隔離する必要があります。あるテストが別のテストに依存すべきではありません。さらに、テストは外部システムに依存すべきではありません。言い換えれば、テスト あなたの コードが依存するコードではなく、コードです。統合テストまたは機能テストの一部として、これらの対話をテストできます。
優れた単体テストのいくつかの特性:
テストが失敗した場合、問題がどこにあるのかがすぐに明らかになるはずです。問題を追跡するためにデバッガーを使用する必要がある場合、テストの粒度は十分ではありません。ここで役立つのは、テストごとにアサーションを 1 つだけ指定することです。
リファクタリングを行う場合、テストが失敗してはなりません。
テストは実行することを躊躇しないほど高速に実行される必要があります。
すべてのテストは常にパスする必要があります。非決定的な結果はありません。
単体テストは、運用コードと同様に、十分に要素を考慮する必要があります。
@アロトル:ライブラリは外部 API でのみ単体テストを行うべきだと言っているのであれば、私は同意しません。外部呼び出し元に公開しないクラスを含む、各クラスの単体テストが必要です。(しかし、 プライベート メソッドのテストを作成する必要があると感じた場合は、リファクタリングする必要があります。)
編集:「テストごとに 1 つのアサーション」によって生じる重複についてのコメントがありました。具体的には、シナリオを設定するためのコードがあり、それに対して複数のアサーションを作成したいが、テストごとにアサーションが 1 つしかない場合、複数のテストにわたって設定を複製する可能性があります。
私はそのようなアプローチはとりません。代わりにテストフィクスチャを使用します シナリオごとに. 。大まかな例を次に示します。
[TestFixture]
public class StackTests
{
[TestFixture]
public class EmptyTests
{
Stack<int> _stack;
[TestSetup]
public void TestSetup()
{
_stack = new Stack<int>();
}
[TestMethod]
[ExpectedException (typeof(Exception))]
public void PopFails()
{
_stack.Pop();
}
[TestMethod]
public void IsEmpty()
{
Assert(_stack.IsEmpty());
}
}
[TestFixture]
public class PushedOneTests
{
Stack<int> _stack;
[TestSetup]
public void TestSetup()
{
_stack = new Stack<int>();
_stack.Push(7);
}
// Tests for one item on the stack...
}
}
あなたが求めているのは、テスト対象のクラスの動作を説明することです。
- 予想される動作の検証。
- エラーケースの検証。
- クラス内のすべてのコード パスをカバーします。
- クラス内のすべてのメンバー関数を実行します。
基本的な目的は、クラスの行動に対する自信を高めることです。
これは、コードのリファクタリングを検討する場合に特に役立ちます。マーティン・ファウラーは興味深いことを言っています 記事 彼のウェブサイトでのテストについて。
HTH。
乾杯、
ロブ
テストは本来失敗するはずです。そうしないと、バグがあり、常にパスするテストを作成する危険があります。
前述の Right BICEP の頭字語が好きです 実用的な単体テスト 本:
- 右:結果は 右?
- B:全部ですか? b異常な条件は正しいですか?
- 私:確認してもいいですか 私逆関係?
- C:していい c他の手段を使用して結果をロスチェックしますか?
- E:強制してもいいですか eエラー条件が発生しますか?
- P:は p性能特性は範囲内ですか?
個人的には、正しい結果が得られることを確認し (加算関数で 1+1 は 2 を返すはずです)、考えられるすべての境界条件を試してみること (たとえば、合計が次の 2 つの数値を使用するなど) によって、かなり遠くまで到達できると感じています。は、add 関数の整数の最大値よりも大きい)、ネットワーク障害などのエラー状態を強制します。
優れたテストは保守可能である必要があります。
複雑な環境でこれを行う方法がまだわかりません。
すべての教科書は、コードベースが到達し始めると、接着が外れ始めます 数百万行または数百万行のコードに。
- チームの交流が爆発する
- テストケースの数が急増する
- コンポーネント間の相互作用が爆発的に増加します。
- すべての単体テストをビルドする時間がビルド時間の重要な部分を占めるようになります
- API の変更は、何百ものテスト ケースに波及する可能性があります。プロダクションコードの変更は簡単だったのに。
- プロセスを正しい状態にシーケンスするために必要なイベントの数が増加し、テストの実行時間が増加します。
優れたアーキテクチャは、相互作用の爆発的な増加をある程度制御できますが、必然的に システムはより複雑になり、自動テストシステムもそれとともに成長します。
ここからトレードオフに対処する必要があります。
- 外部 API のみをテストしてください。それ以外の場合、内部をリファクタリングすると、テスト ケースの大幅なやり直しが必要になります。
- カプセル化されたサブシステムがより多くの状態を保持するため、各テストのセットアップとティアダウンはより複雑になります。
- 夜間のコンパイルと自動テストの実行は数時間にまで増加します。
- コンパイル時間と実行時間の増加は、設計者がすべてのテストを実行しない、または実行しないことを意味します
- テストの実行時間を短縮するには、テストのセットアップとティアダウンを減らすための順序付けを検討します。
次のことも決定する必要があります。
コードベースのどこにテストケースを保存しますか?
- テストケースをどのように文書化しますか?
- テストフィクスチャを再利用してテストケースのメンテナンスを節約できますか?
- 夜間のテスト ケースの実行が失敗するとどうなりますか?トリアージは誰が行うのですか?
- モックオブジェクトをどのように管理しますか?20 個のモジュールがあり、そのすべてが独自のモック ロギング API を使用している場合、API の変更はすぐに影響を及ぼします。テスト ケースが変更されるだけでなく、20 個のモック オブジェクトも変更されます。これら 20 のモジュールは、多くの異なるチームによって数年かけて書かれました。これは典型的な再利用の問題です。
- 個人とそのチームは自動テストの価値を理解していますが、他のチームのやり方が気に入らないだけです。:-)
永遠に続けることもできますが、私が言いたいのは次のことです。
テストは保守可能である必要があります。
これらの原則については少し前に説明しました この MSDN マガジンの記事 これは開発者にとって読むべき重要な内容だと思います。
私が「良い」単体テストを定義する方法は、テストが次の 3 つのプロパティを備えているかどうかです。
- それらは読み取り可能です (名前、アサート、変数、長さ、複雑さなど)。
- 保守可能です (ロジックがなく、過剰に指定されておらず、状態ベースであり、リファクタリングされていません。)
- それらは信頼に値します (統合テストではなく、分離された正しいものをテストします。)
- 単体テストは単体の外部 API をテストするだけであり、内部の動作をテストするべきではありません。
- TestCase の各テストでは、この API 内の 1 つ (1 つだけ) のメソッドをテストする必要があります。
- 失敗した場合には、追加のテスト ケースを含める必要があります。
- テストの範囲をテストします。ユニットがテストされると、このユニット内の行が 100% 実行されるはずです。
ジェイ・フィールズには、 たくさんの良いアドバイス 単体テストの作成については、 最も重要なアドバイスを要約した投稿. 。そこには、自分の状況を批判的に考え、そのアドバイスが自分にとって価値があるかどうかを判断する必要があると書かれています。ここでは素晴らしい答えがたくさん得られますが、どれが自分の状況に最適であるかを決めるのはあなた次第です。試してみて、不快な場合はリファクタリングしてください。
敬具
単純な 2 行の方法が機能するとは決して考えないでください。素早い単体テストを作成することが、ヌル テストの欠落、マイナス記号の置き間違い、および/または微妙なスコープ エラーによる被害を防ぐ唯一の方法です。これに対処する時間が今よりもさらに短い場合は避けられません。
私は「旅行」という答えに次ぐものですが、 テストは相互に依存する必要があります。
なぜ?
DRY - 繰り返しはしない - はテストにも当てはまります。テストの依存関係は、1) セットアップ時間の節約、2) フィクスチャ リソースの節約、3) 障害の特定に役立ちます。もちろん、テスト フレームワークがファーストクラスの依存関係をサポートしている場合に限ります。そうでなければ、彼らは悪いと私は認めます。
多くの場合、単体テストはモック オブジェクトまたはモック データに基づいています。私は 3 種類の単体テストを書くのが好きです。
- 「一時的な」単体テスト:彼らは独自のモックオブジェクト/データを作成し、それを使用して機能をテストしますが、すべてが破壊され、痕跡が残りません(テストデータベースにデータが存在しないのと同様)
- 「永続的な」単体テスト:コード内の関数をテストして、後ほど高度な関数で独自の単体テストに必要となるオブジェクト/データを作成します (高度な関数が独自のモック オブジェクト/データのセットを毎回再作成するのを回避します)
- 「永続ベース」の単体テスト:永続的な単体テストによって既に存在する (別の単体テスト セッションで作成されたため) モック オブジェクト/データを使用した単体テスト。
ポイントはリプレイを避けること すべて すべての機能をテストできるようにするためです。
- すべてのモックオブジェクト/データがすでにそこにあるため、私は 3 番目の種類を非常に頻繁に実行します。
- 私はモデルが変わるたびに第2種を実行します。
- 最初のプログラムを実行して、非常に基本的な機能を時々チェックし、基本的な回帰を確認します。
機能テストとパフォーマンス テストという 2 種類のテストについて考え、別々に扱います。
それぞれに異なる入力とメトリクスを使用します。テストの種類ごとに異なるソフトウェアを使用する必要がある場合があります。
私は、で説明されている一貫したテスト命名規則を使用しています。 Roy Osherove の単体テストの命名基準 特定のテスト ケース クラスの各メソッドには、次の命名スタイル MethodUnderTest_Scenario_ExpectedResult があります。
- 最初のテスト名のセクションは、テスト対象のシステム内のメソッドの名前です。
- 次に、テストされる特定のシナリオです。
- 最後に、そのシナリオの結果を示します。
各セクションは大文字のキャメルケースを使用し、アンダースコアで区切られています。
テストを実行するときに、テスト対象のメソッドの名前ごとにテストがグループ化されるので、これが便利であることがわかりました。そして、他の開発者がテストの意図を理解できるようにするための規約を設けます。
また、テスト対象のメソッドがオーバーロードされている場合は、メソッド名にパラメータを追加します。