プライベートメソッドをどのようにユニットテストしますか?


184

私はJavaプロジェクトに取り組んでいます。ユニットテストは初めてです。Javaクラスのプライベートメソッドを単体テストする最良の方法は何ですか?


1
StackOverflowでこの質問を確認してください。いくつかの手法について説明し、説明します。プライベートメソッドを単体テストする最良の方法は何ですか?
カイロン

34
私の意見では、利用可能なものをテストする必要があるため、プライベートメソッドはテストを必要としないと常に考えていました。パブリックメソッド。パブリックメソッドを破ることができない場合、プライベートメソッドが何をしているかは本当に重要ですか?
リグ

1
パブリックメソッドとプライベートメソッドの両方をテストする必要があります。したがって、テストドライバーは通常、テストするクラス内にある必要があります。このように。
過剰交換

2
@Rig-+1-パブリックメソッドからプライベートメソッドに必要なすべての動作を呼び出すことができるはずです-できない場合は、とにかく機能を呼び出せないため、テストする意味がありません。
ジェームズアンダーソン

使用@Jailbreakからマニホールド直接プライベートメソッドにアクセスするためのフレームワーク。このようにして、テストコードはタイプセーフで読みやすくなります。何よりも、設計上の妥協、テストのための露出過度の方法やフィールドはありません。
スコット

回答:


239

通常、プライベートメソッドを直接単体テストすることはありません。これらはプライベートなので、実装の詳細と考えてください。誰もそれらのいずれかを呼び出して、特定の方法で動作することを期待することはありません。

代わりに、パブリックインターフェイスをテストする必要があります。プライベートメソッドを呼び出すメソッドが期待どおりに機能している場合は、プライベートメソッドが正常に機能していると拡張機能で想定します。


39
+1バジリオン。また、プライベートメソッドが呼び出されない場合は、単体テストを行わずに削除してください!
スティーブンA.ロウ

246
同意しません。プライベートメソッドは実装の詳細にすぎない場合もありますが、テストが必要なほど複雑であり、正しく機能することを確認します。パブリックインターフェイスは、この特定のアルゴリズムを直接対象とするテストを作成するには高すぎる抽象度を提供している可能性があります。共有データのため、それを個別のクラスに分解することは常に実行可能ではありません。この場合、プライベートメソッドをテストしてもかまいません。
quant_dev

10
もちろん削除します。使用していない関数を削除しない唯一の理由は、将来必要になる可能性があると考えている場合であり、それでも削除する必要がありますが、コミットログで関数をメモするか、少なくともコメントアウトしてください人々は、それが無視できる余分なものであることを知っています。
ジョッキング

38
@quant_dev:クラスをいくつかの他のクラスにリファクタリングする必要がある場合があります。共有データも別のクラスにプルできます。「コンテキスト」クラスと言います。次に、2つの新しいクラスは、共有データのコンテキストクラスを参照できます。これは理不尽に思えるかもしれませんが、プライベートメソッドが個々のテストを必要とするほど複雑な場合、オブジェクトグラフをもう少しきめ細かくする必要があることを示すコードの匂いです。
フィル

43
この答えは質問に答えません。問題はプライベートメソッドをテストするかどうかであり、テストする必要があるかどうかではありません。プライベートメソッドをユニットテストする必要があるかどうかは興味深い質問であり、議論に値します、ここでは説明しません。適切な応答は、プライベートメソッドをテストすることは良い考えではない可能性があることを示す質問にコメントを追加し、問題に深く入り込む別の質問へのリンクを提供することです。
トールガイ14年

118

一般的に、私はそれを避けます。プライベートメソッドが非常に複雑で、個別の単体テストが必要な場合、多くの場合、独自のクラスに値することを意味します。これにより、再利用可能な方法で作成することができます。次に、新しいクラスをテストし、古いクラスでそのパブリックインターフェイスを呼び出す必要があります。

一方で、実装の詳細を個別のクラスに分解すると、複雑なインターフェイスを持つクラス、古いクラスと新しいクラスの間でやり取りされる大量のデータ、またはOOPの観点からはよく見えるデザインになりますが、問題ドメインからの直観に一致します(たとえば、プライベートメソッドのテストを回避するために価格設定モデルを2つに分割することはあまり直感的ではなく、コードの保守/拡張時に問題が発生する可能性があります)。常に一緒に変更される「ツインクラス」は必要ありません。

カプセル化とテスタビリティの間の選択に直面したとき、私はむしろ2番目に行きたいです。適切にテストされていなかったため、正しく動作しない素敵なOOPデザインよりも、正しいコードを持つ(つまり、正しい出力を生成する)ことが重要です。Javaでは、メソッドに「デフォルト」アクセス権を与えて、単体テストを同じパッケージに入れることができます。単体テストは、単に開発中のパッケージの一部であり、テストとテスト対象のコードとの間に依存関係があることは問題ありません。つまり、実装を変更するときはテストを変更する必要があるかもしれませんが、それでも構いません-実装を変更するたびにコードを再テストする必要があり、それを行うためにテストを変更する必要がある場合は、それ。

一般に、クラスは複数のインターフェースを提供する場合があります。ユーザー向けのインターフェースと、メンテナー向けのインターフェースがあります。2つ目は、コードを適切にテストするためにさらに公開することができます。プライベートメソッドでの単体テストである必要はありません。たとえば、ロギングなどです。ロギングも「カプセル化を解除」しますが、非常に便利なため、引き続き行います。


9
+1興味深いポイント。最終的には、優れたOOPの「ルール」を順守するのではなく、保守性についてです。
フィル

9
+1カプセル化よりもテスト容易性に完全に同意します。
リチャード

31

プライベートメソッドのテストは、その複雑さに依存します。一部の1行のプライベートメソッドは、テストの余分な労力を実際に保証しません(これはパブリックメソッドとも言えます)。

私が推奨する手法は、プライベートメソッドパッケージをプライベートにすることです。これにより、同じパッケージ内の単体テストにアクセスできますが、それでも他のすべてのコードからカプセル化されます。これにより、(おそらく)複雑なロジックのすべての部分をカバーするためにパブリックメソッドテストに依存する代わりに、プライベートメソッドロジックを直接テストする利点が得られます。

これがGoogle Guavaライブラリの@VisibleForTestingアノテーションとペアになっている場合、このパッケージプライベートメソッドをテストのみに表示されるように明確にマークしているため、他のクラスから呼び出すことはできません。

この手法の反対者は、これによりカプセル化が破られ、プライベートメソッドが開かれて同じパッケージにコーディングされると主張しています。これはカプセル化を破り、プライベートコードを他のクラスに公開することに同意しますが、厳密なカプセル化よりも複雑なロジックのテストの方が重要であり、テストでのみ可視としてマークされているパッケージプライベートメソッドを使用しないことは開発者の責任であると主張しますコードベースの使用と変更。

テスト前のプライベートメソッド:

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

テスト可能なパッケージプライベートメソッド:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

注:同じパッケージにテストを配置することは、同じ物理フォルダーにテストを配置することと同等ではありません。メインコードとテストコードを個別の物理フォルダー構造に分離することは一般的に良い習慣ですが、クラスが同じパッケージ内で定義されている限り、この手法は機能します。


14

外部APIを使用できない場合、または使用したくない場合でも、リフレクションを使用してプライベートメソッドにアクセスするために純粋な標準JDK APIを使用できます。ここに例があります

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Javaチュートリアルhttp://docs.oracle.com/javase/tutorial/reflect/またはJava API http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summaryを確認してください詳細については、html

@kijが彼の答えで引用したように、リフレクションを使用した単純なソリューションがプライベートメソッドをテストするのに本当に良い場合があります。


9

ユニットテストケースとは、コードのユニットをテストすることです。インターフェイスをテストすることを意味するわけではありません。インターフェイスをテストする場合、コード単位をテストすることを意味するわけではありません。それは一種のブラックボックステストになります。また、インターフェイスレベルで問題を特定し、動作していない部分をデバッグしようとするよりも、最小ユニットレベルで問題を見つける方が適切です。したがって、ユニットテストケースは、その範囲に関係なくテストする必要があります。以下は、プライベートメソッドをテストする方法です。

Javaを使用している場合、Deencapsulation.invokeを提供するjmockitを使用して、テスト中のクラスのプライベートメソッドを呼び出すことができます。最終的にそれを呼び出すためにリフレクションを使用しますが、それを囲む素晴らしいラッパーを提供します。(https://code.google.com/p/jmockit/


これは質問にどう答えますか?
ブヨ

@gnat、質問は、Javaクラスのプライベートメソッドを単体テストする最良の方法は何ですか?そして、私の最初のポイントは質問に答えます。
アグラワランクール14

あなたの最初のポイントは、単にツールをアドバタイズし、それはあなたがそれが間接的にプライベートメソッドをテストする、例えばなどの代替、より良いと思う理由を説明していません示唆し、トップの回答で説明したように
ブヨ

jmockitは移動し、古い公式ページは「合成尿と人工おしっこ」に関する記事を指しています。Wichはいまだにモック関連のものですが、ここでは関係ありません:)新しい公式ページは次のとおり
Guillaume

8

まず第一に、他の著者が示唆したように:もし本当にプライベートメソッドをテストする必要があるなら、よく考えてください。そしてそうならば、 ...

.NETでは、これを「Internal」メソッドに変換し、ユニットテストプロジェクトのパッケージ「InternalVisible」を作成できます。

Javaでは、テスト対象のクラスにテスト自体を記述でき、テストメソッドもプライベートメソッドを呼び出すことができます。私は実際には大きなJavaの経験がないので、おそらくベストプラクティスではありません。

ありがとう。


7

私は通常、そのようなメソッドを保護します。クラスが次の場所にあるとしましょう:

src/main/java/you/Class.java

次のようにテストクラスを作成できます。

src/test/java/you/TestClass.java

これで、保護されたメソッドにアクセスでき、それらを単体テストできます(JUnitまたはTestNGは重要ではありません)が、これらのメソッドは不要な呼び出し元から保持します。

これは、Mavenスタイルのソースツリーを想定していることに注意してください。


3

Javaでプライベートメソッドを本当にテストする必要がある場合は、fest assertand / orを使用できますfest reflect。リフレクションを使用します。

ライブラリをmavenでインポートします(指定されたバージョンは、私が考える最新のものではありません)。または、クラスパスに直接インポートします。

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

例として、パラメータとして文字列を取り、値を「これはクールなテスト!」に更新する「myPrivateMethod」という名前のプライベートメソッドを持つ「MyClass」という名前のクラスがある場合、次のjunitテストを実行できます。

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

また、このライブラリを使用すると、Beanプロパティ(プライベートでセッターが記述されていなくても)をモックで置き換えることができます。これをMockitoまたは他のモックフレームワークで使用するのは本当にクールです。現時点で知っておく必要があるのは(次のバージョンで改善されるかどうかわからない)、操作するターゲットフィールド/メソッドの名前とその署名だけです。


2
リフレクションを使用するとプライベートメソッドをテストできますが、それらの使用を検出するのには適していません。プライベートメソッドの名前を変更すると、テストが中断されます。
リチャード

1
テストは「はい」になりますが、これは私の観点からは軽微な問題です。もちろん、パッケージの可視性に関する以下の説明については完全に同意します(テストクラスは別々のフォルダにありますが、同じパッケージです)。本当に選択肢があります。たとえば、「良い」メソッド(同じパッケージにないテストクラスなど)を適用しない企業で、本当に短いミッションがある場合、作業中の既存のコードをリファクタリングする時間がない場合/テスト、これはまだ良い選択肢です。
kij

2

私が通常C#で行うのは、メソッドをプライベートではなく保護することです。プライベートアクセス修飾子はやや劣りますが、テスト対象のクラスから継承しないすべてのクラスからメソッドを隠します。

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

classUnderTestから直接継承しないクラスには、methodToTestが存在することすらわかりません。テストコードでは、このメソッドへのアクセスを拡張および提供する特別なテストクラスを作成できます...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

このクラスは、私のテストプロジェクトにのみ存在します。その唯一の目的は、この単一のメソッドへのアクセスを提供することです。他のほとんどのクラスにはない場所にアクセスできます。


4
protectedパブリックAPIの一部であり、同じ制限が発生します(変更することはできず、文書化する必要があります...)。C#では、とinternal一緒に使用できますInternalsVisibleTo
CodesInChaos

1

テストするクラスの内部クラスにユニットテストを配置すると、プライベートメソッドを簡単にテストできます。TestNGを使用して、ユニットテストは、次のように@Testアノテーションが付けられたパブリックな静的内部クラスである必要があります。

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

これは内部クラスであるため、プライベートメソッドを呼び出すことができます。

私のテストはmavenから実行され、これらのテストケースが自動的に検出されます。1つのクラスをテストしたいだけなら

$ mvn test -Dtest=*UserEditorTest

ソース:http : //www.ninthavenue.com.au/how-to-unit-test-private-methods


4
展開されるパッケージの実際のコードにテストコード(および追加の内部クラス)を含めることを提案していますか?

1
テストクラスは、テストするクラスの内部クラスです。それらの内部クラスをデプロイしたくない場合は、パッケージから* Test.classファイルを削除するだけです。
ロジャーキーズ

1
私はこのオプションが好きではありませんが、多くの人にとってはおそらく有効な解決策であり、降格の価値はありません。
ビルK

@RogerKeays:技術的に展開を行うとき、そのような「テスト内部クラス」をどのように削除しますか?手で切ったとは言わないでください。
gyorgyabraham

削除しません。はい。実行時に決して実行されないコードをデプロイします。本当に悪いことです。
ロジャーキーズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.