web-dev-qa-db-ja.com

ユニットテストの採用

現在のプロジェクトに単体テストを導入しようとしましたが、機能していないようです。余分なコードはメンテナンスの頭痛の種になっているようです。内部フレームワークが変更されたときに、それを回避して単体テストを修正する必要があるためです。

子クラスの抽象メソッド実装を呼び出すテンプレートとして機能するコントローラーを単体テストするための抽象基本クラスがあります。つまり、フレームワークはInitializeを呼び出すため、コントローラークラスはすべて独自のInitializeメソッドを持ちます。

私は以前ユニットテストの提唱者でしたが、現在のプロジェクトでは機能していないようです。

誰かが問題を特定し、ユニットテストを私たちに対してではなく私たちのために機能させる方法を教えてもらえますか?

69
Burt

チップ:

手続き型コードの記述は避けてください

テストは、グローバルな状態に大きく依存している、または醜いメソッドの本体の奥深くにある手続き型コードに対して作成されている場合、維持するのに負担になる可能性があります。 OO言語でコードを書いている場合、 OO構文 を使用してこれを効果的に減らします。

  • 可能であれば、グローバル状態は避けてください。
  • スタティックはコードベース全体に波及する傾向があり、最終的には静的であってはならないものになるため、静的は避けてください。また、テストコンテキストを膨らませます(以下を参照)。
  • ポリモーフィズムを効果的に活用して、 過剰なifとフラグ を防止します

変更点を見つけてカプセル化し、同じままのものから分離します。

コードには、他の部分よりも頻繁に変更されるチョークポイントがあります。コードベースでこれを行うと、テストがより健全になります。

  • 優れたカプセル化は、優れた疎結合設計につながります。
  • リファクタリングとモジュール化。
  • テストを小さく、集中させてください。

テストを取り巻くコンテキストが大きいほど、維持するのが難しくなります。

テストとそれらが実行される周囲のコンテキストを縮小するために、できることは何でもしてください。

  • 合成メソッドリファクタリングを使用して、コードの小さなチャンクをテストします。
  • TestNGやJUnit4などの新しいテストフレームワークを使用していますか?テストライフサイクルへのよりきめ細かいフックを提供することで、テストの重複を取り除くことができます。
  • テストダブル(モック、フェイク、スタブ)を使用して調査し、テストコンテキストのサイズを縮小します。
  • テストデータビルダー パターンを調査します。

テストから重複を削除しますが、焦点が合っていることを確認してください。

おそらくすべての重複を削除することはできませんが、それでも痛みを引き起こしている場所でそれを削除しようとします。誰かが入ってこないほど多くの重複を削除しないように注意し、テストの内容を一目で確認してください。 (同じ概念の別の説明については、Paul Wheatonの "Evil Unit Tests" の記事を参照してください。)

  • テストが何をしているのか理解できない場合、誰もテストを修正したくないでしょう。
  • アレンジ、アクト、アサートパターンに従ってください。
  • テストごとに1つのアサーションのみを使用します。

検証しようとしているものに対して適切なレベルでテストします。

記録と再生のSeleniumテストに伴う複雑さ、および単一のメソッドのテストと比較して、自分の下で何が変わる可能性があるかを考えてください。

  • 依存関係を相互に分離します。
  • 依存性の注入/制御の反転を使用します。
  • Test doubleを使用して、テスト用のオブジェクトを初期化し、単一ユニットのコードを分離してテストしていることを確認します。
  • 関連するテストを書いていることを確認してください
    • わざとバグを導入して「SpringtheTrap」を実行し、テストで確実にキャッチされるようにします。
  • 参照: 統合テストは詐欺です

状態ベースのテストと相互作用ベースのテストをいつ使用するかを知る

真の単体テストには真の分離が必要です。ユニットテストはデータベースにヒットしたり、ソケットを開いたりしません。これらの相互作用をあざけるのはやめましょう。このメソッド呼び出しの適切な結果が「42」であったことではなく、共同編集者と正しく話し合っていることを確認してください。

テスト駆動コードのデモンストレーション

特定のチームがすべてのコードをテストドライブするのか、それともコードのすべての行に対して「最初にテスト」を作成するのかは議論の余地があります。しかし、彼らは最初に少なくともいくつかのテストを書くべきですか?絶対に。テストファーストが間違いなく問題に取り組む最良の方法であるシナリオがあります。

リソース:

108
cwash

十分に小さいコード単位をテストしていますか?コアコードのすべてを根本的に変更しない限り、あまり多くの変更が表示されることはありません。

物事が安定すると、単体テストをより高く評価するようになりますが、今でもテストでは、フレームワークへの変更がどの程度伝播されるかが強調されています。

それは価値があります、あなたができる限りそれに固執してください。

19
cjk

より多くの情報がなければ、なぜあなたがこれらの問題に苦しんでいるのかをきちんと突き刺すのは難しいです。インターフェースの変更などが多くのことを壊すことは避けられない場合もあれば、設計上の問題に帰着する場合もあります。

発生している障害を分類してみることをお勧めします。どんな問題がありますか?例えば。 APIの変更によるテストメンテナンス(リファクタリング後にコンパイルする場合など)ですか、それともAPIの変更の動作によるものですか?パターンが表示されている場合は、製品コードの設計を変更するか、テストが変更されないようにすることができます。

いくつかの変更を行うと、多くの場所でテストスイートに計り知れない荒廃が生じる場合、実行できることがいくつかあります(これらのほとんどは、一般的な単体テストのヒントです)。

  • 小さなコード単位を開発し、小さなコード単位をテストします。コードのユニットに「継ぎ目」が含まれるように、意味のあるインターフェイスまたは基本クラスを抽出します。引き込む必要のある依存関係が多いほど(さらに悪いことに、「new」を使用してクラス内でインスタンス化する)、コードの変更にさらされる可能性が高くなります。コードの各ユニットに少数の依存関係がある場合(場合によってはいくつか、またはまったく依存関係がない場合)、変更からの隔離が強化されます。

  • テストに必要なものについてのみ主張します。中間状態、偶発的状態、または無関係な状態を主張しないでください。契約による設計と契約によるテスト(たとえば、スタックポップメソッドをテストしている場合は、プッシュ後にcountプロパティをテストしないでください-別のテストで行う必要があります)。

    特に各テストがバリアントである場合、この問題はかなり見られます。その偶発的な状態のいずれかが変化すると、それに対してアサートするすべてのものが破壊されます(アサートが必要かどうかは関係ありません)。

  • 通常のコードと同様に、単体テストではファクトリとビルダーを使用します。約40のテストで、APIの変更後にコンストラクター呼び出しを更新する必要があることを学びました...

  • 同様に重要なのは、最初に正面玄関を使用することです。利用可能な場合、テストでは常に通常の状態を使用する必要があります。必要な場合にのみ相互作用ベースのテストを使用しました(つまり、検証する状態がありません)。

とにかく、これの要点は、テストが壊れている理由/場所を見つけて、そこから行くことです。変化から身を守るために最善を尽くしてください。

12
Mark Simpson

単体テストの利点の1つは、このような変更を加えると、コードを壊していないことを証明できることです。テストをフレームワークと同期させる必要がありますが、このかなり平凡な作業は、リファクタリング時に何が壊れたかを把握しようとするよりもはるかに簡単です。

8
Jon B

良い質問!

ソフトウェア自体を設計するのと同じように、優れた単体テストを設計することは困難です。これが開発者によって認められることはめったにないため、結果は、テスト対象のシステムが変更されるたびにメンテナンスを必要とする、急いで作成された単体テストになることがよくあります。したがって、問題の解決策の一部は、単体テストの設計を改善するためにより多くの時間を費やすことである可能性があります。

請求に値する素晴らしい本を1冊お勧めします ユニットテストのデザインパターン

HTH

4
azheglov

テストが実際のコードで古くなっていることが問題である場合は、次のいずれかまたは両方を実行できます。

  1. 単体テストを更新しないコードレビューに合格しないように、すべての開発者をトレーニングします。
  2. チェックインのたびに単体テストのフルセットを実行し、ビルドを中断した人にメールを送信する自動テストボックスを設定します。 (以前は「ビッグボーイ」のためだけのものだと思っていましたが、専用のボックスでオープンソースパッケージを使用していました。)
4
Robert Gowland

TDDに固執することをお勧めします。ユニットテストフレームワークをチェックして、チームで1つのRCA(根本原因分析)を実行し、領域を特定してください。

スイートレベルで単体テストコードを修正し、コード、特に関数名やその他のモジュールを頻繁に変更しないでください。

ケーススタディをうまく共有していただければ、問題のある領域をさらに掘り下げることができますか?

4
Sourabh

あなたのユニットテストは彼らがすることになっていることをしている。フレームワーク、即時コード、またはその他の外部ソースの変更による動作の中断を表面化します。これは、動作が変更されて単体テストを適宜変更する必要があるかどうか、またはバグが発生して単体テストが失敗して修正する必要があるかどうかを判断するのに役立ちます。

諦めないでください。今はイライラしますが、メリットは実現します。

3
David Yancey

コード内のロジックが変更されていて、それらのコードのテストを作成した場合は、新しいロジックをチェックするためにテストを変更する必要があると思います。単体テストは、コードのロジックをテストするかなり単純なコードであると想定されています。

3
CSharpAtl

フレームワークに変更を加えるたびにユニットテストを変更する必要があるのはなぜですか? これは逆ではないでしょうか?

TDDを使用している場合は、最初にテストが間違った動作をテストしていることを確認し、代わりに目的の動作が存在することを確認する必要があります。テストを修正したので、テストは失敗し、テストが再び合格するまで、フレームワークのバグをつぶす必要があります。

コードのテストを維持するのを困難にする特定の問題についてはわかりませんが、テストの中断で同様の問題が発生したときの私自身の経験のいくつかを共有できます。最終的に、テスト容易性の欠如は、主にテスト対象のクラスの設計上の問題が原因であることがわかりました。

  • インターフェイスの代わりに具象クラスを使用する
  • シングルトンの使用
  • インターフェイスメソッドの代わりに、ビジネスロジックとデータアクセスのために多くの静的メソッドを呼び出す

このため、通常、テストが失敗していることがわかりました。テスト対象のクラスが変更されたためではなく、テスト対象のクラスが呼び出していた他のクラスが変更されたためです。一般に、クラスをリファクタリングしてデータの依存関係を要求し、モックオブジェクトを使用してテストする(EasyMock et al for Java)と、テストの焦点が絞られ、保守しやすくなります。私は特にこのトピックに関していくつかのサイトを本当に楽しんだ:

2
Kyle Krull

コードのテストが非常に難しく、テストコードが壊れたり、同期を維持するために多大な労力が必要な場合は、より大きな問題が発生します。

Extract-methodリファクタリングを使用して、1つのことと1つのことだけを実行するコードの小さなブロックをヤンクアウトすることを検討してください。依存関係なしで、それらの小さなメソッドにテストを記述します。

1
sal

余分なコードはメンテナンスの頭痛の種になっているようです。内部フレームワークが変更されたときに、それを回避して単体テストを修正する必要があるためです。

別の方法は、フレームワークが変更されたときに、変更をテストしないことです。または、フレームワークをまったくテストしません。それはあなたが望むものですか?

フレームワークをリファクタリングして、個別にテストできる小さな部分で構成されるようにすることができます。次に、フレームワークが変更されたときに、(a)変更される部分が少ないか、(b)変更のほとんどが部分の構成方法にあることを期待します。どちらの方法でも、コードとテストの両方をより適切に再利用できます。しかし、真の知的努力が含まれています。簡単だとは思わないでください。

1
Norman Ramsey

あなたのユニットテストはブラックボックス指向ではありませんか?つまり...例を挙げましょう:ある種のコンテナをユニットテストしているとしたら、コンテナのget()メソッドを使用して新しいアイテムが実際に保存されていることを確認しますか、それともハンドルを取得できますか?アイテムが保管されている場所に直接アイテムを取得するための実際のストレージ?後者は脆弱なテストを行います。実装を変更すると、テストが中断されます。

内部実装ではなく、インターフェースに対してテストする必要があります。

また、フレームワークを変更するときは、最初にテストを変更してから、フレームワークを変更することをお勧めします。

1
philant

非常に小さなクラスの作成を奨励​​するIoC/DI方法論を使用し、単一責任の原則に忠実に従わない限り、単体テストは複数のクラスの相互作用をテストすることになり、非常に複雑になり、脆弱になることがわかりました。

私のポイントは、新しいソフトウェア開発手法の多くは、一緒に使用した場合にのみ機能するということです。特にMVC、ORM、IoC、ユニットテスト、モック。 DDD(現代の原始的な意味で)とTDD/BDDはより独立しているので、使用するかどうかはわかりません。

1
zvolkov

もちろん、すべてに価格が付いています。開発のこの初期段階では、多くの単体テストを変更する必要があるのが普通です。

コードの一部を確認して、カプセル化を増やしたり、依存関係を減らしたりすることをお勧めします。

生産日が近づくと、それらのテストができて幸せになります。私を信じてください:)

1
Gerrie Schenck

テスト自動化ツールに投資することをお勧めします。継続的インテグレーションを使用している場合は、それを連携して機能させることができます。コードベースをスキャンしてテストを生成するツールがあります。次に、それらを実行します。このアプローチの欠点は、一般的すぎることです。多くの場合、単体テストの目的はシステムを破壊することだからです。私は数多くのテストを作成しましたが、コードベースが変更された場合は変更する必要があります。

自動化ツールには、間違いなくコードカバレッジが向上するという微妙な違いがあります。

ただし、十分に記述された開発者ベースのテストを使用すると、システムの整合性もテストできます。

お役に立てれば。

1
Boris Kleynbok

TDDテストを設計すると、アプリケーション自体の設計に関する質問が開始されることがあります。クラスが適切に設計されているかどうかを確認してください。メソッドは一度に1つのことしか実行していません...優れた設計では、単純なメソッドとクラスをテストするコードを簡単に記述できるはずです。

0
sptremblay

私はこのトピックについて自分で考えていました。私はユニットテストの価値で非常に売られていますが、厳密なTDDでは売られていません。ある時点まで、クラス/インターフェースに分割する方法を変更する必要がある探索的プログラミングを行っているように思われます。古いクラス構造の単体テストに多くの時間を費やした場合、リファクタリングに対する慣性が増し、追加のコードを破棄するのが面倒になります。

0
Anon