テスト可能なコードの作成と投機的一般性の回避


11

私は今朝いくつかのブログ投稿を読んでいて、これに出くわしました

Customerインターフェースを実装する唯一のクラスがCustomerImplである場合、実際には実行時に代用するものがないため、ポリモーフィズムと代替性はありません。それは偽の一般性です。

インターフェースを実装すると複雑さが増し、実装が1つしかない場合は、不必要な複雑さが加わると主張するかもしれません。必要以上に抽象的なコードを記述することは、「投機的一般性」と呼ばれるコードの匂いと見なされることがよくあります(投稿でも言及されています)。

しかし、TDDを使用している場合、インターフェイス実装または他のポリモーフィックオプションの形式であるかどうかにかかわらず、クラスを継承可能にし、そのメソッドを仮想にすることで、その投機的な一般性なしにテストダブルを作成することはできません。

それでは、このトレードオフをどのように調整するのでしょうか?テスト/ TDDを促進するために投機的に一般的であることは価値がありますか?テストdoubleを使用している場合、これらは2番目の実装としてカウントされ、一般性が推測されなくなりますか?具体的な協力者のモックを可能にする、よりヘビーなモックフレームワークを検討する必要があります(たとえば、C#の世界でのMolesとMoq)。または、具体的なクラスでテストし、デザインが自然にポリモーフィズムを必要とするときまで、「統合」テストと考えられるものを記述する必要がありますか?

私はこの問題に関する他の人々の見解を読んでみたいです-あなたの意見を事前に感謝します。


個人的には、実体はm笑されるべきではないと思います。コードドメインコードは通常、サービスが実装されているコードへの参照を持たないため、サービスをモックするだけです。
CodesInChaos

7
動的に型付けされた言語のユーザーは、静的に型付けされた言語があなたに課しているチェーンでの苦労を笑います。これは、動的に型付けされた言語でのユニットテストを容易にする1つのことです。テストの目的でオブジェクトを置き換えるためにインターフェイスを開発する必要はありません。
ウィンストンイーバート

インターフェイスは、一般性を実現するためだけに使用されるわけではありません。これらは多くの目的に使用され、コードの分離は重要なものの1つです。これにより、コードのテストが非常に簡単になります。
マルジャンヴェネマ

@WinstonEwertこれは、動的型付けの興味深い利点です。これは、あなたが指摘しているように、通常動的型付け言語では動作しない人とは考えていませんでした。
エリックディートリッヒ

@CodeInChaosこの質問の目的のための区別は考慮していませんでしたが、それは合理的な区別です。この場合、(現在の)実装が1つだけのサービス/フレームワーククラスを考えることができます。DAOを使用してアクセスするデータベースがあるとします。二次永続性モデルを作成するまで、DAOを接続しないでください。(それはブログ投稿の著者が示唆していることのようです)
エリックディートリッヒ

回答:


14

私は行ってブログの投稿を読みましたが、著者が言ったことの多くに同意します。あなたはユニットテストの目的のためのインターフェイスを使用してコードを書いている場合は、私はインターフェイスのモック実装がいることを言うと思いますです、あなたの第2の実施。特にそうしないことのトレードオフによってクラスが密結合され、テストが困難になる場合は、コードに複雑さの点で実際にはあまり追加しないと主張します。


3
絶対的に正しい。コードのテスト、設計、実装、保守などを正しく行うために必要なため、アプリケーションの一部です。顧客に出荷しないという事実は無関係です。テストスイートに2つ目の実装がある場合、一般性あり、それを収容する必要があります。
キリアンフォス

1
これは私が最も説得力があると思う答えです(そして、@ KilianFothは、コードに2番目の実装が含まれているかどうかはまだ存在していると付け加えています)。ただし、回答を少し受け入れて、他の誰かがチャイムを鳴らすかどうかを確認します。
エリックディートリッヒ

また、テストのインターフェイスに依存している場合、一般性はもはや投機的ではありません
ピート

(インターフェイスを使用して)「そうしない」では、クラスが密結合されることはありません。そうではありません。たとえば、.NET FrameworkにはStreamクラスがありますが、密結合はありません。
ルークピュレット

3

一般的なコードのテストは簡単ではありません。もしそうなら、私たちはずっと前にそれをやっていました、そして、過去10-15年だけでそれのそのような少しの取引をしませんでした。最大の困難の1つは、カプセル化を壊すことなく、まとまりがあり、十分にファクタリングされ、テスト可能なコードをテストする方法を常に決定することでした。BDDプリンシパルは、ほぼ完全に動作に焦点を当てていることを示唆しており、いくつかの点で、内部の詳細をそれほど大きく心配する必要はないことを示唆しているように見えますが、非常に隠された方法で「スタッフ」を行う多くのプライベートメソッド。テストの全体的な複雑さが増し、より一般的なレベルですべての可能な結果を​​処理できるためです。

モッキングはある程度役立ちますが、やはりかなり外部的に焦点が当てられています。依存性注入はまた、モックまたはテストダブルで非常にうまく機能しますが、これはまた、インターフェイスを介して、または非表示のままにすることを好むかもしれない直接のいずれかで要素を公開することを必要とする可能性があります-これは特にそうする場合に当てはまりますシステム内の特定のクラスについて、偏執的なレベルのセキュリティを確保します。

私にとっては、より簡単にテストできるようにクラスを設計するかどうかについて、審査員はまだ出ていません。これにより、レガシーコードを維持しながら新しいテストを提供する必要がある場合に問題が発生する可能性があります。システム内のすべてを完全にテストできるはずですが、クラスのプライベート内部を(間接的であっても)公開するという考えは好きではありません。

私にとって、解決策は常にかなり実用的なアプローチを取り、特定の状況に合わせて多くの手法を組み合わせることでした。多くの継承されたテストダブルを使用して、テストの内部プロパティと動作を公開します。クラスにアタッチできるものすべてをモックします。クラスのセキュリティを損なわない場合は、テストのために動作をオーバーライドまたは注入する手段を提供します。コードをテストする機能の改善に役立つ場合は、よりイベント駆動型のインターフェイスを提供することも検討します。

「テスト不可能な」コードを見つけた場合は、テストしやすくするためにリファクタリングできるかどうかを確認します。多くのプライベートコードが舞台裏で隠されている場合、多くの場合、新しいクラスが分割されるのを待っています。これらのクラスは内部で使用される場合がありますが、多くの場合、より少ないプライベートな動作で独立してテストできます。ただし、テストコードを組み込んだプロダクションコードを記述することは避けてください。「テストラグ」を作成するとif testing then ...、テストの問題が完全に解体されておらず、完全に解決されていないことを示します。

Gerard MeszarosのxUnit Test Patternsの本を読むと役立つかもしれません。この本では、この種のすべてをここで説明するよりもはるかに詳細に扱っています。私はおそらく彼が提案するすべてのことをするわけではありませんが、扱いにくいテスト状況のいくつかを明確にするのに役立ちます。結局のところ、好みの設計を適用しながらテスト要件を満たせるようにしたいので、妥協する必要があるかもしれない場所をより適切に決定するために、すべてのオプションをよりよく理解するのに役立ちます。


1

使用する言語には、テスト用にオブジェクトを「モック」する方法がありますか?その場合、これらの迷惑なインターフェースはなくなる可能性があります。

別の注意として、SimpleInterfaceと、それを実装する唯一のComplexThingがある理由があります。SimpleInterfaceユーザーがアクセスしたくないComplexThingの一部が存在する可能性があります。それは、過剰なオブジェクト指向のコーダーが常に原因であるとは限りません。

ここから離れて、これを実行するコードが「悪臭を放つ」という事実に誰もがジャンプできるようにします。


はい、具体的なオブジェクトのモックをサポートするモックフレームワークを使用して言語で作業しています。これらのツールを使用するには、さまざまな程度でフープをジャンプする必要があります。
エリックディートリッヒ

0

私は2つの部分で答えます:

  1. テストにのみ興味がある場合は、インターフェイスは必要ありません。私はその目的のためにモックフレームワークを使用します(Javaの場合:Mockitoまたはeasymock)。設計するコードは、テスト目的で変更しないでください。テスト可能なコードを書くことは、モジュラーコードを書くことと同等です。そのため、私はモジュラー(テスト可能な)コードを書き、コードパブリックインターフェイスのみをテストする傾向があります。

  2. 私は大きなJavaプロジェクトに取り組んできたと私は深く、読み取り専用(のみゲッター)のインターフェイスを使用することがあると確信になってきています(私は不変の大ファンだということに注意してください)移動するための方法。実装クラスにはセッターが含まれる場合がありますが、これは実装の詳細であり、外部層に公開されるべきではありません。別の観点から、私は継承よりも合成を好むので(モジュール性、覚えていますか?)、インターフェースもここで役立ちます。私は自分の足を撃つよりも投機的な一般性の代価を払っても構わないと思っています。


0

ポリモーフィズム以外のインターフェイスへのプログラミングを開始して以来、多くの利点がありました。

  • クラスのインターフェイス(パブリックメソッド)と、他のクラスのインターフェイスとどのようにやり取りするかについて事前に一生懸命に考える必要があります。
  • よりまとまりがあり、単一の責任原則に従うより小さなクラスを書くのに役立ちます。
  • コードをテストする方が簡単です
  • クラスはインスタンスレベルである必要があるため、静的クラス/グローバル状態が少なくなります
  • プログラム全体の準備ができる前に、ピースを統合およびアセンブリするのが簡単
  • オブジェクト構築とビジネスロジックを分離する依存性注入

多くの人々は、少数の大きなクラスよりも多くの小さなクラスの方が優れていることに同意するでしょう。一度にそれほど集中する必要はなく、各クラスには明確に定義された目的があります。他の人は、より多くのクラスを持つことで複雑さを増していると言うかもしれません。

ツールを使用して生産性を向上させるのは良いことですが、コードにテスト性とモジュール性を直接組み込む代わりに、Mockフレームワークなどのみに依存すると、長期的にはコードの品質が低下します。

全体として、高品質のコードを書くのに役立ったと信じており、その利点は結果を大きく上回ります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.