web-dev-qa-db-ja.com

それを単体テストするためにプライベートメソッドを公開する...良いアイデアですか?

モデレーター注:ここにはすでに39件の回答が投稿されています(一部は削除されています)。投稿する前にyour回答、ディスカッションに意味のある何かを追加できるかどうかを検討してください。他の誰かがすでに言ったことを繰り返しているだけです。


クラスのプライベートテストをいくつかの単体テストを書くためにパブリックにする必要がある場合があります。

通常、これは、メソッドにクラス内の他のメソッド間で共有されるロジックが含まれており、それ自体でロジックをテストする方が賢いためです。 。

私はそれをやるのが本当に好きではないので、他の人は自分でこれをやっているのでしょうか??個人的には、ボーナスは、クラス以外のサービスを実際に提供しないメソッドを公開する問題よりも重要だと思います...

UPDATE

みんなの回答に感謝し、人々の興味をそそったようです。これはクラスがこれまでに使用される唯一の方法であるため、一般的なコンセンサスはパブリックAPIを介してテストする必要があると思います。これに同意します。上記のいくつかのケースは、私が上記のことを行う場合は珍しいケースであり、それを行うメリットはそれだけの価値があると考えました。

しかし、私はそれが実際に起こるべきではないことを皆が指摘するのを見ることができます。そして、もう少し考えてみると、テストに対応するためにコードを変更するのは悪い考えだと思います-結局のところ、テストはある意味でサポートツールであり、「サポートツールをサポートする」ためにシステムを変更するのは露骨だと思います悪い習慣。

282
jcvandan

注:
この回答はもともと質問に対して投稿されたものです 単体テストはゲッターを介してプライベートインスタンス変数を公開する正当な理由ですか? これはマージされたため、少し固有のものである可能性がありますそこで提示されたユースケースへ。

一般的な声明として、私は通常、「プロダクション」コードをリファクタリングして、テストを容易にすることに全力を尽くしています。しかし、私はそれがここで良い電話になるとは思わない。優れた単体テストは(通常)クラスの実装の詳細を気にする必要はなく、目に見える動作のみを気にする必要があります。内部スタックをテストに公開する代わりに、first()またはlast()を呼び出した後、クラスが期待する順序でページを返すことをテストできます。

たとえば、次の擬似コードを考えてみましょう。

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
182
Mureinik

個人的には、パブリックAPIを使用して単体テストを行い、プライベートメソッドをpublicjustにしてテストを容易にすることは絶対にしません。

プライベートメソッドを単独でテストする場合は、Javaで Easymock / Powermock を使用してこれを実行できます。

あなたはそれについて実用的でなければならず、また物事がテストするのが難しい理由を認識する必要があります。

' テストを聞く '-テストが困難な場合、デザインについて何か教えてくれますか?このメソッドのテストが簡単で、パブリックAPIを介したテストで簡単にカバーできる場所にリファクタリングできますか?

マイケルフェザーズが「 レガシーコードで効果的に作業する

「多くの人がこの問題を回避する方法を見つけ出すために多くの時間を費やしています...本当の答えは、プライベートメソッドをテストする衝動があれば、そのメソッドはプライベートであってはならないということです。面倒なことは、それは別の責任の一部であるためです。別のクラスにあるべきです。」 [レガシーコードを効果的に使用する(2005)M. Feathers]

147
blank

他の人が言ったように、プライベートメソッドをユニットテストすることはまったく疑わしいです。プライベート実装の詳細ではなく、パブリックインターフェイスを単体テストします。

そうは言っても、C#でプライベートなものを単体テストするときに使用する手法は、アクセシビリティ保護をプライベートから内部にダウングレードし、 InternalsVisibleTo を使用してユニットテストアセンブリをフレンドアセンブリとしてマークすることです。ユニットテストアセンブリは、内部をパブリックとして扱うことができますが、誤ってパブリックサーフェスに追加することを心配する必要はありません。

62
Eric Lippert

多くの回答が公開インターフェースのテストのみを示唆していますが、私見ではこれは非現実的です-メソッドが5つのステップを実行する場合、それらの5つのステップをすべてではなく別々にテストする必要があります。これには、5つのメソッドすべてをテストする必要があります。(テスト以外)privateです。

「プライベート」メソッドをテストする通常の方法は、すべてのクラスに独自のインターフェイスを与え、「プライベート」メソッドをpublicにするが、インターフェイスには含めないことです。このように、それらはまだテストできますが、インターフェースを肥大化しません。

はい、これはファイルとクラスの膨張をもたらします。

はい、これによりpublicおよびprivate指定子が冗長になります。

はい、これはお尻の痛みです。

残念ながら、これは多くのコードをテスト可能にするための犠牲の1つです。おそらく、将来の言語(または将来のバージョンのC#/ Javaでも)には、クラスおよびモジュールのテスト容易性をより便利にする機能が含まれるでしょう。しかし、一方で、これらのフープを飛び越えなければなりません。


これらの各ステップは 独自のクラス であると主張する人もいますが、私は同意しません-それらがすべて状態を共有する場合、5つのメソッドが行う5つの別々のクラスを作成する理由はありません。さらに悪いことに、これはファイルとクラスの膨張をもたらします。 Plus、モジュールのパブリックAPIに感染します-別のモジュールからテストする場合、それらのクラスはすべてpublicでなければなりません (それ、または同じモジュールにテストコードを含めます。つまり、テストコードを製品とともに出荷します)

単体テストでは、コードを他の部分でクラスを使用する唯一の方法であるパブリックコントラクトをテストする必要があります。プライベートメソッドは実装の詳細です。パブリックAPIが正常に機能する限り、テストは行わないでください。実装は重要ではなく、テストケースを変更せずに変更できます。

26
kan

IMO、あなたのクラスが内部にどのように実装されているかについて深い仮定をしないテストを書くべきです。おそらく別の内部モデルを使用して後でリファクタリングする必要がありますが、以前の実装と同じ保証を引き続き行います。

これを念頭に置いて、クラスの現在の内部実装に関係なく、契約がまだ保持されているかどうかのテストに集中することをお勧めします。パブリックAPIのプロパティベースのテスト。

20
SimY4

パッケージをプライベートにしてはどうですか?その後、テストコードはそれを(およびパッケージ内の他のクラスも)見ることができますが、ユーザーからはまだ隠されています。

しかし、実際には、プライベートメソッドをテストするべきではありません。これらは実装の詳細であり、契約の一部ではありません。彼らが行うことはすべて、パブリックメソッドを呼び出すことでカバーする必要があります(パブリックメソッドによって実行されないコードがそこにある場合は、それが行われます)。プライベートコードが複雑すぎる場合、クラスはおそらくあまりにも多くのことを実行しており、リファクタリングが必要です。

メソッドを公開することは大きなコミットメントです。一度それを行うと、人々はそれを使用することができ、あなたはもはやそれらを変更することはできません。

18
Thilo

Update他の多くの場所で、この質問に対してより拡張された、より完全な答えを追加しました。これは私のブログで見つけることができます

テストするために何かを公開する必要がある場合、これは通常、テスト対象のシステムが 単一責任原則 に従っていないことを示唆しています。したがって、導入する必要のあるクラスが欠落しています。コードを新しいクラスに抽出した後、それを公開します。これで簡単にテストでき、SRPをフォローしています。他のクラスは、この新しいクラスを構成経由で呼び出すだけです。

メソッドを公開する/コードをテストアセンブリから見えるようにマークするなどの言語のトリックを使用することは、常に最後の手段です。

例えば:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

バリデーターオブジェクトを導入することでこれをリファクタリングします。

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

バリデーターが正しく呼び出されることをテストするだけです。検証の実際のプロセス(以前のプライベートロジック)は、完全に分離してテストできます。この検証に合格するために複雑なテストを設定する必要はありません。

15
Finglas

いくつかの素晴らしい答え。私が言及しなかったことの1つは、テスト駆動開発(TDD)では、リファクタリング段階でプライベートメソッドが作成されることです(リファクタリングパターンの例については Extract Method を参照してください)。したがって、すでに必要なテストカバレッジがあります。正しく行われた場合(そしてもちろん、正確性に関してはさまざまな意見が寄せられます)、テストできるようにプライベートメソッドをパブリックにする必要について心配する必要はありません。

12
csano

スタック管理アルゴリズムをユーティリティクラスに分割してみませんか?ユーティリティクラスはスタックを管理し、パブリックアクセサーを提供できます。その単体テストは、実装の詳細に焦点を合わせることができます。アルゴリズム的に扱いにくいクラスの詳細なテストは、Edgeケースをしわ寄せし、カバレッジを確保するのに非常に役立ちます。

その後、現在のクラスは、実装の詳細を公開することなく、ユーティリティクラスにきれいに委任できます。他の人が推奨しているように、そのテストはページネーションの要件に関連します。

10

Javaには、パッケージプライベートにするオプションもあります(つまり、可視性修飾子を省略します)。単体テストがテスト対象のクラスと同じパッケージに含まれている場合、これらのメソッドを見ることができ、メソッドを完全に公開するよりも少し安全です。

9
Tom Jefferys

プライベートメソッドは通常、「ヘルパー」メソッドとして使用されます。したがって、これらは基本的な値のみを返し、オブジェクトの特定のインスタンスで動作することはありません。

テストする場合、いくつかのオプションがあります。

  • リフレクションを使用する
  • メソッドパッケージにアクセス権を付与する

あるいは、ヘルパークラスが新しいクラスの十分な候補である場合、パブリックメソッドとしてヘルパーメソッドを使用して新しいクラスを作成できます。

これには非常に 良い記事 があります。

9
adamjmarkham

必要に応じて、リフレクションを使用してプライベート変数にアクセスします。

しかし、実際には、クラスの内部状態は気にせず、予想できる状況でパブリックメソッドが期待する結果を返すことをテストするだけです。

6
Daniel Alexiuc

C#を使用している場合、メソッドを内部にできます。そうすれば、パブリックAPIを汚染することはありません。

次に、dllに属性を追加します

[アセンブリ:InternalsVisibleTo( "MyTestAssembly")]

これで、すべてのメソッドがMyTestAssemblyプロジェクトに表示されます。完璧ではないかもしれませんが、それをテストするためにプライベートメソッドを公開するよりも良いでしょう。

6
Piotr Perak

単体テストに関しては、メソッドを追加しないでください。各テストの前に呼び出されるfirst()メソッドについてのテストケースを作成した方が良いと思います。 -next()previous()およびlast()を複数回呼び出して、結果が期待どおりかどうかを確認できます。クラスにメソッドを追加しなければ(テスト目的でのみ)、テストの「ブラックボックス」原則に従うことになります。

6
Ion

最初に、メソッドを別のクラスに抽出して公開する必要があるかどうかを確認します。そうでない場合は、パッケージを保護し、Javaで @ VisibleForTesting の注釈を付けます。

5
Noel Yap

あなたが何らかの利益を得たり、潜在的に問題が発生するかどうかわからないので、それは悪い考えだと思います。プライベートメソッドをテストするためだけに呼び出しのコントラクトを変更する場合、クラスの使用方法をテストするのではなく、意図しないシナリオを作成します。

さらに、メソッドをパブリックとして宣言すると、6か月後に(メソッドをパブリックにする唯一の理由がテストのためであることを忘れた後)、あなた(またはプロジェクトを引き継いだ場合)使用しないでください。意図しない結果やメンテナンスの悪夢につながる可能性があります。

5
beny23

更新では、パブリックAPIを使用してテストするだけでよいと述べています。ここには実際に2つの学校があります。

  1. ブラックボックステスト

    ブラックボックススクールは、クラスはその内部の実装を誰も見ることができないブラックボックスと見なされるべきだと言っています。これをテストする唯一の方法は、パブリックAPIを使用することです。クラスのユーザーが使用するのと同じです。

  2. ホワイトボックステスト。

    ホワイトボックススクールは、クラスの実装に関する知識を使用し、クラスが適切に機能することを確認するためにクラスをテストすることを当然と考えています。

私は本当に議論に参加することはできません。クラス(またはライブラリなど)をテストするには、2つの異なる方法があることを知るのは面白いと思いました。

5
bengtb

単独でテストするプライベートメソッドは、クラスに別の「概念」が埋め込まれていることを示しています。その「概念」を独自のクラスに抽出し、別の「ユニット」としてテストします。

このビデオ を見て、このテーマに関する非常に興味深い見解をご覧ください。

4
Jordão

実際にこれを行うべき状況があります(たとえば、いくつかの複雑なアルゴリズムを実装している場合)。パッケージプライベートで行うだけで十分です。しかし、ほとんどの場合、おそらく他のクラスにロジックを分解する必要があるあまりにも複雑なクラスがあります。

テストでコードを指定しないでください。私はTDDや他のDDのことを言っているわけではありません。アプリでこれらのメソッドを公開する必要がありますか。もしそうなら、それらをテストします。そうでない場合は、テストのためだけに公開しないでください。変数なども同様です。アプリケーションのニーズに応じてコードを決定し、テストにニーズが満たされていることをテストさせます。 (繰り返しますが、最初にテストすることを意味するわけではありません。テストの目標を達成するためにクラス構造を変更することを意味するわけではありません)。

代わりに、「より高いテスト」を行う必要があります。プライベートメソッドを呼び出すメソッドをテストします。ただし、テストでは、「実装の決定」ではなく、アプリケーションのニーズをテストする必要があります。

例(ここではbodの擬似コード);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

「追加」をテストする理由はなく、代わりに「本」をテストできます。

テストでコード設計の決定を決して行わないでください。その結果を得る方法ではなく、期待する結果が得られることをテストします。

3
coteyr

Guavaには、そうでなければ拡張スコープ(パッケージまたはパブリック)を持つメソッドをマークするための@VisibleForTesting注釈があります。同じことに対して@Privateアノテーションを使用します。

パブリックAPIはテストする必要がありますが、通常はパブリックではないものにアクセスするのが便利で賢明な場合があります。

いつ:

  • クラスは、それを複数のクラスに分割することにより、非常に読みにくくなります。
  • よりテストしやすくするために、
  • そして、内部へのいくつかのテストアクセスを提供することは、トリックを行います

宗教が工学に勝っているようです。

2
Ed Staub

通常、これらのメソッドはprotectedのままにして、同じパッケージ(ただし、別のプロジェクトまたはソースフォルダー)内に単体テストを配置します。クラスローダーは同じ名前空間に配置するため、すべての保護メソッドにアクセスできます。

2
hiergiltdiestfu

私は、ユニットをテストすることのボーナスが、一部のメンバーの認知度を高める問題を上回ることに同意する傾向があります。わずかな改善は、それを保護および仮想化し、それをテストクラスでオーバーライドして公開することです。

あるいは、個別にテストしたい機能である場合、デザインからオブジェクトが欠落していることを示唆していませんか?たぶん、あなたはそれを別のテスト可能なクラスに入れることができます...そして、あなたの既存のクラスはこの新しいクラスのインスタンスに委任するだけです。

2
IanR

いいえ、その猫をスキニングするより良い方法があるからです。

一部のユニットテストハーネスは、テストモードでビルドされたときに自動的に展開してフックを作成するクラス定義のマクロに依存しています。非常にCスタイルですが、動作します。

簡単なOOイディオムは、テストするものを「プライベート」ではなく「保護」することです。テストハーネスは、テスト対象のクラスを継承し、すべての保護されたメンバーにアクセスできます。

または、「友人」オプションを選択します。個人的には、これはカプセル化の規則を破るので、私が最も好きなC++の機能ですが、C++がいくつかの機能を実装する方法が必要になることがあります。

とにかく、ユニットテストをしているなら、それらのメンバーに値を注入する必要がありそうです。ホワイトボックスのテキストメッセージは完全に有効です。そしてそれは本当にwouldカプセル化を壊します。

2
Graham

validateverifycheckなどのようなメソッドをクラスに追加して、オブジェクトの内部状態をテストするために呼び出せるようにすることがよくあります。

リリース用にコンパイルされないように、このメソッドはifdefブロックでラップされていることがあります(ほとんどC++で記述しています)。しかし、リリースでは、プログラムのオブジェクトツリーを調べて物事をチェックする検証メソッドを提供すると便利なことがよくあります。

2
Zan Lynx

私は通常、テストクラスをテスト対象のクラスと同じプロジェクト/アセンブリに保持します。
この方法では、関数/クラスをテスト可能にするためにinternal可視性のみが必要です。

これにより、テストクラスを除外する必要があるビルドプロセスが多少複雑になります。これを実現するには、すべてのテストクラスにTestedClassTestという名前を付け、正規表現を使用してそれらのクラスをフィルタリングします。

もちろん、これは質問のC#/ .NET部分にのみ適用されます

2
yas4891

他の人のコメントで広く指摘されているように、単体テストはパブリックAPIに焦点を当てるべきです。ただし、賛否両論と正当化は別として、リフレクションを使用して単体テストでプライベートメソッドを呼び出すことができます。もちろん、JREセキュリティで許可されていることを確認する必要があります。プライベートメソッドの呼び出しは、Spring Frameworkが ReflectionUtils で使用するものです(makeAccessible(Method)メソッドを参照)。

プライベートインスタンスメソッドを使用した小さなクラスの例を次に示します。

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

そして、プライベートインスタンスメソッドを実行するサンプルクラス。

import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

Bを実行すると、Doing something private.が出力されます。本当に必要な場合は、リフレクションをユニットテストで使用して、プライベートインスタンスメソッドにアクセスできます。

1
Dan Cruz

.Netには、クラスのプライベートメソッドにアクセスできるように特別に設計されたPrivateObjectという特別なクラスがあります。

詳細については MSDN またはこちら Stack Overflow をご覧ください

(これまで誰も言及していないのではないかと思っています。)

ただし、これでは不十分な場合もあります。その場合、リフレクションを使用する必要があります。

それでも、私はプライベートメソッドをテストしないという一般的な推奨に従いますが、いつものように常に例外があります。

1
yoel halb

個人的には、プライベートメソッドをテストするときに同じ問題が発生します。これは、テストツールの一部が制限されているためです。設計ではなくツールを変更する必要性に対応しない場合、限られたツールで設計を推進するのは良くありません。 C#iを求めることは良いテストツールを提案できないためですが、Javaには2つの強力なツールがあります。TestNGとPowerMockで、.NETプラットフォームに対応するテストツールを見つけることができます。

0
Xoke

ユニットテストのポイントは、そのユニットのパブリックAPIの動作を確認することです。プライベートメソッドをテスト専用に公開する必要はありません。その場合、インターフェイスを再考する必要があります。プライベートメソッドは、パブリックインターフェイスの「ヘルパー」メソッドと考えることができます。したがって、プライベートメソッドを呼び出すため、パブリックインターフェイスを介してテストされます。

これを行う必要があることがわかる唯一の理由は、クラスがテスト用に適切に設計されていないことです。

0
Chris Johnson

そのすべてはプラグマティズムについてです。ユニットテストは、ある意味ではコードのクライアントであり、適切なレベルのコードカバレッジを実現するには、コードを非常にテストしやすくする必要があります。テスト用のコードが非常に複雑な場合、コードの効果的な公開継ぎ目なしで必要なコーナーケースをセットアップできるため、ソリューションは潜在的な障害になります。 IoCを使用すると、この問題にも役立ちます。

0
Dean Chalk