一般的な単体テストの足場コードを作成することの利点と欠点


8

私のチームと私が取り組んでいるプロジェクトでは、大きな足場のコードが必要になることがよくあります。正しい値を持つドメインオブジェクトの作成、リポジトリのモックの設定、キャッシュの処理などは、すべてのテストで一般的に発生することです。多くの場合、ドメインの中心となる同じ基本オブジェクト(人、...)で作業しているため、他のオブジェクトで作業するために、多くのテストでこれらのオブジェクトのインスタンスが作成されます。基本ドメインを使用するさまざまなソリューションがたくさんあるので、この種のコードは多くの場合、それらのソリューションに分散しています。

私はこの足場の多くを行う共通のクラスを作成することを考えてきました。これにより、すべてが設定された完全にインスタンス化された人(リポジトリ経由のアクセス、キャッシングなど)をリクエストできます。これにより、個々の単体テストから重複したコードが削除されますが、テストごとに「多すぎる」コードが大量にあることも意味します(必要なパーツだけでなく、完全なオブジェクトが設定されるため)。

誰かこれをしたことがありますか?このアプローチを検証または無効にする可能性のある洞察、発言、考えなどはありますか?


ここに移行する前に質問を少し編集して、通常は回答に入る要素を削除しました。質問自体に長所と短所をリストすることで、誰かがあなたがすでに言ったことを複製しない包括的な回答をする機会が減ります。このように、あなたが作ったポイントは理想的には答えの一部として浮かび上がるでしょう。そうでない場合は、自由に回答として再投稿してください。コミュニティが投票することで、あなたがそのお金で正しいかどうかを判断できます。
アダム・リア

回答:


4
Creating domain objects with correct values

Growing Object-Oriented Software」の本に詳述されているように、ビルダーパターンを使用してテスト用のドメインオブジェクトを作成します。ビルダーには、デフォルト値をオーバーライドするためのメソッドを持つ一般的なオブジェクトのデフォルト値を生成する静的メソッドがあります。

User user = UserBuilder.anAdminUser().withEmail("test@example.com").build();

これらを組み合わせて、より複雑なオブジェクトを生成できます。

また、特定のタイプのユニットテストに一般的に使用されるモックでテストケースクラスの継承を使用します。たとえば、mvcコントローラーテストでは、リクエストオブジェクトとレスポンスオブジェクトに同じモック/スタブが必要になることがよくあります。


2

特にデプロイを行う場合は、すべてのテスト用に設定することをお勧めします。モックを使用する代わりに、テストデータベースを作成、デプロイしてデータを入力し、インスタンスにテストをポイントします。マニュアルに記載されているとおりの方法ではありませんが、デプロイメントコードを使用して実行するため、より効果的です。


データベースへの依存性がまったくなく、オブジェクトがPOCOであると仮定しますが、それらを取得するデータベースを検討しますか?テストにのみ使用されるデータベースを維持する必要があることは、テストにのみ使用されるオブジェクトを作成するためにコードを維持する必要があることとは異なりますか?
JDT 2011

2

Bedwyr Humphreysが、私が何度か使用したBuilderソリューションについて話していますが、それは本当にうまくいきます。コードの重複がほとんどなく、ドメインオブジェクトをきれいに作成できます。

User user = UserBuilder.anAdminUser().withEmail("test@example.com").build();

私はコード生成(scaffolding、codesmith、lblgenなど)のファンではありません。それはあなたが複製コードをより速く書くのを助けるだけであり、実際にメンテナンスフレンドリーではありません。フレームワークが大量の重複(テスト)コードを作成する原因となっている場合は、コードベースをフレームワークから遠ざける方法を見つける必要があるかもしれません。

リポジトリアクセスコードを作成し続けると、汎用ベースリポジトリでこれを解決し、ユニットテストが継承する汎用ベーステストクラスを作成できる場合があります。

コードの重複を回避するために、AOPはロギング、キャッシングなどにも役立ちます...


1

テストするコードの設計を再検討する必要がある場合があります。おそらくそれは、ファサードデザインパターンのいくつかのインスタンスを必要としますか?テストコードはFacadeを使用でき、より単純になります。


これは有効なアプローチですが、私たちが持っているコードベースには当てはまりません。現在、ドメインオブジェクトからXMLベースのメッセージを作成するビルダークラスを使用しています。ファサードクラスの量によって、完全に構築されたオブジェクトをある時点でプラグインする必要がなくなるわけではありません。これは、さまざまなソリューションで何度も実行する必要があったものです。
JDT 2011

1

この質問に関連することができます。私のチームでは、以前にTDDを試してみましたが、同じ話でした。テストが多すぎると、必要のない多くの足場/ユーティリティメソッドが必要になります。時間のプレッシャーとそれほど正しくない決定がいくつかあったため、非常に長く、再発明された単体テストになりました。リリースの過程で、これらの単体テストは非常に複雑になり、保守が困難になったため、それらを削除する必要がありました。

現在、他の数人の開発者と協力してTDDのチームへの再導入に取り組んでいますが、単に人々に伝えるだけでなく、テストを書きます。今回は慎重に行いたいので、人々は適切なスキルと適切なツールを持っています。共通のサポートコードがないことは、他の人がテストを書き始めたときに、彼らが書くすべてのコードが小さくてポイントになるように改善する必要があると私たちが確認したことの1つです。

この分野での経験はまだ十分ではありませんが、これまでに行った作業とこれまでに行ったいくつかの宿題に基づいて提案することはほとんどありません。

  1. 一般的なコードは良いことです。同じ仕事を5回行うのは意味がなく、時間のプレッシャーの下では、そのうちの少なくとも4回は中途半端になります。
  2. 私の計画は、共通のフレームワークを構築し、次に4〜6人の開発者で構成されるTDDリーダーシップチームを構築することです。次に、単体テストを作成し、フレームワークを学習して、フィードバックを提供します。次に、これらの人にグループの残りの部分に出かけてもらい、フレームワークの力を利用する保守可能な単体テストを作成するように他の人を指導します。また、これらの担当者にフィードバックを収集してフレームワークに戻してもらってください。
  3. Personクラスで提供した例では、実際にmockppまたはgooglemockフレームワークの使用を調査しています。現時点ではgooglemockに傾いていますが、まだ試していません。どちらの場合でも、「借用」できるものを書く理由
  4. テストで実際にオブジェクトが必要かどうかに関係なく、すべてのテストのオブジェクトをインスタンス化する「神」フィクスチャの作成には注意してください。これは、xUnitテストパターンが「一般的なフィクスチャパターン」と呼ぶものです。これにより、a)セットアップコードが多すぎるために各テストの実行に時間がかかりすぎたり、b)不要な依存関係が多くなりすぎたりするなど、いくつかの問題が発生する可能性があります。私たちのフレームワークでは、各テストクラスが実行するために必要なフレームワーク機能を正確に選択するため、不要な手荷物は含まれていません(将来、個別に選択する各メソッドに変更する可能性があります)。BoostとModern C ++ Designからインスピレーションを得ましたそして、必要な機能のmpl :: vectorを受け取り、そのテスト専用のカスタムクラス階層を構築する基本テンプレートクラスを思い付きました。いくつかの本当に気の利いたキャプテンクラッチデコーダリングスーパークレイジーブラックマジックテンプレートのもの:)

1

「コンピューターサイエンスの問題は、間接層を1つ追加することで解決できますが、通常は別の問題が発生します。」– David Wheeler

(免責事項:私はJDTのチームの一員であるため、ここに提示されている問題について、より深い知識があります)

ここでの本質的な問題は、Rockford LhotkaによるCSLAフレームワークに大まかに基づいているため、フレームワーク内のオブジェクト自体がデータベース接続を期待していることです。これはそれらをモックすることをほとんど不可能にし(これをサポートするためにフレームワークを変更する必要があるため)、ユニットテストが持つべきブラックボックスの原則にも違反します。

提案されたソリューションは、それ自体は悪い考えではありませんが、安定したソリューションを実現するために多くの作業を必要とするだけでなく、維持および拡張する必要のあるクラスのさらに別のレイヤーを追加し、既存のソリューションにさらに複雑さを追加します複雑な状況。

私はあなたが常に最良の解決策を探すべきであることに同意しますが、このような現実の状況では、実用的であることにも価値があります。

そして実用性は次のオプションを提案します:

データベースを使用します。

はい、技術的には「本当の」単体テストではなくなっていることを知っていますが、厳密に言えば、テストでクラスの境界を超えた瞬間に、本当の単体テストではなくなります。ここで自問する必要があるのは、大量の足場コードとグルーコードを必要とする「純粋な」単体テストが必要なのか、それともテスト可能なコードが必要なのか、です。

使用するフレームワークがすべてのデータベースアクセスをカプセル化するという事実を利用して、クリーンなサンドボックスデータベースに対してテストを実行することもできます。その後、独自のトランザクションで各テストを実行し、各「論理」テストセットの最後にロールバックすれば、相互依存性やテスト順序の問題は発生しません。

データベースをデータベース化しようとしないでください。


サム、トニー・ホプキンソンの返答についての私のコメントを見てください。私たちがすべてPOCOクラスだけのプロジェクトを扱っている場合も、同じことをしますか?
JDT 2011

外部リソースなしでそれ自体で機能するアルゴリズムをテストしているのでない限り、確かに。「テスト」データベースには最小限のデータしか含まれておらず、インフラストラクチャ以外のテストデータはテスト自体で挿入する必要があるため、メンテナンスコストを削減できるという考え方です。しかし、確かに、「明確な」POCOオブジェクトを使用すると、テストデータベースのケースはそれほど強くありません。
Sam

0

開発者が正確に何が設定されているのかを知らなくても、「ブラックマジック」が多すぎるコードが多すぎることを警告します。また、やがて、このコード/データに大きく依存するようになります。そのため、私はテストデータベースに基づいてテストを行うのが好きではありません。テストデータベースは、自動テストではなく、ユーザーとしてのテスト用であると思います。

とはいえ、あなたの問題は本当です。必要なデータを定期的に設定するための一般的な方法をいくつか作成します。特定のテストでは異なるデータが必要になるため、理想的には、メソッドのパラメーター化を試みます。

しかし、大きなオブジェクトグラフを作成することは避けます。必要な最小限のデータでテストを実行するのが最善です。他の多くのデータがある場合、テスト結果に予期せぬ影響を与える可能性があります(最悪のシナリオ)。

テストをできる限り「サイロ化」したままにしておきたい。しかし、それは主観的な質問です。ただし、テストデータを数行のコードで設定できるように、パラメーターを使用する一般的なメソッドを使用します。


0

答えにまとめられたすべての可能性:

Builderパターンを使用します(Bedwyr Humphreys、DXM、Pascal Mestdach)

利点:

  • テストでコードが重複しない
  • 複雑なテストのために異なるビルダーを組み合わせることができます
  • きれいなインターフェース
  • ビルダーからの大きなオブジェクトグラフはテスト結果に影響を与える可能性があります

短所:

  • ビルダーは書かれ、維持されなければならない
  • ビルダーコードもテストする必要があります
  • ビルダーコードには、常にグルーミングと注意が必要です。

テストデータベースを使用する(Tony HopkinsonとSam)

利点:

  • シンプルさ
  • すべてのデータを一元管理
  • 開発済みのデータアクセスコードを使用できます
  • POCOではないドメインオブジェクトに使用できます(例:Active Recordなど)。

短所:

  • テストを妨害する可能性があるDALの使用が必要
  • 「純粋」なTDDではない
  • 「ブラックボックス」、オブジェクトを含むデータを確認するには、データベースを確認する必要があります

テストごとにドメインオブジェクトを作成する(Peter)

利点:

  • 分離されたテストコード
  • 「ブラックボックス」はありません。テストコードに表示されるのは取得したものです。

短所:

  • 一般的なオブジェクトのコードの複製

ドメインオブジェクトにファサードを使用する(Raedwald)

利点:

  • 簡単なテスト

短所:

  • すべての場合に使用できるわけではありません
  • Facadeの背後に隠されたコードもテストが必要
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.