TDDを実行する場合、プライベートメソッドを避ける必要がありますか?


100

私はちょうどTDDを学んでいます。パブリックメソッドはオブジェクトの整合性を検証するのに十分な情報を提供するので、プライベートメソッドはテストできず、心配するべきではないというのが私の理解です。

私はしばらくの間OOPを理解しました。私の理解では、プライベートメソッドはオブジェクトをよりカプセル化するため、変更やエラーに対する耐性が高まります。したがって、デフォルトで使用する必要があり、クライアントにとって重要なメソッドのみを公開する必要があります。

さて、プライベートメソッドのみを持ち、イベントをリッスンすることで他のオブジェクトとやり取りするオブジェクトを作成することは可能です。これは非常にカプセル化されますが、完全にはテストできません。

また、テストのためにメソッドを追加するのは悪い習慣と考えられています。

これは、TDDがカプセル化と対立することを意味しますか?適切なバランスは何ですか?私は今、私のメソッドのほとんどまたはすべてを公開したいと思っています...


9
ソフトウェア業界の悪い習慣と現実は異なる動物です。理想的な状況は、多くの場合、ビジネスの世界のゆがんだ現実です。理にかなっていることを実行し、アプリケーション全体でそれに従ってください。今月のフレーバーがアプリケーション全体に広がるのではなく、むしろ悪いプラクティスが必要です。
アーロンマクアイバー

10
「プライベートメソッドはテストできません」?どの言語?一部の言語では不便です。他の言語では完全にシンプルです。また、カプセル化の設計原則は常に多くのプライベートメソッドで実装する必要があると言っていますか?それは少し極端に思えます。一部の言語にはプライベートメソッドがありませんが、まだデザインがうまくカプセル化されているようです。
-S.Lott

「プライベートメソッドはオブジェクトをよりカプセル化するため、変更やエラーに対する抵抗力が増すことを理解しています。したがって、デフォルトで使用し、クライアントにとって重要なメソッドのみを公開する必要があります。」これは、TDDが何を達成しようとしているのかという反論のように思えます。TDDは、シンプルで実用的でオープンに変更可能な設計を作成するための開発手法です。「非公開から」と「公開のみ」の検索は完全に方向転換されます。TDDを採用するためのプライベートメソッドなどはありません。後で、必要に応じて実行します。リファクタリングの一環として。
ハービー


@gnatあなたは、これはこの質問に対する私の答えから生じた質問の複製として閉じられるべきだと思いますか?* 8 ')
マークブース

回答:


50

実装のテストよりもインターフェイスのテストを優先します。

プライベートメソッドはテストできないことは私の理解です

これは開発環境によって異なります。以下を参照してください。

[プライベートメソッド]は、パブリックAPIがオブジェクトの整合性を検証するのに十分な情報を提供するため、心配する必要はありません。

そうです、TDDはインターフェイスのテストに重点を置いています。

プライベートメソッドは、リファクタリングサイクル中に変更される可能性がある実装の詳細です。インターフェイスまたはブラックボックスの動作を変更せずにリファクタリングすることが可能であるべきです。実際、それはTDDの利点の一部であり、クラス内部の変更がそのクラスのユーザーに影響を与えないという自信を簡単に生成できることです。

さて、プライベートメソッドのみを持ち、イベントをリッスンすることで他のオブジェクトとやり取りするオブジェクトを作成することは可能です。これは非常にカプセル化されますが、完全にはテストできません。

クラスは何のpublicメソッドを持っていない場合でも、イベントハンドラは、それはですされているのですパブリックインターフェイス、およびそれにそのに対してその パブリックインターフェイスあなたがテストすることができます。

イベントはインターフェースなので、そのオブジェクトをテストするために生成する必要があるのはイベントです。

テストシステムの接着剤としてモックオブジェクトを使用することを検討してください。イベントを生成し、結果として生じる状態の変化を取得する単純なモックオブジェクトを作成できるはずです(別のレシーバーモックオブジェクトによって可能)。

また、テストのためにメソッドを追加するのは悪い習慣と考えられています。

絶対に、内部状態を公開することに非常に注意する必要があります。

これは、TDDがカプセル化と対立することを意味しますか?適切なバランスは何ですか?

絶対違う。

TDDは、クラスの実装を(おそらく前の時点からYAGNIを適用することによって)単純化する以外に変更すべきではありません。

TDDを使用したベストプラクティスは、TDDを使用しないベストプラクティスと同じです。開発中のインターフェイスを使用しているため、その理由がすぐにわかります。

私は今、私のメソッドのほとんどまたはすべてを公開したいと思っています...

これはむしろ、お風呂の水で赤ちゃんを捨てるでしょう。

TDD方式で開発できるように、すべてのメソッドをパブリックにする必要はありません。以下のメモを参照して、プライベートメソッドが実際にテストできないかどうかを確認してください。

プライベートメソッドのテストの詳細

言語/環境に応じて、クラスのプライベートな動作を絶対に単体テストする必要がある場合、3つのオプションがあります。

  1. テストするクラスにテストを配置します。
  2. テストを別のクラス/ソースファイルに配置し、テストするプライベートメソッドをパブリックメソッドとして公開します。
  3. テスト環境を使用して、テストコードと本番コードを別々に保ちながら、テストコードが本番コードのプライベートメソッドにアクセスできるようにします。

明らかに、3番目のオプションが最適です。

1)テストしたいクラスにテストを配置します(理想的ではありません)

テスト対象の製品コードと同じクラス/ソースファイルにテストケースを保存するのが最も簡単なオプションです。しかし、多くのプリプロセッサディレクティブや注釈がないと、テストコードが不必要に製品コードを肥大化させることになり、コードの構造に応じて、そのコードのユーザーに内部実装を誤って公開してしまう可能性があります。

2)テストするプライベートメソッドをパブリックメソッドとして公開します(実際にはお勧めできません)

これが非常に悪い習慣であることが示唆されているように、カプセル化を破壊し、コードのユーザーに内部実装を公開します。

3)より良いテスト環境を使用します(使用可能な場合は最適なオプション)

Eclipseの世界では、フラグメントを使用して3.を実現できます。C#の世界では、部分クラスを使用する場合があります。多くの場合、他の言語/環境にも同様の機能があります。見つける必要があるだけです。

1.または2.のみをオプションとして想定すると、テストコードや、汚いリネンを公共の場で洗い流す厄介なクラスインターフェースで本番ソフトウェアが肥大化する可能性があります。* 8 ')

  • 全体として-プライベート実装に対してテストしない方がはるかに優れています。

5
あなたが提案する3つのオプションのいずれかに同意するかどうかはわかりません。前に言ったようにパブリックインターフェイスのみをテストするのが私の好みですが、そうすることでプライベートメソッドが実行されることを確認します。そうすることの利点の1つは、デッドコードを見つけることです。これは、テストコードに言語の通常の使用を強制的に中断させる場合には起こりそうにありません。
メイガス14年

メソッドは1つのことを行う必要があり、テストでは実装を決して考慮すべきではありません。プライベートメソッドは実装の詳細です。パブリックメソッドのみをテストすると、テストが統合テストである場合、設計上の問題があります。
メイガス14年

メソッドをデフォルト/保護し、同じパッケージのテストプロジェクトでテストを作成するのはどうですか?
リチャードサイファー

@RichardCypherこれは2)と実質的に同じです。テスト環境の欠陥に対応するためにメソッド仕様を理想的に変更しているため、間違いなく実践が不十分です。
マークブース

75

もちろん、プライベートメソッドを持つこともできますし、もちろんテストすることもできます。

そこにあるいずれかのいくつかの実行するためのプライベートメソッドを取得する方法は、あなたがそのようにテストすることができ、その場合には、または存在しないただ、なぜ一体あなたはそれをテストしようとしている:実行するためのプライベートを取得する方法は、その場合、いまいましいものを削除してください!

あなたの例では:

さて、プライベートメソッドのみを持ち、イベントをリッスンすることで他のオブジェクトとやり取りするオブジェクトを作成することは可能です。これは非常にカプセル化されますが、完全にはテストできません。

なぜそれはテストできないのでしょうか?メソッドがイベントに反応して呼び出された場合、テストにオブジェクトに適切なイベントを供給してください。

それはプライベートメソッドを持たないことではなく、カプセル化を壊さないことです。プライベートメソッドを使用できますが、パブリックAPIを使用してテストする必要があります。パブリックAPIがイベントに基づいている場合は、イベントを使用します。

プライベートヘルパーメソッドのより一般的なケースについては、それらを呼び出すパブリックメソッドを通じてテストできます。特に、失敗したテストに合格するためのコードの記述のみが許可されており、テストはパブリックAPIをテストしているため、記述するすべての新しいコードは通常パブリックになります。プライベートメソッドは、Extract Method Refactoringの結果としてのみ表示されます。既存のパブリックメソッドから抽出された場合のみです。ただし、その場合、パブリックメソッドはプライベートメソッドを呼び出すため、パブリックメソッドをテストする元のテストはプライベートメソッドもカバーします。

そのため、通常、プライベートメソッドは、既にテスト済みのパブリックメソッドから抽出されたときにのみ表示されるため、既にテスト済みです。


3
パブリックメソッドによるテストは、99%の時間でうまく機能します。課題は、単一のパブリックメソッドの背後に数百または数千行の複雑なコードがあり、すべての中間状態が実装固有である時間の1%です。パブリックメソッドからのすべてのエッジケースをヒットしようとして十分に複雑になると、せいぜい苦痛になります。あるいは、カプセル化を解除してメソッドをプライベートとして公開するか、クラッジを使用してテストでプライベートメソッドを直接呼び出すことにより、エッジケースをテストすると、ttleいだけでなく、脆弱なテストケースになります。
ダンニーリー

24
大規模で複雑なプライベートメソッドは、コードのにおいです。実装が非常に複雑であるため、コンポーネント部分に(パブリックインターフェイスを使用して)便利に分解できない場合、テストおよび潜在的な設計およびアーキテクチャの問題が明らかになります。プライベートコードが巨大なケースの1%は、通常、分解して公開するための手直しの恩恵を受けます。
-S.Lott

13
@Dan Neelyのようなコードは、とにかくテストできません。そして、単体テストの記述の一部がこれを指摘しています。状態を排除し、クラスを分割し、すべての典型的なリファクタリングを適用してから、単体テストを作成します。また、TDDを使用して、どのようにしてそのポイントに到達しましたか?これはTDDの利点の1つであり、テスト可能なコードの作成が自動化されます。
ビルK

少なくともクラスinternal内のメソッドまたはパブリックメソッドは、internal頻繁に直接テストする必要があります。幸いなことに.netはをサポートしていますがInternalsVisibleToAttribute、それがなければ、これらのメソッドをテストするのはPITAになります。
-CodesInChaos

25

コードで新しいクラスを作成するとき、いくつかの要件に答えるためにそれを行います。要件は、コードがどのように実行する必要があるかではなく、方法を指定しますこれにより、ほとんどのテストがパブリックメソッドレベルで行われる理由を簡単に理解できます。

テストを通じて、コードが期待どおりに動作すること、期待されるときに適切な例外をスローすることなどを検証します。開発者がコードを実装する方法はあまり気にしません。実装、つまりコードがそれを行う方法を気にしませんが、プライベートメソッドのテストを避けることは理にかなっています。

パブリックメソッドを持たず、イベントを介してのみ外部と対話するクラスのテストについては、テスト、イベントを送信し、応答をリッスンすることでテストすることもできます。たとえば、クラスがイベントを受信するたびにログファイルを保存する必要がある場合、単体テストはイベントを送信し、ログファイルが書き込まれたことを確認します。

最後になりましたが、プライベートメソッドをテストすることが完全に有効な場合もあります。たとえば.NETでは、ソリューションがパブリックメソッドほど単純ではない場合でも、パブリッククラスだけでなくプライベートクラスもテストできます。


4
+1 TDDの重要な機能の1つは、METHODSが自分の考えていることを行うことをテストするのではなく、REQUIREMENTSが満たされていることをテストすることを強制することです。したがって、「プライベートメソッドをテストできますか?」という質問は、TDDの精神に少し反しています。代わりに、「実装にプライベートメソッドが含まれる要件をテストできますか?」そして、この質問に対する答えは明らかにイエスです。
ダウッドイブンカリーム

6

プライベートメソッドはテストできないことは私の理解です

私はその声明に同意しません。または、プライベートメソッドを直接テストしないと言います。パブリックメソッドは、異なるプライベートメソッドを呼び出す場合があります。著者は「小さな」メソッドを望み、コードの一部を巧妙に名付けられたプライベートメソッドに抽出したかもしれません。

パブリックメソッドの記述方法に関係なく、テストコードはすべてのパスをカバーする必要があります。テスト後に、1つのプライベートメソッドのブランチステートメント(if / switch)の1つがテストでカバーされていないことがわかった場合、問題があります。ケースを見逃して実装が正しいか、実装が間違っているか、そのブランチが実際に存在することはなかったはずです。

私がCoberturaとNCoverを頻繁に使用する理由は、公開メソッドテストがプライベートメソッドもカバーするようにするためです。プライベートメソッドを使用して適切なOOオブジェクトを自由に記述し、そのような問題でTDD /テストを邪魔しないでください。


5

CUTが対話するインスタンスを提供するためにDependency Injectionを使用している限り、サンプルは完全にテスト可能です。次に、モックを使用して目的のイベントを生成し、CUTがその依存関係に対して正しいアクションを実行するかどうかを観察します。

一方、イベントサポートが良好な言語を使用している場合は、少し異なる方法を使用できます。オブジェクトがイベント自体をサブスクライブするのは好きではなく、オブジェクトを作成するファクトリがオブジェクトのパブリックメソッドにイベントを結び付けます。テストが簡単になり、CUTをテストする必要があるイベントの種類を外部から確認できるようになります。


それは素晴らしいアイデアです。「...オブジェクトを作成するファクトリにイベントをオブジェクトのパブリックメソッドに結び付けます。テストが簡単で、CUTをテストする必要のあるイベントの種類を外部から見えるようにします。 」
子犬

5

プライベートメソッドを使用して放棄する必要はありません。それらを使用することは完全に合理的ですが、テストの観点からは、カプセル化を壊したり、クラスにテスト固有のコードを追加したりせずに直接テストすることは困難です。秘Theは、コードを汚してしまったように感じるので、あなたが腸を震わせることを知っているものを最小限にすることです。

これらは、実行可能なバランスをとるために心がけていることです。

  1. 使用するプライベートメソッドとプロパティの数を最小限にします。クラスに必要なもののほとんどは、とにかく公開される必要がある傾向があるので、その賢いメソッドを本当にプライベートにする必要があるかどうかを考えてください。
  2. プライベートメソッドのコードの量を最小限に抑える-とにかくこれを行う必要があります-他のメソッドの動作を介して間接的にテストできます。100%のテストカバレッジを期待することは決してありません。おそらく、デバッガーを介していくつかの値を手で確認する必要があります。プライベートメソッドを使用して例外をスローすることは、間接的に簡単にテストできます。プライベートプロパティは、手動でテストするか、別の方法でテストする必要がある場合があります。
  3. 間接的または手動のチェックがうまくいかない場合は、保護されたイベントを追加し、インターフェースを介してアクセスして、プライベートなものをいくつか公開します。これにより、カプセル化の規則が効果的に「曲がり」ますが、テストを実行するコードを実際に出荷する必要がなくなります。欠点は、これにより、必要に応じてイベントが発生することを確認するための内部コードが少し追加される可能性があることです。
  4. パブリックメソッドが十分に「安全」ではないと感じる場合は、メソッドに何らかの検証プロセスを実装して、使用方法を制限する方法があるかどうかを確認してください。メソッドを実装するためのより良い方法を考えているか、別のクラスが形になり始めているのを見て、これを考えている可能性があります。
  5. パブリックメソッドの「処理」を行うプライベートメソッドが多数ある場合、新しいクラスが抽出されるのを待っている可能性があります。これを個別のクラスとして直接テストできますが、それを使用するクラス内でコンポジットとしてプライベートに実装できます。

横に考えます。クラスを小さくし、メソッドを小さくし、多くの構成を使用します。仕事のように聞こえますが、最終的にはより個別にテスト可能な項目になり、テストがより簡単になります。実際の大きくて複雑なオブジェクトの代わりにシンプルなモックを使用するオプションが増えます。ファクタリングされた疎結合コード、さらに重要なことは、より多くのオプションを自分に与えることです。物事を小さく保つことは、各クラスで個別にチェックする必要があるものの数を減らし、クラスが大きくなりたくさんあるときに時々発生する可能性のあるコードスパゲッティを自然に減らす傾向があるため、最終的に時間を節約する傾向があります内部的に相互依存するコードの動作。


4

さて、プライベートメソッドのみを持ち、イベントをリッスンすることで他のオブジェクトとやり取りするオブジェクトを作成することは可能です。これは非常にカプセル化されますが、完全にはテストできません。

このオブジェクトはこれらのイベントにどのように反応しますか?おそらく、他のオブジェクトのメソッドを呼び出す必要があります。これらのメソッドが呼び出されるかどうかを確認することでテストできます。モックオブジェクトを呼び出すようにすると、期待どおりに動作することを簡単にアサートできます。

問題は、オブジェクトと他のオブジェクトとの相互作用のみをテストすることです。オブジェクト内で何が起こっているかは気にしません。そのため、以前よりもパブリックメソッドは必要ありません。


4

私もこれと同じ問題に苦しんでいます。本当に、それを回避する方法はこれです:あなたのプログラムの残りがそのクラスとインターフェースすることをどのように期待しますか?それに応じてクラスをテストします。 これにより、プログラムの残りの部分がどのようにインターフェイスするかに基づいてクラスを設計する必要があり、実際、クラスのカプセル化と優れた設計を促進します。


3

個人使用のデフォルト修飾子の代わりに。その後、パブリックメソッドだけでなく、これらのメソッドを個別にテストできます。これには、テストがメインコードと同じパッケージ構造を持っている必要があります。


...これがJavaであると仮定します。
ダウッドイブンカリーム

またはinternal.netで。
CodesInChaos

2

通常、いくつかのプライベートメソッドは問題になりません。コードがパブリックメソッドにインライン化されているかのように、パブリックAPIを介してテストするだけです。過剰なプライベートメソッド、凝集度が低いことを示している可能性あります。クラスにはまとまりのある責任が1つある必要があり、多くの場合、メソッドはプライベートになり、実際には存在しないまとまりのある外観が得られます。

たとえば、これらのイベントに応答して多くのデータベース呼び出しを行うイベントハンドラーがあるとします。データベースコールを行うためにイベントハンドラをインスタンス化することは明らかに悪い習慣であるため、データベース関連のすべての呼び出しをプライベートメソッドにし、それらを実際に別のクラスにプルする必要があります。


2

これは、TDDがカプセル化と対立することを意味しますか?適切なバランスは何ですか?私は今、私のメソッドのほとんどまたはすべてを公開したいと思っています。

TDDはカプセル化と対立しません。選択した言語に応じて、ゲッターメソッドまたはプロパティの最も単純な例を取り上げます。Customerオブジェクトがあり、Idフィールドが必要だとしましょう。最初に書くテストは、「customer_id_initializes_to_zero」のようなものです。未実装の例外をスローするようにゲッターを定義し、テストが失敗するのを監視します。次に、そのテストパスを作成するためにできる最も簡単なことは、ゲッターがゼロを返すようにすることです。

そこから、他のテストに進みます。おそらく、実際の機能フィールドである顧客IDを含むテストです。ある時点で、ゲッターによって返されるものを追跡するために顧客クラスが使用するプライベートフィールドを作成する必要があります。これを正確に追跡するにはどうすればよいですか?それは単純なバッキングintですか?文字列を追跡してからintに変換しますか?20個のintを追跡し、それらを平均しますか?外の世界は気にしません-あなたのTDDテストは気にしません。それはカプセル化された詳細です。

TDDを開始するとき、これは必ずしもすぐに明らかになるとは限らないと思います-あなたはメソッドが内部的に何をするのかをテストしているのではなく、クラスのより粒度の低い懸念をテストしているのです。したがって、メソッドDoSomethingToFoo()がBarをインスタンス化し、そのメソッドを呼び出し、そのプロパティの1つに2を追加するなどをテストすることを探しているわけではありません。 (か否か)。これがテストの一般的なパターンです。「テスト対象のクラスにXを実行すると、その後Yを監視できます」。Yに到達する方法はテストの懸念事項ではなく、これがカプセル化されるものであり、これがTDDがカプセル化と対立しない理由です。


2

使用を避ける?いいえ。?で始まることは
避けてください はい。

TDDで抽象クラスを使用しても大丈夫かどうかについて質問していないことに気付きました。TDD中に抽象クラスがどのように出現するかを理解すれば、同じ原則がプライベートメソッドにも適用されます。

プライベートメソッドを直接テストできないように、抽象クラスのメソッドを直接テストすることはできませんが、それが抽象クラスとプライベートメソッドから始めない理由です。具体的なクラスとパブリックAPIから始めてから、一般的な機能をリファクタリングします。

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