プライベートメソッドのユニットテストの必要性を回避する方法


15

私はあなたがプライベートメソッドをテストすることになっていないことを知っています、そしてそれがあなたが必要であるように見えるなら、そこに出てくるのを待っているクラスがあるかもしれません。

しかし、パブリックインターフェイスをテストできるようにするためだけに数十億のクラスを持ちたくはありません。パブリックメソッドをテストするだけで多くの依存関係をモックする必要があり、ユニットテストは多くのクラスで行われます。巨大で追跡が難しい。

パブリックメソッドをテストする場合はプライベートメソッドをモックし、プライベートメソッドをテストする場合は外部依存関係をモックすることをお勧めします。

私は夢中ですか?


完全な単体テストは、特定のクラスのすべてのプライベートメンバーを暗黙的にカバーする必要があります。なぜなら、それらを直接呼び出すことはできませんが、それらの動作は出力に影響を与えるからです。そうでない場合、なぜ彼らはそもそもそこにいるのですか?単体テストでは、結果がどのように到達したかではなく、結果が重要であることに注意してください。
GordonM

回答:


23

あなたは部分的に正しいです- プライベートメソッドを直接テストするべきではありません。クラスのプライベートメソッドは、1つ以上のパブリックメソッドによって呼び出される必要があります(おそらく間接的に-パブリックメソッドによって呼び出されるプライベートメソッドは、他のプライベートメソッドを呼び出すことができます)。したがって、パブリックメソッドをテストするときは、プライベートメソッドもテストします。テストされていないプライベートメソッドがある場合、テストケースが不十分であるか、プライベートメソッドが使用されていないため削除できます。

ホワイトボックステストアプローチを採用している場合は、パブリックメソッドを中心に単体テストを構築するときに、プライベートメソッドの実装の詳細を考慮する必要があります。ブラックボックスアプローチを採用している場合、パブリックメソッドまたはプライベートメソッドの実装の詳細に対してテストするのではなく、予想される動作に対してテストする必要があります。

個人的に、私は単体テストにホワイトボックスアプローチを好みます。テストを作成して、テスト中のメソッドとクラスを、パブリックメソッドとプライベートメソッドで興味深い動作を引き起こすさまざまな状態に設定し、結果が期待どおりであることをアサートできます。

だから-あなたのプライベートメソッドをモックしないでください。それらを使用して、提供する機能を適切にカバーするためにテストする必要があるものを理解します。これは、単体テストレベルで特に当てはまります。


1
ええ、私はあなたがクレイジーに細かい細かいラインの上に交差している可能性があり、それらをからかっている場合、テストを理解することができます「あなたのプライベートメソッドをモックしていない」
ユアン・

ええ、しかし、私が言ったように、ホワイトボックステストの私の問題は、通常、パブリックメソッドとプライベートメソッドのすべての依存関係をモックすると、本当に大きなテストメソッドになることです。それに対処する方法はありますか?
フランSEVILLANO

8
@FranSevillanoそんなにスタブやモックが必要な場合は、全体的なデザインを見ていきます。何か気分が悪い。
トーマスオーエンズ

これには、コードカバレッジツールが役立ちます。
アンディ

5
@FranSevillano:1つのことを行うクラスが多くの依存関係を持つ理由はあまりありません。依存関係がたくさんある場合は、おそらくGodクラスがあります。
Mooingダック

4

これは非常に悪い考えだと思います。

プライベートメンバーの単体テストを作成する際の問題は、製品のライフサイクルにあまり適合しないことです。

これらのメソッドをプライベートにすることを選択した理由は、クラスが実行しようとしていることの中心ではなく、その機能を現在どのように実装するかのヘルパーにすぎないためです。リファクタリングすると、これらのプライベートな詳細が変更される可能性が高く、リファクタリングとの摩擦が生じます。

また、パブリックメンバーとプライベートメンバーの主な違いは、パブリックAPIを慎重に検討し、それを適切に文書化し、よく確認する(アサーションなど)必要があることです。しかし、プライベートAPIの場合、慎重に考えることは無意味です(その使用法は非常にローカライズされているため、無駄な努力です)。プライベートメソッドを単体テストに入れると、それらのメソッドに外部依存関係が作成されます。APIの意味は安定しており、十分に文書化されている必要があります(誰かがそれらのユニットテストが失敗した場合、失敗した理由を把握する必要があるため)。

私はあなたをお勧めします:

  • これらのメソッドをプライベートにするかどうかを再考する
  • 一度だけのテストを作成します(今すぐ正しいことを確認するために、テストできるように一時的に公開して、テストを削除します)
  • #ifdefおよびアサーションを使用して、実装に条件付きテストを組み込みました(テストはデバッグビルドでのみ実行されます)。

この機能をテストする傾向があり、現在のパブリックAPIを使用してテストするのは難しいことを感謝します。しかし、テストカバレッジよりもモジュール性を個人的に重視しています。


6
同意しません。IMO、これは統合テストに対して100%正しいです。しかし、単体テストでは状況が異なります。単体テストの目標は、バグがどこにあるかを正確に特定し、バグをすばやく修正できるようにすることです。私は頻繁にこの状況にいることに気づきます:パブリックメソッドが非常に少ないのは、それが本当にクラスの中心的な価値だからです(あなたが言ったように)。ただし、パブリックでもプライベートでもない400行のメソッドを記述することは避けます。そのため、私の少数のパブリックメソッドは、数十のプライベートメソッドの助けを借りなければ目標を達成できません。「すぐに修正」するにはコードが多すぎます。デバッガーなどを起動する必要があります
。– marstato

6
@marstato:提案:最初にテストの記述を開始し、ユニットテストについて気を変えてください。バグは検出されませんが、開発者が意図したとおりにコードが機能することを確認してください。
ティモシートラックル

@marstatoありがとう!はい。ものをチェックインするとき(またはチェックインしなかった場合!)、テストは常に最初に合格します。これらは、コードを進化させて何かを壊したときにヘッズアップを提供し、良好な回帰テストを持っている場合、問題を探す場所についての快適性/ガイダンスを提供します(安定したものではありません) 、十分な回帰テスト済み)。
ルイスプリングル

@marstato「ユニットテストの目標はバグの場所を特定することです」-それがまさにOPの質問につながる誤解です。単体テストの目標は、APIの意図された(そしてできれば文書化された)動作を検証することです。
user560822

4
@marstato「統合テスト」という名前は、複数のコンポーネントが連携して動作する(つまり、適切に統合される)テストに由来します。単体テストは、単一のコンポーネントが単独で行うはずのことを行うことをテストします。つまり、基本的には、パブリックAPIが文書化/要求どおりに機能することを意味します。どちらの用語も、統合が機能することを保証する一環として内部実装ロジックのテストを含めるか、単一ユニットが機能することを含めるかについては何も述べていません。
ベン

3

UnitTestsは、コードではなく、パブリックな観測可能な動作をテストし ます。「パブリック」とは、戻り値と依存関係との通信を意味します。

「ユニット」とは、同じ問題を解決する(またはより正確には、同じ理由で変更する)コードです。それは、単一のメソッドまたはクラスの束かもしれません。

テストしたくない主な理由private methodsは、これらは実装の詳細であり、リファクタリング中に変更する必要がある場合があることです(機能を変更せずにオブジェクト指向の原則を適用することでコードを改善します)。これは、リファクタリング中にCuT の動作が変更されなかったことを保証できるように、ユニットテストを変更したくない場合です。

しかし、パブリックインターフェイスをテストできるようにするためだけに数十億のクラスを持ちたくはありません。パブリックメソッドをテストするだけで多くの依存関係をモックする必要があり、ユニットテストは多くのクラスで行われます。巨大で追跡が難しい。

私は通常、逆のことを経験します。クラスが小さいほど(責任が小さいほど)、依存関係が少なくなり、ユニットテストの書き込みと読み取りの両方が容易になります。

同じレベルの抽象化パターンをクラスに適用するのが理想的です。これは、クラスがいくつかのビジネスロジック(好ましくは、独自の状態を維持せずにパラメータで動作する「純粋な関数」として)(x)を提供するか、他のオブジェクトのメソッドを呼び出すことを意味します。

企業行動を単体テストこの方法では、ケーキの一部であり、「委任」は、通常されているオブジェクトに失敗しないために単純すぎる何の単体テストを必要としないように(何の分岐、無の状態変化)を、そのテストをしておくことができ、統合またはモジュールテスト


1

特にアプリケーションが非常に大きい場合や非常に複雑な場合は、コードで作業している唯一のプログラマーである場合、単体テストにはいくつかの価値があります。ユニットテストが不可欠になるのは、同じコードベースで作業するプログラマが多数いる場合です。ユニットテストの概念は、これらの大規模なチームで作業する際のいくつかの困難に対処するために導入されました。

単体テストが大規模なチームに役立つ理由は、すべて契約に関するものです。私のコードが他の誰かによって書かれたコードを呼び出す場合、私は他の人のコードがさまざまな状況で何をしようとしているかを推測しています。これらの前提条件がまだ真であれば、コードは引き続き機能しますが、どの前提条件が有効であるか、どのように前提条件が変更されたかを知るにはどうすればよいですか?

これがユニットテストの出番です。クラスの作成者は、ユニットテストを作成して、クラスの予想される動作を文書化します。単体テストは、クラスを使用するすべての有効な方法を定義し、単体テストを実行すると、これらのユースケースが期待どおりに機能することを検証します。クラスを利用したい別のプログラマーは、ユニットテストを読んでクラスに期待できる動作を理解し、これをクラスの動作に関する前提の基礎として使用できます。

このように、クラスとユニットテストのパブリックメソッドシグネチャは、クラス作成者と、コードでこのクラスを使用する他のプログラマーとの間でコントラクトを形成します。

このシナリオでは、プライベートメソッドのテストを含めるとどうなりますか?明らかにそれは意味がありません。

あなたがあなたのコードに取り組んでいる唯一のプログラマーであり、コードをデバッグする方法として単体テストを使用したい場合、そのことには何の害もありません。それは単なるツールであり、ただし、単体テストが導入された理由ではなく、単体テストの主な利点はありません。


依存関係をモックアウトして依存関係の動作を変更してもテストが失敗しないようにするため、これに苦労しています。この失敗は、単体テストではなく統合テストで発生するはずですか?
トッド

モックは実際の実装と同じように動作するはずですが、依存関係はありません。実際の実装が変更され、それらが異なるようになった場合、これは統合テストの失敗として表示されます。個人的には、モックの開発と実際の実装は1つのタスクであると考えています。私は単体テストの作成の一環としてモックを作成しません。このようにして、クラスの動作を変更し、モックを一致するように変更すると、単体テストを実行すると、この変更によって破損した他のすべてのクラスが識別されます。
bikeman868

1

そのような質問に答える前に、実際に達成したいことを決める必要があります。

あなたはコードを書きます。あなたはそれがその契約を履行することを望みます(言い換えれば、それはそれがすべきことをします。それがすべきことを書き留めることは、一部の人々にとって大きな前進です)。

コードが意図したとおりに動作することを合理的に確信するには、十分に長く見つめるか、十分なケースをテストして「コードがこれらすべてのテストに合格すれば正しい」と納得させるテストコードを記述します。

多くの場合、いくつかのコードのパブリックに定義されたインターフェースのみに関心があります。私はあなたのライブラリーを使用している場合、私はそれが唯一のことを、あなたはそれが正しく動作作った方法を気にしない正しく仕事を。単体テストを実行して、ライブラリが正しいことを確認します。

ただし、ライブラリを作成しています。正しく動作させるのは難しい場合があります。ライブラリがオペレーションXを正しく実行することだけを気にしているので、Xの単体テストがあります。ライブラリを作成する開発者である開発者は、ステップA、B、およびCを組み合わせてXを実装します。ライブラリを機能させるには、テストを追加して、A、B、Cがそれぞれ正しく機能することを確認します。これらのテストが必要です。「プライベートメソッドの単体テストを行うべきではない」と言うのはまったく無意味です。これらのプライベートメソッドのテスト必要です。たぶん、誰かがプライベートメソッドをテストするユニットが間違っているとあなたに言うでしょう。しかし、それは単に「ユニットテスト」ではなく「プライベートテスト」など、好きなものを呼び出すことができることを意味します。

Swift言語は、A、B、Cをパブリックメソッドとして公開したくないという問題を、関数に "testable"属性を与えることでテストしたいという理由で解決します。コンパイラーは、非テストコードからではなく、単体テストからプライベートテスト可能メソッドを呼び出すことを許可します。


0

はい、あなたは狂っています。フォックスのように!

プライベートメソッドをテストするにはいくつかの方法があり、そのうちのいくつかは言語に依存しています。

  • 反射!ルールが破られるように作られています!
  • それらを保護し、継承し、オーバーライドします
  • friend / InternalsVisibleToクラス

ただし、全体として、プライベートメソッドをテストする場合は、依存関係にあるパブリックメソッドに移動して、テスト/インジェクトすることをお勧めします。


よくわかりませんが、私の目には、それは良いアイデアではないと説明しましたが、質問に答え続けました
-Liath

私はすべての拠点をカバーしたと思った:(
ユアン

3
私が賛成したのは、プライベートヘルパーメソッドをテストするためだけにパブリックAPIに公開するというアイデアはおかしいからです。
ロバートハーヴェイ

0

実用的です。インスタンスの状態とパブリックメソッドのパラメーターを設定して、それらのケースが発生するようにプライベートメソッドで特殊なケースをテストすることは、しばしば非常に複雑です。

これらの「プライベート」メソッドをテストするために、明確に名前が付けられた追加のinternalアクセサーを(InternalsVisibleToテストアセンブリのフラグと共に)追加しDoSomethingForTesting(parameters)ます。

もちろん、実装はいつか変更される可能性があり、テストアクセッサを含むそれらのテストは廃止されます。テストされていないケースや読み取り不可能なテストよりも優れています。

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