web-dev-qa-db-ja.com

単体テストで独自のメソッドを使用するべきではありませんか?

今日、私は " JUnit basics"ビデオを見ていました。著者は、プログラムで特定のメソッドをテストするときは、プロセスで独自のメソッドの他のものを使用しないでくださいと言いました。

より具体的には、彼は引数に名前と姓を使用するレコード作成メソッドのテストについて話していて、それを使用して特定のテーブルにレコードを作成しました。しかし、彼はこの方法をテストする過程で、他の [〜#〜] dao [〜#〜] メソッドを使用してデータベースにクエリを実行し、最終結果を確認する(確認する)べきではないと主張しました。そのレコードは確かに正しいデータで作成されました)。そのため、データベースにクエリを実行して結果を確認するには、追加の [〜#〜] jdbc [〜#〜] コードを記述する必要があると彼は主張しました。

私は彼の主張の精神を理解していると思います:1つのメソッドのテストケースが他のメソッド(この場合はDAOメソッド)の正確さに依存することを望まない場合、これは独自の検証を(再度)作成することによって達成されます/ supportingコード(より具体的で焦点が絞られている必要があるため、より単純なコード)。

それにもかかわらず、私の頭の中の声は、コードの重複、不必要な追加の努力などの引数で抗議し始めました。つまり、テストバッテリー全体を実行し、すべてのパブリックメソッド(この場合はDAOメソッドを含む)を徹底的にテストする場合、他のメソッドをテストしながら、これらのメソッドのいくつかを使用するだけで問題はありませんか?それらの1つが想定どおりに動作しない場合、それ自体のテストケースは失敗し、修正してテストバッテリーを再度実行できます。コードを複製する必要はありません(たとえコードを複製するほうが多少簡単な場合でも)か、無駄な作業はありません。

最近いくつかの Excel - [〜#〜] vba [〜#〜] 私が書いたアプリケーション(適切にユニットテストされたおかげで適切にユニットテストされた) VBAのRubberduck )。この推奨事項を適用すると、認識された利点がなく、追加の作業が多くなることを意味します。

これについてのあなたの洞察を共有していただけませんか?

82
carlossierra

彼の主張の精神は確かに正しい。ユニットテストのポイントは、コードを分離し、依存関係がないようにテストすることです。これにより、エラーの発生した動作を、発生している場所ですばやく認識できます。

そうは言っても、単体テストはツールであり、それはあなたの目的を果たすためのものであり、祈られる祭壇ではありません。依存関係が十分に機能し、モックしたくないので依存関係を残すことを意味する場合もあれば、統合テストではなくても実際にかなり近い単体テストがある場合もあります。

最終的には評価されませんが、重要なのはテスト対象のソフトウェアの最終製品ですが、ルールを曲げて、トレードオフに価値があるかどうかを判断するときに注意する必要があります。

182
whatsisname

これは専門用語に帰着すると思います。多くの人にとって、「単体テスト」は非常に具体的なものであり、定義によりは、nit以外のコードに依存する合格/不合格条件を持つことはできません、機能など)テスト中です。これには、データベースとの対話が含まれます。

他の人にとっては、「単体テスト」という用語ははるかに緩やかであり、アプリケーションの統合部分をテストするテストコードを含む、あらゆる種類の自動テストを網羅しています。

テスト純粋主義者(その用語を使用する場合があります)は統合テストと呼ぶかもしれませんが、テストが複数の純粋に依存しているという理由で単体テストと区別されます- nit=コード。

「単体テスト」というより緩いバージョンを使用しているのではないかと思いますが、実際には「統合テスト」を指しています。

これらの定義を使用すると、「ユニットテスト」でテスト中のユニットの外部のコードに依存するべきではありません。ただし、「統合テスト」では、特定のコードを実行して、合格基準についてデータベースを検査するテストを作成することは完全に合理的です。

統合テストまたは単体テスト、あるいはその両方に依存すべきかどうかは、はるかに大きな議論のトピックです。

35
Eric King

答えはイエスとノーです...

個別に実行する独自のテストは、低レベルのコードが適切に機能しているという土台を築くことができるため、絶対に必要です。しかし、より大きなライブラリのコーディングでは、ユニットをまたがるテストが必要になるコードの領域も見つかります。

これらのクロスユニットテストは、コードカバレッジやエンドツーエンドの機能をテストする場合に適していますが、次の点に注意する必要があります。

  • 何が壊れているかを確認するための独立したテストがない場合、「クロスユニット」テストに失敗すると、コードの何が問題かを特定するための追加のトラブルシューティングが必要になります。
  • クロスユニットテストに依存しすぎると、SOLIDオブジェクト指向コードを作成するときに常に従うべきであるという契約の考え方から抜け出すことができます。分離テストは、ユニットが基本的なアクションを1つだけ実行します。
  • エンドツーエンドのテストが望ましいですが、テストでデータベースへの書き込みが必要な場合や、運用環境で実行したくないアクションを実行する必要がある場合は危険な場合があります。 Mockito のようなフレームワークをモックする多くの理由の1つは、オブジェクトを偽造してエンドツーエンドのテストを模倣して、実際にすべきでないものを変更することができるためです。

結局のところ、両方のいくつかを必要としています。低レベルの機能をトラップする多くのテストと、エンドツーエンドの機能をテストするいくつかのテスト。そもそもテストを書く理由に焦点を当ててください。これらは、コードが期待どおりに動作していることを確信させるためにあります。それを実現するために必要なことは何でも結構です。

悲しいが本当の事実は、自動化されたテストをまったく使用している場合は、すでに多くの開発者に足を踏み入れていることです。開発チームが厳しい締め切りに直面したとき、適切なテストプラクティスを最初に説明します。だから、あなたが自分の銃にこだわり、あなたのコードがどのように機能するかを意味のある反映である単体テストを書いている限り、私はあなたのテストがどれほど「純粋」であるかにこだわりません。

7
DanK

他のオブジェクトメソッドを使用して条件をテストする場合の問題の1つは、お互いをキャンセルするエラーを見逃してしまうことです。さらに重要なのは、難しいテスト実装の苦痛を見逃していて、その苦痛が基礎となるコードについて何かを教えていることです。痛みを伴うテストは、後で痛みを伴うメンテナンスを意味します。

ユニットを小さくし、クラスを分割し、リファクタリングして、テストしやすくなるまで再設計しますなし残りのオブジェクトを再実装します。コードまたはテストをこれ以上簡略化できないと思われる場合は、助けを求めてください。いつも運が良さそうな同僚のことを考えて、きれいにテストしやすい割り当てを取得してください。それは運ではないので、彼または彼女に助けを求めてください。

4
Karl Bielefeldt

少なくとも1つの非常に重要な理由がありますnotデータベースと対話するビジネスコードの単体テストで直接データベースアクセスを使用する:データベースの実装を変更した場合、それらすべてを書き直す必要がありますユニットテスト。列の名前を変更します。コードについては、データマッパー定義の1行を変更するだけです。ただし、テスト中にデータマッパーを使用しない場合は、この列を参照するすべての単体テストも変更する必要があります。これは、特に検索と置換が困難な、より複雑な変更の場合、非常に多くの作業になる可能性があります。

また、データベースと直接やり取りするのではなく、データマッパーを抽象的なメカニズムとして使用すると、データベースへの依存関係を完全に削除するが簡単になります。単体テストの数とそれらのすべてがデータベースにヒットしている場合、テストスイートは実行に数分かかることから数秒かかることに低下するため、それらを簡単にリファクタリングしてその依存関係を削除できることに感謝します。生産性。

あなたの質問は、あなたが正しい方向に沿って考えていることを示しています。ご想像のとおり、単体テストもコードです。コードベースの他の部分と同様に、それらを保守可能にし、将来の変更に容易に適応できるようにすることが重要です。それらを読み取り可能に保ち、重複を排除するように努めれば、はるかに優れたテストスイートが得られます。そして、良いテストスイートは慣れるものです。そして、慣れるテストスイートはエラーを見つけるのに役立ちますが、慣れないテストスイートは価値がありません。

1
Periata Breatta

単体テストと統合テストについて学んだときに私が学んだ最高の教訓は、メソッドをテストするのではなく、動作をテストすることです。つまり、このオブジェクトは何をするのでしょうか。

そのように見ると、データを永続化するメソッドとそれを読み戻す別のメソッドがテスト可能になり始めています。もちろん、具体的にメソッドをテストしている場合は、次のような各メソッドを個別にテストすることになります。

@Test
public void canSaveData() {
    writeDataToDatabase();
    // what can you assert - the only expectation you can have here is that an exception was not thrown.
}

@Test
public void canReadData() {
    // how do I even get data in there to read if I cannot call the method which writes it?
}

この問題は、テスト方法の観点から発生します。メソッドをテストしないでください。テスト動作。 WidgetDaoクラスの動作は何ですか?ウィジェットを永続化します。それでは、ウィジェットが永続することをどのように確認しますか?さて、永続化の定義は何ですか?それはあなたがそれを書いたとき、あなたはそれを再び読むことができることを意味します。したがって、読み取りと書き込みの両方がテストになり、私の意見では、より意味のあるテストになります。

@Test
public void widgetsCanBeStored() {
    Widget widget = new Widget();
    widget.setXXX.....
    // blah

    widgetDao.storeWidget(widget);
    Widget stored = widgetDao.getWidget(widget.getWidgetId());
    assertEquals(widget, stored);
}

これは論理的で、まとまりがあり、信頼性が高く、私の意見では、意味のあるテストです。

他の答えは、分離がいかに重要であるか、実用的か現実的か、そして単体テストがデータベースにクエリを実行するかどうかに焦点を当てています。それらは実際に質問に答えません。

何かを保存できるかどうかをテストするには、保存してから読み戻す必要があります。読み取りが許可されていない場合、何かが格納されているかどうかをテストすることはできません。データの取得とは別にデータの格納をテストしないでください。あなたは何も言わないテストで終わるでしょう。

1
Brandon

テストされる機能の単位が「データが永続的に格納され、検索可能である」である場合、実際のデータベースに格納し、データベースへの参照を保持しているオブジェクトを破棄し、コードを呼び出して、オブジェクトを戻します。

レコードがデータベースに作成されているかどうかをテストすることは、システムの他の部分に公開されている機能のユニットをテストするのではなく、ストレージメカニズムの実装の詳細に関係しているようです。

モックデータベースを使用してパフォーマンスを改善するためにテストを短縮することもできますが、そのようなテストに合格し、実際にはデータベースに何も保存していないシステムとの契約の最後に去った請負業者がいますシステムの再起動の間。

ユニットテストの「ユニット」が「コードのユニット」を表すのか、「機能のユニット」を表すのか、おそらく、多くのコードユニットが協調して作成した機能かどうかについて、議論することができます。これは有用な区別ではないと思います-私が心に留めておくべき質問は、「テストはシステムがビジネス価値を提供するかどうかについて何かを教えてくれますか」、「実装が変更された場合テストは脆弱ですか?」説明したようなテストは、システムをTDDしているときに役立ちます-「データベースレコードからオブジェクトを取得」をまだ書いていないため、機能の完全なユニットをテストすることはできませんが、実装の変更に対して脆弱なので、完全な操作がテスト可能になったら、それらを削除します。

1
Pete Kirkham

精神は正しいです。

理想的には、ユニットテストユニット(個々のメソッドまたは小さなクラス)をテストします。

理想的には、データベースシステム全体をスタブ化します。つまり、偽の環境でメソッドを実行し、正しいDB APIを正しい順序で呼び出すことを確認します。明確に、積極的にnotを実行して、独自のメソッドの1つをテストするときにDBをテストする必要があります。

メリットはたくさんあります。何よりも、正しいDB環境をセットアップして後でロールバックする必要がないので、テストはすぐに目がくらむようになります。

もちろん、これは、最初からこれを正しく行わないソフトウェアプロジェクトの大きな目標です。しかし、それはnitテストの精神です。

このアプローチとは異なる機能テスト、動作テストなどの他のテストがあることに注意してください。「テスト」と「単体テスト」を混同しないでください。

1
AnoE

これは私のやり方です。これは製品の結果に影響を与えるものではなく、製品の製造方法にのみ影響するため、これは個人的な選択です。

これは、テストしているアプリケーションなしでデータベースにモック値を追加できるかどうかに依存します。

次の2つのテストがあるとします。A-データベースの読み取りB-データベースへの挿入(Aに依存)

Aがパスした場合、Bの結果は依存関係ではなくBでテストされた部分に依存することになります。 Aが失敗した場合、Bに偽陰性がある可能性があります。エラーはBまたはAにある可能性があります。Aが成功を返すまで確実に知ることはできません。

アプリケーションの背後にあるDB構造を知ることができない(または変更される可能性がある)ため、単体テストでいくつかのクエリを記述しようとはしません。コード自体が原因で、テストケースが失敗する可能性があることを意味します。テスト用のテストを書いていないのに、誰がテスト用のテストをテストするのか...

0
AxelH

キャッチしたい失敗の例の1つは、テスト対象のオブジェクトがキャッシュレイヤーを使用しているが、必要に応じてデータを永続化できないことです。次に、オブジェクトにクエリを実行すると、「そうです、新しい名前とアドレスを取得しました」と表示されますが、実際には想定されていないため、テストを失敗させます。

あるいは、単一責任違反を見落とし、UTF-8でエンコードされたバージョンの文字列をバイト指向のフィールドに永続化する必要があるが、実際にはシフトJISは永続化するとします。他のいくつかのコンポーネントはデータベースを読み取る予定であり、UTF-8が表示されることを期待しているため、要件です。次に、このオブジェクトを介した往復により、Shift JISから変換されるため、正しい名前と住所が報告されますが、テストではエラーが検出されません。うまくいけば、後の統合テストで検出されるでしょうが、単体テストの全体のポイントは、問題を早期に発見し、どのコンポーネントが原因であるかを正確に知ることです。

それらの1つが想定どおりに動作しない場合、独自のテストケースは失敗し、修正してテストバッテリーを再度実行できます。

注意しないと、相互に依存する一連のテストを作成するため、これを想定することはできません。 「保存しますか?」 testはテストするsaveメソッドを呼び出し、次にloadメソッドを呼び出して、保存されたことを確認します。 「ロードされますか?」 testはsaveメソッドを呼び出してテストフィクスチャをセットアップし、次にテスト対象のloadメソッドを呼び出して結果を確認します。どちらのテストも、テストしていないメソッドの正当性に依存しています。つまり、どちらのテストも、テストしているメソッドの正当性を実際にテストしていません。

ここに問題があるという手がかりは、おそらく異なるユニットをテストしている2つのテスト実際には同じことを行うです。どちらもセッターを呼び出してからゲッターを呼び出し、結果が元の値であることを確認します。しかし、セッターとゲッターのペアが一緒に機能することではなく、セッターがデータを保持することをテストしたいとしました。何かが間違っていることがわかったら、何を見つけてテストを修正するだけです。

コードが単体テスト用に適切に設計されている場合、テスト対象のメソッドによってデータが実際に正しく永続化されているかどうかをテストするには、少なくとも2つの方法があります。

  • データベースインターフェースをモック化し、適切な関数が期待値とともに呼び出されたことをモックに記録させます。これは、メソッドが想定どおりに機能するかどうかをテストするものであり、古典的な単体テストです。

  • まったく同じ意図を使用して実際のデータベースに渡し、データが正しく保持されているかどうかを記録します。しかし、「うん、正しいデータが得られた」とだけ言う偽の関数を持つのではなく、テストはデータベースから直接読み取り、それが正しいことを確認します。これはpurestの可能性のあるテストではない可能性があります。データベースエンジン全体が美化されたモックを作成するために使用するのは非常に大きなものであり、何かがあったとしてもテストに合格する微妙さを見落とす可能性が高いためです。間違っています(たとえば、コミットされていないトランザクションが表示される可能性があるため、書き込みに使用されたのと同じデータベース接続を使用して読み取りを行うべきではありません)。しかし、それは正しいことをテストし、少なくともあなたはそれが正確にがモックコードを書く必要なしにデータベースインターフェース全体を実装することを知っています!

したがって、JDBCを使用してテストデータベースからデータを読み取るか、データベースをモックするかは、テスト実装の単なる詳細です。どちらの方法でも、同じクラスの他の誤ったメソッドと共謀して、何かが間違っている場合でも正しく見えるようにすると、ユニットを分離することで、ユニットをより適切にテストできるようになります。したがって、適切なデータが保持されていることを確認するための便利な手段を使用したいと思います以外私がテストしているメソッドのコンポーネントを信頼します。

コードがnotユニットテスト用に適切に設計されている場合は、メソッドをテストするオブジェクトがデータベースを注入された依存関係として受け入れない可能性があるため、選択肢がない可能性があります。その場合、テスト中のユニットを分離するための最良の方法についての議論は、テスト中のユニットを分離するのにどれだけ近いことが可能であるかについての議論に変わります。結論は同じです。障害のあるユニット間の陰謀を回避できる場合は、利用可能な時間や、コード内の障害を見つけるのにより効果的であると考えるその他の条件に従って実行します。

0
Steve Jessop