プライベートメソッドをテストする必要がありますか、パブリックメソッドのみをテストする必要がありますか?[閉まっている]


347

私はプライベートメソッドをテストする方法についてこの記事を読みました。オブジェクトの外部から呼び出されるパブリックメソッドのみをテストする方が速いといつも思っていたので、通常はテストしません。プライベートメソッドをテストしますか?常にテストする必要がありますか?



「プライベートヘルパーをテストする必要がありますか?」はい。「プライベートヘルパーを直接テストする必要がありますか?」それは、一般に、パブリックインターフェイスを介してそれらを簡単にテストできるかどうか、なぜ直接テストするかによって異なります。パブリックインターフェイスを介してヘルパーのすべての側面をテストすることが複雑になった場合、コンポーネントは単一のユニットとしての存在よりも長く存続したでしょうか。
ミハイダニラ

回答:


328

私はプライベートメソッドの単体テストは行いません。プライベートメソッドは、クラスのユーザーに対して非表示にする必要がある実装の詳細です。プライベートメソッドをテストすると、カプセル化が壊れます。

プライベートメソッドが巨大であるか、複雑であるか、独自のテストを必要とするほど重要であることがわかった場合は、それを別のクラスに配置し、そこで公開します(メソッドオブジェクト)。次に、以前はプライベートでしたが今はパブリックになっているメソッドを、独自のクラスに存在するように簡単にテストできます。


88
同意しません。理想的には、関数のコーディングを開始する前に簡単なテストを記述します。典型的な入力と出力がどうなるかを考えてください。テストを作成し(数秒以上かかることはありません)、テストが正しくなるまでコードを記述します。プライベートメソッドでそのスタイルの作業を放棄する理由はありません。
フランク

254
プライベートメソッドがテストを必要としないと言うことは、車が大丈夫である限り車は大丈夫だと言っているようなもので、内部の内容は問題ではありません。しかし、ユーザーが何も気づいていない場合でも、内部のケーブルが緩んでいることを知っておくのは良いことではないでしょうか。もちろん、すべてを公開できますが、何が重要なのでしょうか。常にいくつかのプライベートメソッドが必要になります。
フランク

37
「プライベートメソッドは、クラスのユーザーに対して非表示にする必要がある実装の詳細です。」しかし、テストは「通常の」(ランタイム)ユーザーとクラスのインターフェイスの同じ側に本当にありますか?;)
mlvljr

34
テストしたいものを別のクラスに引き出すことの危険性は、製品を過剰に設計し、再利用されない100万個の再利用可能なコンポーネントを使用するというオーバーヘッドが発生する可能性があることです。
オクルス2014年

44
コードの一部を車と比較するのは間違っています。コードが時間とともに「悪くなることはありません。それは永遠です。公開インターフェースのテストが「大丈夫に見える」と判断するまでしか進んでいない場合、公開コードのテストは不十分です。この場合、プライベートメソッドを個別にテストしても、どんなに頑張っても、全体的なテストは完了しません。適切なシナリオを作成するためにコードの内部動作の知識を使用して、パブリックコード全体を徹底的にテストすることに集中してください。
rustyx

293

テストの目的は何ですか?

これまでの回答の大部分は、パブリックメソッドが十分にテストされ機能している限り、プライベートメソッドは実装の詳細であり、重要ではない(または少なくとも重要ではない)と述べています。テストの唯一の目的がパブリックインターフェイスが機能することを保証することである場合、これは完全に正しい

個人的には、コードテストの主な用途は、将来のコード変更によって問題が発生しないことを確認し、問題が発生した場合のデバッグ作業を支援することです。プライベートメソッドをパブリックインターフェイスと同じくらい徹底的にテストすると(そうでない場合でも!)、その目的がさらに促進されることがわかりました。

考えてみましょう:プライベートメソッドBを呼び出すパブリックメソッドAがあります。AとBはどちらもメソッドCを使用しています。Cが(おそらくベンダーによって)変更されているため、Aはテストに失敗し始めています。プライベートであるにも関わらず、Bのテストを行うことは有用ではないでしょうか。問題がAのCの使用、BのCの使用、またはその両方にあるかどうかがわかるでしょうか。

プライベートメソッドのテストは、パブリックインターフェイスのテストカバレッジが不完全な場合にも価値をもたらします。これは一般的に避けたい状況ですが、効率の単体テストは、バグを検出するテストと、それらのテストに関連する開発および保守コストの両方に依存します。場合によっては、100%のテストカバレッジの利点は、それらのテストのコストを保証するには不十分であると判断され、パブリックインターフェイスのテストカバレッジにギャップが生じることがあります。このような場合、privateメソッドのターゲットを絞ったテストは、コードベースに非常に効果的に追加できます。


72
ここでの問題は、これらの「将来のコード変更」は必ず、一部のクラスの内部動作をリファクタリングすることを意味することです。これは非常に頻繁に発生するため、テストを作成するとリファクタリングの障壁が作成されます。
アウトロープログラマ

40
また、ユニットテストを継続的に変更している場合は、テストの一貫性がすべて失われ、ユニットテスト自体にバグが発生する可能性さえあります。

6
@ 17テストと実装が同期的に変更された場合(そうであるようですが、そうである必要があります)、問題ははるかに少なくなります。
mlvljr 2012年


3
@Pacerierコードのテストと継続的な自動テストプロセスの違いもあります。明らかにプライベートメソッドが機能することを確認する必要がありますが、プライベートメソッドに結合するテストはソフトウェアのユースケースの一部ではないため、必要ありません。
Didier A.

150

私は、Dave ThomasとAndy Huntの著書Pragmatic Unit Testingのアドバイスに従う傾向があります。

一般に、テストのためにカプセル化を解除したくない(または、ママがよく言っているように、「プライベートを公開しないでください!」)。ほとんどの場合、パブリックメソッドを実行することでクラスをテストできるはずです。プライベートアクセスまたは保護されたアクセスの背後に隠されている重要な機能がある場合は、そこに別のクラスが存在することを警告している可能性があります。

しかし、完全に堅牢なプログラムを構築しているという安心感が得られるため、プライベートメソッドのテストを止めることができない場合があります。


9
プライベートメソッドを対象とする単体テストを無効にすることをお勧めします。これらはコード結合であり、将来のリファクタリング作業に負担をかけるか、機能の追加や変更の邪魔になることさえあります。それらを実装しているときに、実装作業であると断言する自動化された方法として、それらのテストを記述することは良いことですが、テストをリグレッションとして維持することは有益ではありません。
Didier A.

61

プロジェクトで私たちの最新のQA推奨事項の1つをますますフォローしているので、プライベート関数をテストすることを余儀なくされているような気がします。

関数あたりの循環的複雑度は10以下です。

このポリシーを適用すると、私の非常に大規模なパブリック関数の多くが、より焦点を絞った、より適切な名前のプライベート関数に分割されます。
パブリック関数は(もちろん)まだそこにありますが、基本的にはそれらすべてのプライベート「サブ関数」を呼び出すように削減されます

コールスタックが読みやすくなったため、これは実際にクールです(大きな関数内のバグではなく、理解するのに役立つように、コールスタック内の以前の関数の名前を持つサブサブ関数にバグがあります'私がそこに着いた方法')

ただし、これらの非公開のものを直接単体テストする方が簡単になりました。関数を単体テストして、大きなパブリック関数のテストを、シナリオに対処する必要があるある種の「統合」テストに任せる。

ちょうど私の2セント。


2
@jopに反応するために、(あまりにもサイクロマティックで複雑なパブリック関数が分割されているために作成された)これらのプライベート関数を別のクラスにエクスポートする必要性を感じていません。私はそれらを同じクラスのパブリック関数とまだ密に結合させたいです。しかし、まだユニットテストされています。
VonC 2008

2
私の経験では、これらのプライベートメソッドは、これらのパブリックメソッドによって再利用されているユーティリティメソッドにすぎません。場合によっては、元のクラスを2つ(または3つ)のまとまりのあるクラスに分割し、それらのプライベートメソッドを独自のクラスでパブリックにして、テスト可能にする方が便利な場合があります。
2008

7
いいえ、私の場合、これらの新しいプライベート関数は、パブリック関数によって表されるより大きなアルゴリズムの一部です。その機能は、実用的ではなく、より大きなプロセスのステップである小さな部分に分割されています。したがって、(アルゴ全体を一度に単体テストするのではなく)それらを単体テストする必要があります
VonC

循環的複雑度に興味のある人のために、私は、トピックに関する質問を追加しました:stackoverflow.com/questions/105852/...
VonC

タイトルにタイプミスがあるため、質問のURLが変更されました! stackoverflow.com/questions/105852/...
VonC

51

はい、私はプライベート関数をテストします。なぜなら、それらはパブリックメソッドによってテストされますが、アプリケーションの最小部分をテストすることはTDD(テスト駆動設計)で優れているからです。ただし、テストユニットクラスにいるときは、プライベート関数にアクセスできません。プライベートメソッドをテストする方法は次のとおりです。

なぜプライベートメソッドがあるのですか?

プライベートメソッドは、パブリックメソッドで読み取り可能なコードを作成するため、主にクラスに存在します。このクラスのユーザーがこれらのメソッドを直接呼び出すのではなく、パブリックメソッドを介して呼び出します。また、クラスを拡張するとき(保護されている場合)の動作を変更したくないので、それはプライベートです。

コーディングするときは、テスト駆動設計(TDD)を使用します。これは、非公開でテストしたい機能に時々遭遇することを意味します。Testクラスでプライベート関数にアクセスできないため、プライベート関数はphpUnitでテストできません(プライベートです)。

私たちはここに3つの解決策があると思います:

1.パブリックメソッドを使用してプライベートをテストできます

メリット

  • 簡単な単体テスト(「ハッキング」は不要)

短所

  • プログラマはパブリックメソッドを理解する必要があるが、プライベートメソッドのみをテストしたい
  • アプリケーションのテスト可能な最小部分をテストしていない

2.プライベートが非常に重要である場合、それは新しいプライベートクラスを作成するためのコードメルである可能性があります。

メリット

  • これを新しいクラスにリファクタリングできます。それが重要な場合は、他のクラスでも必要になる可能性があるためです。
  • テスト可能なユニットがパブリックメソッドになり、テスト可能に

短所

  • クラスが不要な場合は作成せず、メソッドの呼び出し元のクラスでのみ使用する
  • オーバーヘッドの増加によりパフォーマンスが低下する可能性

3.アクセス修飾子を(最終)保護に変更します

メリット

  • アプリケーションのテスト可能な最小部分をテストしています。final保護を使用する場合、関数はオーバーライドできません(プライベートのように)
  • パフォーマンスの低下なし
  • 余分なオーバーヘッドなし

短所

  • プライベートアクセスを保護されたものに変更します。つまり、子供がアクセスできます。
  • それを使用するには、テストクラスにMockクラスが必要です。

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を呼び出して、以前のプライベート関数をテストできます。


eddy147、私モックを介して保護されたメソッドをテストする概念が本当に好きです。ありがとう!!!!
セオドアR.スミス

15
TDDの元の説明では、ユニットテストでは、ユニットはメソッド/関数ではなくクラスであることを指摘しておきます。したがって、「アプリケーションの最小部分をテストする」という場合、テスト可能な最小部分をメソッドと呼ぶのは誤りです。そのロジックを使用する場合は、コードブロック全体ではなく1行のコードを使用している可能性もあります。
Matt Quigley、2015年

@Matt作業単位はクラスを指すことができますが、単一のメソッドを指すこともできます。
eddy147

4
@ eddy147ユニットテストは、ユニットがクラスとして定義されたテスト駆動開発です。インターネットで発生するように、セマンティクスは多くのことを意味するように拡張されました(つまり、ユニットテストと統合テストの違いを2人に尋ねると、7つの答えが得られます)。TDDは、単一の責任を含むSOLIDの原則を使用してソフトウェアを作成する方法として意図されたものであり、クラスには単一の責任があり、高い循環複雑度を持つべきではありません。TDDでは、クラスを作成して、両方をテストします。カプセル化されたプライベートメソッドには、対応する単体テストがありません。
Matt Quigley

「コーディングするときは、テスト駆動設計(TDD)を使用します。これは、非公開でテストしたい機能に出会うことがあります。」私はこの声明に強く同意しません。詳細については、以下の私の回答を参照してください。TDDは、プライベートメソッドのテストを強制されることを意味しません。プライベートメソッドをテストすることを選択できます。それはあなたの選択ですが、そのようなことを行うのはTDDではありません。
Matt Messersmith

41

いくつかの理由で、プライベート機能のテストは好きではありません。それらは次のとおりです(これらはTLDRの人々にとって主要なポイントです)。

  1. 通常、クラスのプライベートメソッドをテストしたい場合、それはデザインの匂いです。
  2. パブリックインターフェイスを介してそれらをテストできます(クライアントがそれらを呼び出す/使用する方法であるため、テストする方法です)。プライベートメソッドのすべてのテストに合格すると、緑色の信号が表示され、安心感を得ることができます。パブリックインターフェイスを介してプライベート関数のエッジケースをテストする方がはるかに優れています。
  3. プライベートメソッドをテストすることで、テストが大幅に重複する可能性があります(外観が非常によく似たテスト)。要件が変化すると、必要以上のテストが失敗するため、これは大きな影響を及ぼします。また、テストスイートが原因でリファクタリングが困難な状況に置かれる可能性もあります。これは、テストスイートが安全に再設計およびリファクタリングを行うために存在するため、究極の皮肉です!

これらのそれぞれを具体的な例で説明します。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メンバーとしてのオブジェクト。これで、上記の場合と同じテストを続けることができます。ただしTokenizerRuleEvaluatorます。

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_TESTgoogletest、または言語がそれをサポートする場合のリフレクションからです。

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つあります。

  1. 私はレガシーシステムを広範囲にわたって使用してきました(そのため、私はMichael Feathersの大ファンです)、プライベート機能をテストするのが最も安全な場合もあると言えます。「特性評価テスト」をベースラインに入れるのに特に役立ちます。

  2. あなたは急いでいて、今ここで可能な限り最速のことをしなければなりません。長期的には、プライベートメソッドをテストする必要はありません。ただし、設計の問題に対処するために通常はリファクタリングに時間がかかると言います。そして、時にはあなたは一週間で出荷する必要があります。それは大丈夫です:あなたがそれがあなたが仕事を成し遂げるための最も速くて最も信頼できる方法であると思うなら、手探りツールを使用してプライベートメソッドをすばやくそして汚いようにテストしてください。しかし、あなたがしたことは長期的には次善の策であったことを理解し、それに戻ることを検討してください(または、それが忘れられたが後で見た場合は修正してください)。

おそらく他の状況でも問題ないでしょう。大丈夫だと思い、正当な理由がある場合は、それを実行してください。誰もあなたを止めません。潜在的なコストに注意してください。

TDDの言い訳

余談ですが、私はTDDをプライベートメソッドのテストの言い訳として使用することを本当に嫌いです。私はTDDを実践していますが、TDDがこれを強制することはないと思います。最初に(パブリックインターフェイス用の)テストを記述してから、そのインターフェイスを満たすコードを記述できます。ときどき、パブリックインターフェイスのテストを作成し、1つまたは2つの小さなプライベートメソッドも作成してそれを満たします(ただし、プライベートメソッドを直接テストしませんが、動作することはわかっています。または、パブリックテストが失敗します。 )。そのプライベートメソッドのエッジケースをテストする必要がある場合は、パブリックインターフェイスを介してヒットする一連のテスト全体を記述します。エッジケースにヒットする方法がわからない場合、これは、それぞれが独自のパブリックメソッドを持つ小さなコンポーネントにリファクタリングする必要があることを示す強い兆候です。これは、プライベート関数がやりすぎであり、クラスのスコープ外であることを示しています。

また、時々、私が現時点で噛むには大きすぎるテストを書いていることに気づくことがあります。コメントアウトして、私の心の奥に留めておきます)。ここで、私が会った多くの開発者がTDDをスケープゴートとして使用して、プライベート機能のテストを作成し始めます。「ああ、まあ、他のテストが必要ですが、そのテストを書くには、これらのプライベートメソッドが必要になります。したがって、テストを書かないとプロダクションコードを書くことができないので、テストを書く必要があります。プライベートメソッドの場合。」しかし、彼らが実際に行う必要があるのは、現在のクラスにプライベートメソッドの束を追加/テストする代わりに、小さくて再利用可能なコンポーネントにリファクタリングすることです。

注意:

少し前に、GoogleTestを使用したプライベートメソッドのテストに関する同様の質問に回答しました。ここでは、その答えをより言語にとらわれないように変更しました。

PSこれは、Michael Feathersによる氷山のクラスと模索ツールに関する関連する講義です。https//www.youtube.com/watch?v = 4cVZvoFGJTU


利点として「これらのクラスをシステムの他の部分で再利用する可能性がある」と記載した場合の問題は、関数をプライベートとしてマークする理由は、関数を他の部分で使用したくないためですシステム。これは言語固有の問題です:理想的には、これは「モジュール」にプライベートですが、言語がそれをサポートしていない場合(PHPなど)、私のクラスはユニットではなくモジュールを表します。プライベートメソッドは再利用可能なコードです独自のコントラクトを使用しますが、そのクラス内でのみ再利用する必要があります。
IMSoP 2017年

私はあなたの言っていることを理解していますが、Pythonコミュニティがその問題を処理する方法が好きです。問題の「プライベート」メンバーに先頭_にを付けて名前を付けると、「ねえ、これは「プライベート」です。使用できますが、完全に開示されています。再利用するようには設計されていません。本当に使用する場合にのみ使用してくださいあなたが何をしているのか知っている」。どの言語でも同じアプローチを取ることができます。それらのメンバーを公開しますが、先頭にを付け_ます。あるいは、それらの関数は実際にはプライベートであり、パブリックインターフェイスを介してテストされるだけかもしれません(詳細については回答を参照してください)。ケースバイケースであり、一般的なルールはありません
Matt Messersmith、

26

オブジェクトのパブリックインターフェイスをテストするのが最善だと思います。外界の視点からは、重要なのはパブリックインターフェイスの動作のみであり、これがユニットテストの方向性です。

オブジェクトに対していくつかの堅牢な単体テストを作成したら、インターフェイスの背後の実装が変更されたという理由だけで、それらのテストに戻って変更する必要はありません。この状況では、ユニットテストの一貫性が損なわれています。


21

あなたのプライベートメソッドがあなたのパブリックメソッドを呼び出すことによってテストされていない場合、それは何をしていますか?私は保護されていない、または友人のプライベートな話をしています。


3
ありがとうございました。これは驚くほど過小評価されているコメントであり、特に執筆されてからほぼ8年経った今でもなお関連性があります。
Sauronlord、2015年

1
同じ理由で、ソフトウェアのすべての機能がそこから何らかの方法で実行されるため、ユーザーインターフェイスからのソフトウェアのテストのみ(システムレベルのテスト)と主張することができます。
Dirk Herrmann、

18

プライベートメソッドが適切に定義されている場合(つまり、テスト可能な関数があり、時間の経過とともに変化することを意図していない場合)、はい。私はそれが理にかなっている場所でテスト可能なすべてのものをテストします。

たとえば、暗号化ライブラリは、一度に8バイトのみを暗号化するプライベートメソッドを使用してブロック暗号化を実行するという事実を隠している可能性があります。私はそのための単体テストを作成します-それが隠されていても、変更することを意味していません、そしてそれが壊れた場合(たとえば、将来のパフォーマンス強化のため)、それが壊れたのは、プライベート関数だけではなく、公共機能の1つが壊れたということ。

後でデバッグを高速化します。

-アダム


1
この場合、そのプライベートメソッドを別のクラスに移動してから、それをパブリックまたはパブリックスタティックにするだけでは意味がありませんか?
アウトロープログラマ

+1プライベートメンバー関数をテストせず、パブリックインターフェイスのテストが失敗した場合、何かが何であるかわからないまま、何かが壊れているのと同じ結果が得られます。
Olumide 2017

12

テスト駆動(TDD)を開発している場合は、プライベートメソッドをテストします。



4
真実ではありません。パブリックメソッドをテストし、テストに合格したら、「クリーニング」ステップ中にパブリックメソッドのコードをプライベートメソッドに抽出します。プライベートメソッドをテストすると、実装の変更が困難になるため、悪い考えです(いつか何か方法を変更したい場合、それを変更してすべてのテストを実行でき、新しい方法で彼らがパスするべきことは正解です。私はこれのためにすべてのプライベートテストを変更する必要はありません)。
Tesseract 2017年

1
@Tesseract、私があなたのコメントを複数回賛成できるとしたら、私はそうします。「...それを変更してすべてのテストを実行でき、新しいことを実行する新しい方法が正しければ、合格するはずです」これは、単体テストの主な利点の1つです。自信を持ってリファクタリングできます。クラスの内部プライベート動作を完全に変更でき、(すべてのユニットテストを書き直すことなく)(パブリックインターフェース上にある)すべての(既存の)ユニットテストがまだパスしているため、何も壊していないと確信できます。
Lee

同意しない、以下の私の回答を参照してください
マットメッサースミス2018

11

私はこの分野の専門家ではありませんが、単体テストでは実装ではなく動作をテストする必要があります。プライベートメソッドは厳密に実装の一部であるため、IMHOはテストしないでください。


その後、実装はどこでテストされますか?一部の機能がキャッシングを使用している場合、これは実装の詳細であり、キャッシングはテストされていませんか?
Dirk Herrmann、

11

私たちは推論によってプライベートメソッドをテストします。つまり、クラスの合計テストカバレッジを少なくとも95%探しますが、テストにはパブリックメソッドまたは内部メソッドのみを呼び出します。カバレッジを取得するには、発生する可能性のあるさまざまなシナリオに基づいて、パブリック/内部に対して複数の呼び出しを行う必要があります。これにより、テスト対象のコードの目的を中心にテストがより集中的になります。

あなたがリンクした投稿に対するTrumpiの回答は最高です。


9

ユニットテストはパブリックメソッドをテストするためのものだと思います。パブリックメソッドはプライベートメソッドを使用するため、間接的にもテストされます。


7

私はこの問題をしばらくの間、特にTDDで試してみました。

TDDの場合、この問題に十分に対処できると思う投稿が2つあります。

  1. プライベートメソッド、TDD、テスト駆動型リファクタリングのテスト
  2. テスト駆動開発はテストではない

要約すれば:

  • テスト駆動開発(設計)テクニックを使用する場合、プライベートメソッドは、すでに機能しテスト済みのコードのリファクタリングプロセス中にのみ発生します。

  • プロセスの性質上、徹底的にテストされた機能から抽出された単純な実装機能は、自己テストされます(つまり、間接テストのカバレッジ)。

コーディングの最初の部分では、デザインをカプセル化/記述しているため、ほとんどのメソッドがより高レベルの関数になることは私には十分に明らかです。

したがって、これらのメソッドは公開され、それらのテストは十分簡単に​​なります。

すべてが正常に機能し、読みやすさ清潔のためにリファクタリングを行うようになると、プライベートメソッドは後で追加されます。


6

上記で引用したように、「プライベートメソッドをテストしない場合、メソッドが壊れないことをどのようにして確認しますか?」

これは大きな問題です。単体テストの大きなポイントの1つは、どこで、いつ、どのように何かがASAPを破ったかを知ることです。したがって、開発とQAの労力を大幅に削減できます。テストされるすべてがパブリックである場合、クラスの内部の正直な報道と描写はありません。

これを行うための最良の方法の1つは、テスト参照をプロジェクトに追加し、テストをプライベートメソッドと並行してクラスに配置することです。テストが最終プロジェクトに組み込まれないように、適切なビルドロジックを追加します。

次に、これらのメソッドをテストすることのすべての利点が得られ、数分または数時間ではなく、数秒で問題を見つけることができます。

要約すると、はい、プライベートメソッドを単体テストします。


2
同意しません。「プライベートメソッドをテストしない場合、メソッドが壊れないことをどうやって確認できますか?」:私が知っているのは、プライベートメソッドが壊れている場合、プライベートメソッドに依存するパブリックメソッドをテストするテストが失敗するためです。パブリックメソッドの実装方法について気が変わるたびにテストを変更する必要はありません。また、ユニットテストの主な目的は、どのコード行に欠陥があるかを具体的に知ることではなく、変更を加えたときに(プライベートメソッドに)何かを壊していないことを多少なりとも確信できることだと思います。
Tesseract 2017年

6

すべきではない。プライベートメソッドがテストする必要がある十分な複雑さを持っている場合、それらを別のクラスに置く必要があります。高い凝集度を維持します。クラスには1つの目的しかありません。クラスpublicインターフェースで十分です。


3

プライベートメソッドをテストしない場合、メソッドが壊れないことをどうやって確認できますか?


19
あなたのパブリックメソッドのテストを通して書くことによって。
scubabbl 2008

3
これらのプライベートメソッドは、おそらくクラスのパブリックメソッドによって呼び出されます。したがって、プライベートメソッドを呼び出すパブリックメソッドをテストするだけです。
2008

1
パブリックメソッドが適切に機能している場合は、アクセスするプライベートメソッドが適切に機能していることは明らかです。

パブリックメソッドのテストが失敗した場合、オブジェクト/コンポーネントなどの下位レベルで何かが正しくないことがすぐにわかります。
Rob

3
それはだ、本当にそれは内部関数や(内部関数は大丈夫です、あなたが外部に集中できることを逆か)破っただけでなく、外部関数だということを知っている、しかし、素敵。
アダムデイビス

2

それは明らかに言語に依存しています。過去にc ++を使用して、テストクラスをフレンドクラスとして宣言しました。残念ながら、これにはプロダクションコードがテストクラスについて知っている必要があります。


5
友達のキーワードは私を悲しくさせます。
Rob

テストクラスが別のプロジェクトで実装されている場合、これは問題ではありません。重要なのは、量産コードがテストクラスを参照しないことです。
Olumide

2

プライベートメソッドが実装の詳細と見なされるため、テストする必要がないという観点を理解しています。オブジェクトの外でのみ開発する必要がある場合は、このルールを使用します。しかし、私たちは、オブジェクトの外部でのみ開発し、パブリックメソッドのみを呼び出す、制限された開発者のようなものですか?それとも実際にそのオブジェクトを開発していますか?外部オブジェクトをプログラムする必要はないので、おそらくこれらのプライベートメソッドを、開発中の新しいパブリックメソッドに呼び出す必要があります。プライベートメソッドがすべてのオッズに対して抵抗することを知って素晴らしいと思いませんか?

私がそのオブジェクトに別のパブリックメソッドを開発している場合、これをテストする必要があると答える人もいるでしょう(プライベートメソッドはテストなしで実行できます)。ただし、これはオブジェクトのすべてのパブリックメソッドにも当てはまります。Webアプリを開発する場合、オブジェクトのすべてのパブリックメソッドはコントローラーメソッドから呼び出されるため、コントローラーの実装の詳細と見なすことができます。

では、なぜオブジェクトをユニットテストするのでしょうか。本当に難しいので、基礎となるコードのすべてのブランチをトリガーする適切な入力でコントローラーのメソッドをテストしていることを確認することは不可能ではありません。つまり、スタックの上位にあるほど、すべての動作をテストすることが難しくなります。プライベートメソッドも同様です。

私にとって、私的な方法と公的な方法の境界は、テストに関しては心理的な基準です。私にとってより重要な基準は次のとおりです。

  • メソッドは別の場所から複数回呼び出されていますか?
  • テストを必要とするほど洗練された方法ですか?

1

プライベートメソッドが巨大であるか、複雑であるか、独自のテストを必要とするほど重要であることがわかった場合は、それを別のクラスに入れ、そこで公開します(メソッドオブジェクト)。次に、以前はプライベートでしたが今は独自のクラスで動作するパブリックメソッドを簡単にテストできます。


1

私はユニットテストの概念を理解していませんが、今はそれが目的であるかを知っています。

単体テストは完全なテストではありません。したがって、QAおよび手動テストの代わりにはなりません。この側面でのTDDの概念は間違っています。プライベートメソッドだけでなく、リソース(特に、制御できないリソース)を使用するメソッドも含めてすべてをテストすることはできないためです。TDDは、そのすべての品質に基づいており、達成できなかったものです。

ユニットテストはピボットテスト です。任意のピボットをマークすると、ピボットの結果は同じままになります。


1

パブリックとプライベートは、テストからどのAPIを呼び出すかを区別するのに役立ちません。また、メソッドとクラスも区別されません。ほとんどのテスト可能なユニットは、1つのコンテキストでは表示されますが、他のコンテキストでは非表示になります。

重要なのは補償範囲とコストです。プロジェクトのカバレッジ目標(ライン、ブランチ、パス、ブロック、メソッド、クラス、同等クラス、ユースケース...チームが決定するものすべて)を達成しながら、コストを最小限に抑える必要があります。

そのため、カバレッジを確保するためのツールを使用し、コストが最小になるようにテストを設計します(短期および長期)。

テストを必要以上に高価にしないでください。パブリックエントリポイントのみをテストするのが最も安い場合は、それを行ってください。プライベートメソッドをテストするのが最も安い場合は、それを実行します。

経験を積むにつれて、テストメンテナンスの長期的なコストを回避するためにリファクタリングする価値がある時期を予測できるようになります。


0

メソッドが十分/複雑で十分である場合、通常は「保護」してテストします。一部のメソッドは非公開のままにされ、public / protectedメソッドの単体テストの一部として暗黙的にテストされます。


1
@VisibleForTestingはそのための注釈です。私はむしろdp4j.comを使用し、テストするためのカプセル化をリラックスしていないと思います
simpatico

0

多くの人が同じ考えのもとにいるのを目にします:公共レベルでのテスト。しかし、それは私たちのQAチームが行うことではありませんか?彼らは入力と期待される出力をテストします。開発者としてパブリックメソッドのみをテストする場合は、QAの仕事をやり直すだけであり、「ユニットテスト」による付加価値はありません。


現在の傾向は、QAチームを削減するか、持たないことです。これらの単体テストは、エンジニアがマスターブランチにコードをプッシュするたびに実行される自動テストになります。QAがあっても、自動テストほど速くアプリケーション全体をテストすることはできません。
Patrick Desjardins

0

「プライベートメソッドをテストする必要がありますか?」に対する答え 「……時々」です。通常、クラスのインターフェースに対してテストする必要があります。

  • その理由の1つは、機能を2度カバーする必要がないためです。
  • もう1つの理由は、プライベートメソッドを変更すると、オブジェクトのインターフェイスがまったく変更されていなくても、それらの各テストを更新する必要があるためです。

次に例を示します。

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

これでRefactoredThing5つのテストがあり、そのうち2つはリファクタリングのために更新する必要がありましたが、オブジェクトの機能は実際には変更されていません。したがって、物事はそれよりも複雑で、次のような出力の順序を定義するメソッドがあるとします。

def some_string_positioner
  if some case
  elsif other case
  elsif other case
  elsif other case
  else one more case
  end
end

これは外部のユーザーによって実行されるべきではありませんが、カプセル化クラスは、そのクラスのロジックを何度も繰り返し実行するために重い場合があります。この場合、おそらくこれを別のクラスに抽出し、そのクラスにインターフェースを与え、それに対してテストすることをお勧めします。

最後に、メインオブジェクトが非常に重く、メソッドが非常に小さく、出力が正しいことを確認する必要があるとしましょう。「私はこのプライベートメソッドをテストしなければならない!」と考えています。重い作業の一部を初期化パラメーターとして渡すことで、オブジェクトを軽量化できると思いませんか?次に、より軽いものを渡し、それに対してテストできます。


0

なぜプライベートメソッドをテストすべきではないのですか?さらに、Mockitoなどの一般的なモッキングフレームワークは、プライベートメソッドのテストをサポートしていません。


0

主なポイントは

ロジックが正しいことを確認するためにテストし、プライベートメソッドがロジックを実行している場合は、テストする必要があります。だよね?それで、なぜそれをスキップするのですか?

メソッドの可視性に基づいてテストを作成することは、まったく無関係です。

逆に

一方、元のクラスの外部でプライベートメソッドを呼び出すことが主な問題です。また、一部のモックツールでプライベートメソッドをモックする場合にも制限があります。(例:モッキート

それをサポートするPower Mockのようないくつかのツールがありますが、それは危険な操作です。その理由は、それを実現するにはJVMをハックする必要があるからです。

実行できる回避策の1つは、(プライベートメソッドのテストケースを作成する場合)

これらのプライベートメソッドをprotectedとして宣言します。しかし、いくつかの状況では不便かもしれません。


0

これは、パブリックまたはプライベートのメソッドまたは関数に関するだけでなく、実装の詳細に関するものです。プライベート関数は、実装の詳細の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のテストも変更する必要があります。プライベートなものをパブリックにしても、魔法のように安定性が変わるわけではありません。


0

はい、できる限りプライベートメソッドをテストする必要があります。どうして?最終的に、同じ入力に対して同じプライベート関数を繰り返し暗黙的にテストすることになるテストケースの不要な状態空間の爆発を回避するため。その理由を例を挙げて説明しましょう。

次のわずかに工夫された例を考えてみましょう。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とはありませんisPrimeandAll

テスターとして、私たちはそれぞれの引数のための5つの可能性に興味があるかもしれません:< 0= 0= 1prime > 1not prime > 1。ただし、徹底的に説明するには、引数のすべての組み合わせがどのように作用するかを確認する必要があります。つまり5*5*5、直感によると、これは125のテストケースであり、この機能を徹底的にテストする必要があります。

一方、プライベート関数をテストすることが許可されれば、より少ないテストケースで十分な範囲をカバーできます。isPrime以前の直感と同じレベルにテストするために必要なテストケースは5つだけです。そして、ダニエルジャクソンによって提案されたスモールスコープ仮説により、andAll関数を短い長さ、たとえば3または4 までテストするだけで済みます。これは最大で16のテストになります。合計21のテストです。もちろん、125の代わりに。おそらくいくつかのテストをで実行する必要がありますallPrimeが、私たちが気にかけていた入力シナリオの125の組み合わせすべてを網羅することはそれほど義務付けられているとは思わないでしょう。ほんの少しの幸せな道。

確かに人為的な例ですが、明確なデモンストレーションには必要でした。そして、パターンは実際のソフトウェアにまで及びます。プライベート関数は通常、最下位レベルのビルディングブロックであり、したがって、多くの場合一緒に結合されて、より高いレベルのロジックを生成します。より高いレベルの意味では、さまざまな組み合わせにより、より低いレベルのものの繰り返しが多くなります。


まず、あなたが示したような純粋な関数でそのような組み合わせをテストする必要はありません。の呼び出しisPrimeは完全に独立しているため、すべての組み合わせを盲目的にテストすることはほとんど無意味です。次に、isPrimeprivate と呼ばれる純粋な関数にマークを付けると、どこから始めればよいかわからないほど多くの設計ルールに違反します。isPrime非常に明確に公共の機能であるべきです。そうは言っても、この極端に悪い例に関係なく、私はあなたが言っていることを理解しています。しかし、それはあなたがしたい前提オフ組み込まれているしたい、実際のソフトウェアシステムで、これはめったに良いアイデアではないとき、組み合わせテストを行うことを。
Matt Messersmith、

マットはい、例は理想的ではありません。しかし、原則は明白でなければなりません。
Colm Bhandal

前提は、組み合わせテストを実行したいということではありません。パズルの公開部分のみをテストするように制限した場合、それはあなたがしなければならないことです。適切なカプセル化の原則に準拠するために、純粋な関数をプライベートにしたい場合があります。そして、この純粋なプライベート関数は、パブリック関数で使用できます。おそらく他の純粋なプライベート関数との組み合わせで。その場合、プライベートをテストしてはならないという教義に従って、プライベートコンポーネントのモジュールテストを実行するのではなく、パブリック関数を組み合わせてテストする必要があります。
Colm Bhandal

0

メソッドをpackage-private、つまりdefaultにすることもでき、プライベートである必要がない限り、ユニットテストを実行できるはずです。

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