私はプライベートメソッドをテストする方法についてこの記事を読みました。オブジェクトの外部から呼び出されるパブリックメソッドのみをテストする方が速いといつも思っていたので、通常はテストしません。プライベートメソッドをテストしますか?常にテストする必要がありますか?
私はプライベートメソッドをテストする方法についてこの記事を読みました。オブジェクトの外部から呼び出されるパブリックメソッドのみをテストする方が速いといつも思っていたので、通常はテストしません。プライベートメソッドをテストしますか?常にテストする必要がありますか?
回答:
私はプライベートメソッドの単体テストは行いません。プライベートメソッドは、クラスのユーザーに対して非表示にする必要がある実装の詳細です。プライベートメソッドをテストすると、カプセル化が壊れます。
プライベートメソッドが巨大であるか、複雑であるか、独自のテストを必要とするほど重要であることがわかった場合は、それを別のクラスに配置し、そこで公開します(メソッドオブジェクト)。次に、以前はプライベートでしたが今はパブリックになっているメソッドを、独自のクラスに存在するように簡単にテストできます。
テストの目的は何ですか?
これまでの回答の大部分は、パブリックメソッドが十分にテストされ機能している限り、プライベートメソッドは実装の詳細であり、重要ではない(または少なくとも重要ではない)と述べています。テストの唯一の目的がパブリックインターフェイスが機能することを保証することである場合、これは完全に正しい。
個人的には、コードテストの主な用途は、将来のコード変更によって問題が発生しないことを確認し、問題が発生した場合のデバッグ作業を支援することです。プライベートメソッドをパブリックインターフェイスと同じくらい徹底的にテストすると(そうでない場合でも!)、その目的がさらに促進されることがわかりました。
考えてみましょう:プライベートメソッドBを呼び出すパブリックメソッドAがあります。AとBはどちらもメソッドCを使用しています。Cが(おそらくベンダーによって)変更されているため、Aはテストに失敗し始めています。プライベートであるにも関わらず、Bのテストを行うことは有用ではないでしょうか。問題がAのCの使用、BのCの使用、またはその両方にあるかどうかがわかるでしょうか。
プライベートメソッドのテストは、パブリックインターフェイスのテストカバレッジが不完全な場合にも価値をもたらします。これは一般的に避けたい状況ですが、効率の単体テストは、バグを検出するテストと、それらのテストに関連する開発および保守コストの両方に依存します。場合によっては、100%のテストカバレッジの利点は、それらのテストのコストを保証するには不十分であると判断され、パブリックインターフェイスのテストカバレッジにギャップが生じることがあります。このような場合、privateメソッドのターゲットを絞ったテストは、コードベースに非常に効果的に追加できます。
testDoSomething()
またはのいずれかにありtestDoSomethingPrivate()
ます。これにより、テストの価値が低くなります。。ここでプライベートテストするための複数の理由だ stackoverflow.com/questions/34571/...は:
私は、Dave ThomasとAndy Huntの著書Pragmatic Unit Testingのアドバイスに従う傾向があります。
一般に、テストのためにカプセル化を解除したくない(または、ママがよく言っているように、「プライベートを公開しないでください!」)。ほとんどの場合、パブリックメソッドを実行することでクラスをテストできるはずです。プライベートアクセスまたは保護されたアクセスの背後に隠されている重要な機能がある場合は、そこに別のクラスが存在することを警告している可能性があります。
しかし、完全に堅牢なプログラムを構築しているという安心感が得られるため、プライベートメソッドのテストを止めることができない場合があります。
プロジェクトで私たちの最新のQA推奨事項の1つをますますフォローしているので、プライベート関数をテストすることを余儀なくされているような気がします。
関数あたりの循環的複雑度は10以下です。
このポリシーを適用すると、私の非常に大規模なパブリック関数の多くが、より焦点を絞った、より適切な名前のプライベート関数に分割されます。
パブリック関数は(もちろん)まだそこにありますが、基本的にはそれらすべてのプライベート「サブ関数」を呼び出すように削減されます
コールスタックが読みやすくなったため、これは実際にクールです(大きな関数内のバグではなく、理解するのに役立つように、コールスタック内の以前の関数の名前を持つサブサブ関数にバグがあります'私がそこに着いた方法')
ただし、これらの非公開のものを直接単体テストする方が簡単になりました。関数を単体テストして、大きなパブリック関数のテストを、シナリオに対処する必要があるある種の「統合」テストに任せる。
ちょうど私の2セント。
はい、私はプライベート関数をテストします。なぜなら、それらはパブリックメソッドによってテストされますが、アプリケーションの最小部分をテストすることはTDD(テスト駆動設計)で優れているからです。ただし、テストユニットクラスにいるときは、プライベート関数にアクセスできません。プライベートメソッドをテストする方法は次のとおりです。
なぜプライベートメソッドがあるのですか?
プライベートメソッドは、パブリックメソッドで読み取り可能なコードを作成するため、主にクラスに存在します。このクラスのユーザーがこれらのメソッドを直接呼び出すのではなく、パブリックメソッドを介して呼び出します。また、クラスを拡張するとき(保護されている場合)の動作を変更したくないので、それはプライベートです。
コーディングするときは、テスト駆動設計(TDD)を使用します。これは、非公開でテストしたい機能に時々遭遇することを意味します。Testクラスでプライベート関数にアクセスできないため、プライベート関数はphpUnitでテストできません(プライベートです)。
私たちはここに3つの解決策があると思います:
1.パブリックメソッドを使用してプライベートをテストできます
メリット
短所
2.プライベートが非常に重要である場合、それは新しいプライベートクラスを作成するためのコードメルである可能性があります。
メリット
短所
3.アクセス修飾子を(最終)保護に変更します
メリット
短所
例
class Detective {
public function investigate() {}
private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
public function investigate() {}
final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {
public test_sleepWithSuspect($suspect)
{
//this is now accessible, but still not overridable!
$this->sleepWithSuspect($suspect);
}
}
したがって、テストユニットはtest_sleepWithSuspectを呼び出して、以前のプライベート関数をテストできます。
いくつかの理由で、プライベート機能のテストは好きではありません。それらは次のとおりです(これらはTLDRの人々にとって主要なポイントです)。
これらのそれぞれを具体的な例で説明します。2)と3)はやや複雑に関連していることがわかります。そのため、プライベートメソッドをテストしてはならない理由を個別に考えていますが、例は似ています。
プライベートメソッドのテストが適切な場合がありますが、上記のマイナス面に注意することが重要です。後で詳しく説明します。
最後に、TDDがプライベートメソッドをテストするための有効な言い訳にならない理由についても説明します。
私が目にする最も一般的な(反)パターンの1つは、Michael Feathersが"Iceberg"クラスと呼んでいるものです(Michael Feathersが誰であるかわからない場合は、彼の本「レガシーコードの効果的な使用」を購入/読んでください。彼はあなたがプロのソフトウェアエンジニア/開発者であるかどうかを知る価値のある人)。この問題を引き起こす他の(アンチ)パターンがありますが、これは私が偶然見つけた最も一般的なパターンです。「Iceberg」クラスには1つのパブリックメソッドがあり、残りはプライベートです(そのため、プライベートメソッドをテストしたくなります)。通常、1つのパブリックメソッドが出現するため、「Iceberg」クラスと呼ばれますが、残りの機能はプライベートメソッドの形で水中に隠されています。
たとえばGetNextToken()
、文字列に対して連続的に呼び出し、期待した結果が返されることを確認してテストすることができます。このような関数はテストを保証します。特にトークン化ルールが複雑な場合、その動作は簡単ではありません。それほど複雑ではないと仮定してみましょう。スペースで区切られたトークンにロープをかけたいだけです。したがって、テストを作成すると、おそらく次のようになります(一部の言語にとらわれない擬似コード、うまくいけばアイデアは明確です)。
TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
re = RuleEvaluator(input_string);
ASSERT re.GetNextToken() IS "1";
ASSERT re.GetNextToken() IS "2";
ASSERT re.GetNextToken() IS "test";
ASSERT re.GetNextToken() IS "bar";
ASSERT re.HasMoreTokens() IS FALSE;
}
まあ、それは実際にはかなりいいですね。変更を加えても、この動作を維持する必要があります。しかし、GetNextToken()
ある民間の機能!このようにテストすることはできません。ためコンパイルすらため(Pythonなどの一部のスクリプト言語とは異なり、実際にパブリック/プライベートを強制する言語を使用していると仮定します)。しかしRuleEvaluator
、単一の責任の原則(単一の責任の原則)に従うようにクラスを変更することについてはどうでしょうか?たとえば、パーサー、トークナイザー、エバリュエーターが1つのクラスに組み込まれているようです。それらの責任を分離する方が良いのではないでしょうか。その上、Tokenizer
クラスを作成すると、そのパブリックメソッドはHasMoreTokens()
andになりGetNextTokens()
ます。RuleEvaluator
クラスが持っている可能性がありTokenizer
メンバーとしてのオブジェクト。これで、上記の場合と同じテストを続けることができます。ただしTokenizer
、RuleEvaluator
ます。
UMLでは次のようになります。
この新しい設計によりモジュール性が向上するため、システムの他の部分でこれらのクラスを再利用できる可能性があることに注意してください(できなかった場合、プライベートメソッドは定義により再利用できません)。これは、RuleEvaluatorを分解する主な利点であり、理解度/局所性が向上します。
テストは非常に似ていますが、GetNextToken()
メソッドがTokenizer
クラスでパブリックになっているため、今回は実際にコンパイルされます。
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS FALSE;
}
問題をより少ないモジュール式コンポーネントに分解できるとは思わない場合でも(それを行おうとするだけで95%の確率で実行できます)、パブリックインターフェイスを介してプライベート関数をテストできます。多くの場合、プライベートメンバーはパブリックインターフェイスを通じてテストされるため、テストする価値はありません。私が目にするのは非常によく見えるテスト、よく似たテストですが、2つの異なる関数/メソッドをテストします。何が起こって終わることは要件の変更が(と彼らはいつも)とき、あなたは今の代わりに1の2つの壊れたテストを持っているそして、あなたは本当にすべてのあなたのプライベートメソッドをテストした場合、あなたはより多くのような10回の壊れたテストの代わりの1持っているかもしれないということです要するにを、プライベート関数のテスト(FRIEND_TEST
または、パブリックにするか、リフレクションを使用して)、パブリックインターフェイスを介してテストしないと、テストが重複する可能性がありますリフレクションのたりすると、通常、最終的にはそれを後悔することになります。。あなたが本当にこれを望まないのは、あなたのテストスイートがあなたを遅くすること以外に害を与えるものはないからです。開発時間を短縮し、メンテナンスコストを削減するはずです。パブリックインターフェイスを介して他の方法でテストされるプライベートメソッドをテストする場合、テストスイートは反対のことを非常にうまく行い、メンテナンスコストを増加させ、開発時間を増加させます。プライベート関数をパブリックにする場合、または次のようなものを使用する場合FRIEND_TEST
Tokenizer
クラスの次の可能な実装を検討してください。
SplitUpByDelimiter()
配列の各要素がトークンになるような配列を返す責任があるとしましょう。さらに、それGetNextToken()
は単にこのベクトルの反復子であるとしましょう。したがって、公開テストは次のようになります。
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS false;
}
Michael Featherが模索ツールと呼んでいるものがあるとしましょう。これは、他の人のプライベートな部分に触れることができるツールです。例はFRIEND_TEST
googletest、または言語がそれをサポートする場合のリフレクションからです。
TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
result_array = tokenizer.SplitUpByDelimiter(" ");
ASSERT result.size() IS 4;
ASSERT result[0] IS "1";
ASSERT result[1] IS "2";
ASSERT result[2] IS "test";
ASSERT result[3] IS "bar";
}
さて、要件が変更され、トークン化がさらに複雑になったとしましょう。単純な文字列区切り記号では不十分であると判断し、必要なDelimiter
、ジョブを処理クラス。当然、1つのテストが失敗することを期待することになりますが、プライベート関数をテストすると、その痛みが増大します。
ソフトウェアには「1つのサイズですべてに対応」することはできません。時々「ルールを破る」ことは大丈夫です(そして実際には理想的です)。できる限りプライベート機能をテストしないことを強くお勧めします。大丈夫だと思う主な状況は2つあります。
私はレガシーシステムを広範囲にわたって使用してきました(そのため、私はMichael Feathersの大ファンです)、プライベート機能をテストするのが最も安全な場合もあると言えます。「特性評価テスト」をベースラインに入れるのに特に役立ちます。
あなたは急いでいて、今ここで可能な限り最速のことをしなければなりません。長期的には、プライベートメソッドをテストする必要はありません。ただし、設計の問題に対処するために通常はリファクタリングに時間がかかると言います。そして、時にはあなたは一週間で出荷する必要があります。それは大丈夫です:あなたがそれがあなたが仕事を成し遂げるための最も速くて最も信頼できる方法であると思うなら、手探りツールを使用してプライベートメソッドをすばやくそして汚いようにテストしてください。しかし、あなたがしたことは長期的には次善の策であったことを理解し、それに戻ることを検討してください(または、それが忘れられたが後で見た場合は修正してください)。
おそらく他の状況でも問題ないでしょう。大丈夫だと思い、正当な理由がある場合は、それを実行してください。誰もあなたを止めません。潜在的なコストに注意してください。
余談ですが、私はTDDをプライベートメソッドのテストの言い訳として使用することを本当に嫌いです。私はTDDを実践していますが、TDDがこれを強制することはないと思います。最初に(パブリックインターフェイス用の)テストを記述してから、そのインターフェイスを満たすコードを記述できます。ときどき、パブリックインターフェイスのテストを作成し、1つまたは2つの小さなプライベートメソッドも作成してそれを満たします(ただし、プライベートメソッドを直接テストしませんが、動作することはわかっています。または、パブリックテストが失敗します。 )。そのプライベートメソッドのエッジケースをテストする必要がある場合は、パブリックインターフェイスを介してヒットする一連のテスト全体を記述します。エッジケースにヒットする方法がわからない場合、これは、それぞれが独自のパブリックメソッドを持つ小さなコンポーネントにリファクタリングする必要があることを示す強い兆候です。これは、プライベート関数がやりすぎであり、クラスのスコープ外であることを示しています。
また、時々、私が現時点で噛むには大きすぎるテストを書いていることに気づくことがあります。コメントアウトして、私の心の奥に留めておきます)。ここで、私が会った多くの開発者がTDDをスケープゴートとして使用して、プライベート機能のテストを作成し始めます。「ああ、まあ、他のテストが必要ですが、そのテストを書くには、これらのプライベートメソッドが必要になります。したがって、テストを書かないとプロダクションコードを書くことができないので、テストを書く必要があります。プライベートメソッドの場合。」しかし、彼らが実際に行う必要があるのは、現在のクラスにプライベートメソッドの束を追加/テストする代わりに、小さくて再利用可能なコンポーネントにリファクタリングすることです。
注意:
少し前に、GoogleTestを使用したプライベートメソッドのテストに関する同様の質問に回答しました。ここでは、その答えをより言語にとらわれないように変更しました。
PSこれは、Michael Feathersによる氷山のクラスと模索ツールに関する関連する講義です。https://www.youtube.com/watch?v = 4cVZvoFGJTU
_
にを付けて名前を付けると、「ねえ、これは「プライベート」です。使用できますが、完全に開示されています。再利用するようには設計されていません。本当に使用する場合にのみ使用してください。あなたが何をしているのか知っている」。どの言語でも同じアプローチを取ることができます。それらのメンバーを公開しますが、先頭にを付け_
ます。あるいは、それらの関数は実際にはプライベートであり、パブリックインターフェイスを介してテストされるだけかもしれません(詳細については回答を参照してください)。ケースバイケースであり、一般的なルールはありません
あなたのプライベートメソッドがあなたのパブリックメソッドを呼び出すことによってテストされていない場合、それは何をしていますか?私は保護されていない、または友人のプライベートな話をしています。
プライベートメソッドが適切に定義されている場合(つまり、テスト可能な関数があり、時間の経過とともに変化することを意図していない場合)、はい。私はそれが理にかなっている場所でテスト可能なすべてのものをテストします。
たとえば、暗号化ライブラリは、一度に8バイトのみを暗号化するプライベートメソッドを使用してブロック暗号化を実行するという事実を隠している可能性があります。私はそのための単体テストを作成します-それが隠されていても、変更することを意味していません、そしてそれが壊れた場合(たとえば、将来のパフォーマンス強化のため)、それが壊れたのは、プライベート関数だけではなく、公共機能の1つが壊れたということ。
後でデバッグを高速化します。
-アダム
テスト駆動(TDD)を開発している場合は、プライベートメソッドをテストします。
私はこの分野の専門家ではありませんが、単体テストでは実装ではなく動作をテストする必要があります。プライベートメソッドは厳密に実装の一部であるため、IMHOはテストしないでください。
私はこの問題をしばらくの間、特にTDDで試してみました。
TDDの場合、この問題に十分に対処できると思う投稿が2つあります。
要約すれば:
テスト駆動開発(設計)テクニックを使用する場合、プライベートメソッドは、すでに機能しテスト済みのコードのリファクタリングプロセス中にのみ発生します。
プロセスの性質上、徹底的にテストされた機能から抽出された単純な実装機能は、自己テストされます(つまり、間接テストのカバレッジ)。
コーディングの最初の部分では、デザインをカプセル化/記述しているため、ほとんどのメソッドがより高レベルの関数になることは私には十分に明らかです。
したがって、これらのメソッドは公開され、それらのテストは十分簡単になります。
すべてが正常に機能し、読みやすさと清潔さのためにリファクタリングを行うようになると、プライベートメソッドは後で追加されます。
上記で引用したように、「プライベートメソッドをテストしない場合、メソッドが壊れないことをどのようにして確認しますか?」
これは大きな問題です。単体テストの大きなポイントの1つは、どこで、いつ、どのように何かがASAPを破ったかを知ることです。したがって、開発とQAの労力を大幅に削減できます。テストされるすべてがパブリックである場合、クラスの内部の正直な報道と描写はありません。
これを行うための最良の方法の1つは、テスト参照をプロジェクトに追加し、テストをプライベートメソッドと並行してクラスに配置することです。テストが最終プロジェクトに組み込まれないように、適切なビルドロジックを追加します。
次に、これらのメソッドをテストすることのすべての利点が得られ、数分または数時間ではなく、数秒で問題を見つけることができます。
要約すると、はい、プライベートメソッドを単体テストします。
すべきではない。プライベートメソッドがテストする必要がある十分な複雑さを持っている場合、それらを別のクラスに置く必要があります。高い凝集度を維持します。クラスには1つの目的しかありません。クラスpublicインターフェースで十分です。
プライベートメソッドをテストしない場合、メソッドが壊れないことをどうやって確認できますか?
プライベートメソッドが実装の詳細と見なされるため、テストする必要がないという観点を理解しています。オブジェクトの外でのみ開発する必要がある場合は、このルールを使用します。しかし、私たちは、オブジェクトの外部でのみ開発し、パブリックメソッドのみを呼び出す、制限された開発者のようなものですか?それとも実際にそのオブジェクトを開発していますか?外部オブジェクトをプログラムする必要はないので、おそらくこれらのプライベートメソッドを、開発中の新しいパブリックメソッドに呼び出す必要があります。プライベートメソッドがすべてのオッズに対して抵抗することを知って素晴らしいと思いませんか?
私がそのオブジェクトに別のパブリックメソッドを開発している場合、これをテストする必要があると答える人もいるでしょう(プライベートメソッドはテストなしで実行できます)。ただし、これはオブジェクトのすべてのパブリックメソッドにも当てはまります。Webアプリを開発する場合、オブジェクトのすべてのパブリックメソッドはコントローラーメソッドから呼び出されるため、コントローラーの実装の詳細と見なすことができます。
では、なぜオブジェクトをユニットテストするのでしょうか。本当に難しいので、基礎となるコードのすべてのブランチをトリガーする適切な入力でコントローラーのメソッドをテストしていることを確認することは不可能ではありません。つまり、スタックの上位にあるほど、すべての動作をテストすることが難しくなります。プライベートメソッドも同様です。
私にとって、私的な方法と公的な方法の境界は、テストに関しては心理的な基準です。私にとってより重要な基準は次のとおりです。
私はユニットテストの概念を理解していませんが、今はそれが目的であるかを知っています。
単体テストは完全なテストではありません。したがって、QAおよび手動テストの代わりにはなりません。この側面でのTDDの概念は間違っています。プライベートメソッドだけでなく、リソース(特に、制御できないリソース)を使用するメソッドも含めてすべてをテストすることはできないためです。TDDは、そのすべての品質に基づいており、達成できなかったものです。
ユニットテストはピボットテスト です。任意のピボットをマークすると、ピボットの結果は同じままになります。
パブリックとプライベートは、テストからどのAPIを呼び出すかを区別するのに役立ちません。また、メソッドとクラスも区別されません。ほとんどのテスト可能なユニットは、1つのコンテキストでは表示されますが、他のコンテキストでは非表示になります。
重要なのは補償範囲とコストです。プロジェクトのカバレッジ目標(ライン、ブランチ、パス、ブロック、メソッド、クラス、同等クラス、ユースケース...チームが決定するものすべて)を達成しながら、コストを最小限に抑える必要があります。
そのため、カバレッジを確保するためのツールを使用し、コストが最小になるようにテストを設計します(短期および長期)。
テストを必要以上に高価にしないでください。パブリックエントリポイントのみをテストするのが最も安い場合は、それを行ってください。プライベートメソッドをテストするのが最も安い場合は、それを実行します。
経験を積むにつれて、テストメンテナンスの長期的なコストを回避するためにリファクタリングする価値がある時期を予測できるようになります。
メソッドが十分/複雑で十分である場合、通常は「保護」してテストします。一部のメソッドは非公開のままにされ、public / protectedメソッドの単体テストの一部として暗黙的にテストされます。
多くの人が同じ考えのもとにいるのを目にします:公共レベルでのテスト。しかし、それは私たちのQAチームが行うことではありませんか?彼らは入力と期待される出力をテストします。開発者としてパブリックメソッドのみをテストする場合は、QAの仕事をやり直すだけであり、「ユニットテスト」による付加価値はありません。
「プライベートメソッドをテストする必要がありますか?」に対する答え 「……時々」です。通常、クラスのインターフェースに対してテストする必要があります。
次に例を示します。
class Thing
def some_string
one + two
end
private
def one
'aaaa'
end
def two
'bbbb'
end
end
class RefactoredThing
def some_string
one + one_a + two + two_b
end
private
def one
'aa'
end
def one_a
'aa'
end
def two
'bb'
end
def two_b
'bb'
end
end
これでRefactoredThing
5つのテストがあり、そのうち2つはリファクタリングのために更新する必要がありましたが、オブジェクトの機能は実際には変更されていません。したがって、物事はそれよりも複雑で、次のような出力の順序を定義するメソッドがあるとします。
def some_string_positioner
if some case
elsif other case
elsif other case
elsif other case
else one more case
end
end
これは外部のユーザーによって実行されるべきではありませんが、カプセル化クラスは、そのクラスのロジックを何度も繰り返し実行するために重い場合があります。この場合、おそらくこれを別のクラスに抽出し、そのクラスにインターフェースを与え、それに対してテストすることをお勧めします。
最後に、メインオブジェクトが非常に重く、メソッドが非常に小さく、出力が正しいことを確認する必要があるとしましょう。「私はこのプライベートメソッドをテストしなければならない!」と考えています。重い作業の一部を初期化パラメーターとして渡すことで、オブジェクトを軽量化できると思いませんか?次に、より軽いものを渡し、それに対してテストできます。
主なポイントは
ロジックが正しいことを確認するためにテストし、プライベートメソッドがロジックを実行している場合は、テストする必要があります。だよね?それで、なぜそれをスキップするのですか?
メソッドの可視性に基づいてテストを作成することは、まったく無関係です。
逆に
一方、元のクラスの外部でプライベートメソッドを呼び出すことが主な問題です。また、一部のモックツールでプライベートメソッドをモックする場合にも制限があります。(例:モッキート)
それをサポートするPower Mockのようないくつかのツールがありますが、それは危険な操作です。その理由は、それを実現するにはJVMをハックする必要があるからです。
実行できる回避策の1つは、(プライベートメソッドのテストケースを作成する場合)
これらのプライベートメソッドをprotectedとして宣言します。しかし、いくつかの状況では不便かもしれません。
これは、パブリックまたはプライベートのメソッドまたは関数に関するだけでなく、実装の詳細に関するものです。プライベート関数は、実装の詳細の1つの側面にすぎません。
結局のところ、ユニットテストはホワイトボックステストのアプローチです。たとえば、カバレッジ分析を使用して、これまでのテストで無視されてきたコードの部分を特定する人は、実装の詳細に入ります。
A)はい、実装の詳細をテストする必要があります。
パフォーマンス上の理由から、要素が10個までの場合はBubbleSortのプライベート実装を使用し、10個を超える要素がある場合は異なるソートアプローチ(たとえば、ヒープソート)のプライベート実装を使用するソート関数について考えてみます。公開APIは、ソート関数のAPIです。ただし、テストスイートでは、実際には2つのソートアルゴリズムが使用されているという知識をより有効に活用します。
この例では、確かに、パブリックAPIでテストを実行できます。ただし、これには、ヒープソートアルゴリズムが十分にテストされるように、10を超える要素でソート関数を実行する多くのテストケースが必要になります。このようなテストケースが存在するだけで、テストスイートが関数の実装の詳細に関連付けられていることがわかります。
ソート関数の実装の詳細が変更された場合、2つのソートアルゴリズム間の制限が変更されるか、ヒープソートがマージソートなどに置き換えられる可能性があります。既存のテストは引き続き機能します。それでもそれらの値には疑問があり、変更されたソート関数をより適切にテストするために、それらを再加工する必要がある可能性があります。つまり、テストがパブリックAPIで行われたにもかかわらず、メンテナンス作業が必要になります。
B)実装の詳細をテストする方法
多くの人がプライベート関数や実装の詳細をテストすべきではないと主張する1つの理由は、実装の詳細が変更される可能性が高いためです。この少なくとも変更の可能性が高いことが、実装の詳細をインターフェースの背後に隠す理由の1つです。
ここで、インターフェースの背後の実装に、内部インターフェースでの個別のテストがオプションとなる可能性のある大きなプライベートパーツが含まれていると想定します。一部の人々は、これらの部分は非公開の場合はテストされるべきではなく、公開されているものに変えられるべきだと主張します。いったん公開されると、そのコードの単体テストで問題ありません。
これは興味深いものです。インターフェースは内部的なものでしたが、実装の詳細であるため、変更される可能性がありました。同じインターフェースを取り、それを公開すると、いくつかの魔法の変換が行われます。つまり、変更される可能性が低いインターフェースに変換されます。明らかに、この議論にはいくつかの欠陥があります。
しかし、それにもかかわらず、この背後にいくつかの真実があります。特に内部インターフェイスを使用して実装の詳細をテストするときは、安定したままである可能性が高いインターフェイスを使用するように努力する必要があります。ただし、一部のインターフェースが安定している可能性があるかどうかは、それがパブリックであるかプライベートであるかに基づいて単純に決定できるわけではありません。私がしばらくの間取り組んできた世界のプロジェクトでは、パブリックインターフェースも十分に変更されることが多く、多くのプライベートインターフェースは長い間手つかずのままでした。
それでも、「正面玄関」を最初に使用するのが良い目安です(http://xunitpatterns.com/Principles%20of%20Test%20Automation.htmlを参照)。ただし、「正面玄関先」ではなく「正面玄関先のみ」と呼ばれていることに注意してください。
C)まとめ
実装の詳細もテストします。安定したインターフェース(パブリックまたはプライベート)でのテストを推奨します。実装の詳細が変更された場合、パブリックAPIのテストも変更する必要があります。プライベートなものをパブリックにしても、魔法のように安定性が変わるわけではありません。
はい、できる限りプライベートメソッドをテストする必要があります。どうして?最終的に、同じ入力に対して同じプライベート関数を繰り返し暗黙的にテストすることになるテストケースの不要な状態空間の爆発を回避するため。その理由を例を挙げて説明しましょう。
次のわずかに工夫された例を考えてみましょう。3つの整数を取り、3つの整数がすべて素数である場合にのみtrueを返す関数を公開します。次のように実装します。
public bool allPrime(int a, int b, int c)
{
return andAll(isPrime(a), isPrime(b), isPrime(c))
}
private bool andAll(bool... boolArray)
{
foreach (bool b in boolArray)
{
if(b == false) return false;
}
return true;
}
private bool isPrime(int x){
//Implementation to go here. Sorry if you were expecting a prime sieve.
}
我々は唯一の公共の機能がテストされるべきであるという厳格なアプローチを取るとしたら今、我々は唯一のテストに許されるだろうallPrime
とはありませんisPrime
かandAll
。
テスターとして、私たちはそれぞれの引数のための5つの可能性に興味があるかもしれません:< 0
、= 0
、= 1
、prime > 1
、not prime > 1
。ただし、徹底的に説明するには、引数のすべての組み合わせがどのように作用するかを確認する必要があります。つまり5*5*5
、直感によると、これは125のテストケースであり、この機能を徹底的にテストする必要があります。
一方、プライベート関数をテストすることが許可されれば、より少ないテストケースで十分な範囲をカバーできます。isPrime
以前の直感と同じレベルにテストするために必要なテストケースは5つだけです。そして、ダニエルジャクソンによって提案されたスモールスコープ仮説により、andAll
関数を短い長さ、たとえば3または4 までテストするだけで済みます。これは最大で16のテストになります。合計21のテストです。もちろん、125の代わりに。おそらくいくつかのテストをで実行する必要がありますallPrime
が、私たちが気にかけていた入力シナリオの125の組み合わせすべてを網羅することはそれほど義務付けられているとは思わないでしょう。ほんの少しの幸せな道。
確かに人為的な例ですが、明確なデモンストレーションには必要でした。そして、パターンは実際のソフトウェアにまで及びます。プライベート関数は通常、最下位レベルのビルディングブロックであり、したがって、多くの場合一緒に結合されて、より高いレベルのロジックを生成します。より高いレベルの意味では、さまざまな組み合わせにより、より低いレベルのものの繰り返しが多くなります。
isPrime
は完全に独立しているため、すべての組み合わせを盲目的にテストすることはほとんど無意味です。次に、isPrime
private と呼ばれる純粋な関数にマークを付けると、どこから始めればよいかわからないほど多くの設計ルールに違反します。isPrime
非常に明確に公共の機能であるべきです。そうは言っても、この極端に悪い例に関係なく、私はあなたが言っていることを理解しています。しかし、それはあなたがしたい前提オフ組み込まれているしたい、実際のソフトウェアシステムで、これはめったに良いアイデアではないとき、組み合わせテストを行うことを。