JUnit を使用して、内部プライベート メソッド、フィールド、またはネストされたクラスを持つクラスをテストするにはどうすればよいですか?
テストを実行できるようにするためだけにメソッドのアクセス修飾子を変更するのは良くないようです。
JUnit を使用して、内部プライベート メソッド、フィールド、またはネストされたクラスを持つクラスをテストするにはどうすればよいですか?
テストを実行できるようにするためだけにメソッドのアクセス修飾子を変更するのは良くないようです。
アップデート:
約 10 年後、おそらくプライベート メソッドまたはアクセスできないメンバーをテストする最良の方法は、 Manifoldフレームワーク
@Jailbreak
から経由することです。@Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;
このようにして、コードは型安全で読みやすいままになります。設計上の妥協はなく、テストのために方法やフィールドを過度に露出することもありません。
レガシーJavaアプリケーションがあり、メソッドの可視性を変更できない場合、プライベート メソッドをテストする最善の方法は、リフレクションを使用することです。
内部的にはヘルパーを使用してprivate
と変数を取得/設定し、とメソッドprivate static
を呼び出します。次のパターンでは、プライベート メソッドとフィールドに関連するほとんどすべてのことを実行できます。もちろん、リフレクションによって変数を変更することはできません。private
private static
private static final
Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
フィールドの場合:
Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
注:
1.メソッドTargetClass.getDeclaredMethod(methodName, argClasses)
を調べることができますprivate
。についても同様ですgetDeclaredField
。
2.setAccessible(true)
プライベートで遊ぶには必要です。
プライベート メソッドをテストする最良の方法は、別のパブリック メソッドを使用することです。これができない場合は、次のいずれかの条件が当てはまります。
クラスにプライベートメソッドがあり、プライベートメソッドを直接テストする必要があると感じる場合、それはコードの臭いです。クラスが複雑すぎます。
このような問題に対処するための私の通常のアプローチは、興味深いビットを含む新しいクラスを引き出すことです。多くの場合、このメソッドとそれが相互作用するフィールド、およびおそらく別のメソッドまたは2つを新しいクラスに抽出できます。
新しいクラスはこれらのメソッドを「public」として公開するため、単体テストでアクセスできます。新しいクラスと古いクラスはどちらも元のクラスよりも単純になりました。これは私にとって素晴らしいことです(物事を単純に保つ必要があります。そうしないと迷子になります!)。
私は人々が彼らの脳を使わずにクラスを作成することを示唆していないことに注意してください!ここでのポイントは、ユニットテストの力を使用して、優れた新しいクラスを見つけるのに役立てることです。
この記事: Testing Private Methods with JUnit and SuiteRunner (Bill Venners) から、基本的に 4 つのオプションがあります。
- プライベート メソッドをテストしないでください。
- メソッドにパッケージへのアクセス権を付与します。
- ネストされたテスト クラスを使用します。
- 反射を使用します。
通常、ユニット テストは、クラスまたはユニットのパブリック インターフェイスを実行することを目的としています。したがって、プライベート メソッドは、明示的にテストする必要のない実装の詳細です。
プライベートメソッドをテストしたい場所の2つの例:
SecurityManager
ただし、これらはコードに固有であり、複雑であり、常に機能する必要があります(明らかな例外は、これを防ぐように構成されていない場合、ほとんどの場合、プライベートメソッドを表示するために使用できるリフレクションです)。「契約」のみをテストするという考えを理解しています。しかし、実際にコードをテストしないことを主張できる人はいないと思います。マイレージは異なる場合があります。
したがって、私のトレードオフには、セキュリティとSDKを損なうのではなく、JUnitをリフレクションで複雑にすることが含まれます。
プライベート メソッドはパブリック メソッドによって呼び出されるため、パブリック メソッドへの入力は、それらのパブリック メソッドによって呼び出されるプライベート メソッドもテストする必要があります。public メソッドが失敗した場合、それは private メソッドの失敗である可能性があります。
Spring Frameworkでは、このメソッドを使用してプライベート メソッドをテストできます。
ReflectionTestUtils.invokeMethod()
例えば:
ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
私が使用した別のアプローチは、private メソッドをパッケージ化する private または protected に変更し、Google Guava ライブラリの@VisibleForTestingアノテーションでそれを補完することです。
これは、このメソッドを使用するすべての人に注意を促し、パッケージ内であっても直接アクセスしないようにします。また、テストクラスは物理的に同じパッケージにある必要はありませんが、テストフォルダーの下の同じパッケージにある必要があります。
たとえば、テストするメソッドが にある場合、src/main/java/mypackage/MyClass.java
テスト コールは に配置する必要がありますsrc/test/java/mypackage/MyClassTest.java
。そうすれば、テスト クラスのテスト メソッドにアクセスできます。
大規模で風変わりなクラスを使用してレガシー コードをテストするには、現在作成中の 1 つのプライベート (またはパブリック) メソッドをテストできると非常に役立ちます。
私はjunitx.util.PrivateAccessor -package for Java を使用しています。プライベート メソッドとプライベート フィールドにアクセスするための便利なワンライナーがたくさんあります。
import junitx.util.PrivateAccessor;
PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
Java用のリフレクションを使用してCemCatikkasのソリューションを試したので、彼はここで説明したよりもエレガントなソリューションだったと言わざるを得ません。ただし、リフレクションを使用する代わりの方法を探していて、テストしているソースにアクセスできる場合は、これはオプションです。
特にテスト駆動開発では、コードを作成する前に小さなテストを設計したい場合に、クラスのプライベートメソッドをテストすることにはメリットがあります。
プライベートメンバーとメソッドにアクセスできるテストを作成すると、パブリックメソッドのみにアクセスするだけでは特にターゲットを絞ることが難しいコードの領域をテストできます。パブリックメソッドに複数のステップが含まれる場合、それはいくつかのプライベートメソッドで構成され、個別にテストできます。
利点:
短所:
ただし、継続的テストでこのメソッドが必要な場合は、プライベートメソッドを抽出する必要があることを示している可能性があります。これは、従来のパブリックメソッドでテストできます。
これがどのように機能するかの複雑な例を次に示します。
// Import statements and package declarations
public class ClassToTest
{
private int decrement(int toDecrement) {
toDecrement--;
return toDecrement;
}
// Constructor and the rest of the class
public static class StaticInnerTest extends TestCase
{
public StaticInnerTest(){
super();
}
public void testDecrement(){
int number = 10;
ClassToTest toTest= new ClassToTest();
int decremented = toTest.decrement(number);
assertEquals(9, decremented);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(StaticInnerTest.class);
}
}
}
内部クラスはにコンパイルされClassToTest$StaticInnerTest
ます。
他の人が言ったように...プライベートメソッドを直接テストしないでください。ここにいくつかの考えがあります:
単体テストでコード カバレッジを実行します。メソッドが完全にテストされていないことがわかった場合は、テストに追加してカバレッジを上げてください。100% のコード カバレッジを目指しますが、おそらく達成できないことを認識してください。
プライベート メソッドは、パブリック メソッドによって消費されます。そうでなければ、それらはデッド コードです。そのため、パブリック メソッドをテストして、パブリック メソッドの期待される結果をアサートし、それによってプライベート メソッドを消費します。
プライベート メソッドのテストは、パブリック メソッドで単体テストを実行する前に、デバッグによってテストする必要があります。
また、テスト駆動開発を使用してデバッグし、すべてのアサーションが満たされるまで単体テストをデバッグすることもできます。
個人的には、TDD を使用してクラスを作成する方がよいと考えています。パブリック メソッド スタブを作成し、事前に定義されたすべてのアサーションを使用して単体テストを生成します。これにより、メソッドの期待される結果は、コーディングする前に決定されます。このようにして、単体テストのアサーションを結果に適合させるという間違った道をたどることはありません。すべての単体テストに合格すると、クラスは堅牢になり、要件を満たします。
Spring を使用している場合、ReflectionTestUtilsは、最小限の労力でここで役立ついくつかの便利なツールを提供します。たとえば、望ましくないパブリック セッターを追加せずにプライベート メンバーにモックを設定するには、次のようにします。
ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
変更したくない、または変更できない既存のコードをテストしようとしている場合は、リフレクションが適しています。
クラスの設計がまだ柔軟で、個別にテストしたい複雑なプライベート メソッドがある場合は、それを別のクラスに取り出して、そのクラスを個別にテストすることをお勧めします。これは、元のクラスのパブリック インターフェイスを変更する必要はありません。ヘルパー クラスのインスタンスを内部的に作成し、ヘルパー メソッドを呼び出すことができます。
ヘルパー メソッドから発生する難しいエラー条件をテストする場合は、さらに一歩進んでください。ヘルパー クラスからインターフェイスを抽出し、パブリック ゲッターとセッターを元のクラスに追加してヘルパー クラスを挿入し (そのインターフェイスを介して使用)、元のクラスにヘルパー クラスのモック バージョンを挿入して、元のクラスがどのように機能するかをテストします。ヘルパーからの例外に応答します。このアプローチは、ヘルパー クラスをテストせずに元のクラスをテストする場合にも役立ちます。
プライベート メソッドをテストすると、内部実装を変更するたびにクライアント コード (この場合はテスト) が壊れるため、クラスのカプセル化が壊れます。
したがって、プライベート メソッドをテストしないでください。
コードを変更できないレガシー アプリケーションのプライベート メソッドをテストする場合、Java のオプションの 1 つはjMockit です。これにより、オブジェクトがクラスに対してプライベートであってもオブジェクトのモックを作成できます。
JUnit.org の FAQ ページからの回答:
しかし、もしあなたがしなければならないなら...
JDK 1.3 以降を使用している場合は、リフレクションを使用して、PrivilegedAccessorを使用してアクセス制御メカニズムを無効にすることができます。詳しい使い方はこちらの記事をご覧ください。
JDK 1.6 以降を使用していて、テストに @Test のアノテーションを付ける場合、Dp4jを使用してテスト メソッドにリフレクションを挿入できます。使用方法の詳細については、このテスト スクリプトを参照してください。
JUnit を使用している場合は、junit-addons をご覧ください。Java セキュリティ モデルを無視し、プライベート メソッドと属性にアクセスする機能があります。
私はプライベートメソッドをテストしない傾向があります。狂気があります。個人的には、公開されたインターフェイス (および保護された内部メソッドを含む) のみをテストする必要があると思います。
コードを少しリファクタリングすることをお勧めします。コードをテストするためだけに、リフレクションやその他の種類のものを使用することを考え始めなければならない場合、コードに問題が発生しています。
あなたはさまざまな種類の問題について言及しました。プライベートフィールドから始めましょう。プライベート フィールドの場合は、新しいコンストラクターを追加し、それにフィールドを注入します。これの代わりに:
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
私はこれを使用していたでしょう:
public class ClassToTest {
private final String first;
private final List<String> second;
public ClassToTest() {
this("first", new ArrayList<>());
}
public ClassToTest(final String first, final List<String> second) {
this.first = first;
this.second = second;
}
...
}
これは、一部のレガシー コードでも問題になりません。古いコードは空のコンストラクターを使用します。私に尋ねると、リファクタリングされたコードはきれいに見え、リフレクションなしで必要な値をテストに挿入できるようになります。
次に、プライベート メソッドについて説明します。私の個人的な経験では、テストのためにプライベート メソッドをスタブする必要がある場合、そのメソッドはそのクラスでは何の関係もありません。その場合の一般的なパターンは、インターフェイス内でラップCallable
し、コンストラクターでもそのインターフェイスを渡すことです (複数のコンストラクターのトリックを使用)。
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
私が書いたもののほとんどは、依存性注入パターンのように見えます。私の個人的な経験では、テスト中には非常に便利です。この種のコードはよりクリーンで、保守が容易になると思います。ネストされたクラスについても同じことが言えます。ネストされたクラスに重いロジックが含まれている場合は、それをパッケージ プライベート クラスとして移動し、それを必要とするクラスに注入した方がよいでしょう。
レガシ コードのリファクタリングと保守の際に使用した他のデザイン パターンもいくつかありますが、それはすべて、テストするコードのケースによって異なります。リフレクションを使用することはほとんど問題ではありませんが、十分にテストされたエンタープライズ アプリケーションがあり、すべての展開の前にテストが実行されると、すべてが非常に遅くなります (面倒で、私はそのようなことは好きではありません)。
セッター注入もありますが、使用はお勧めしません。コンストラクターを使い続けて、本当に必要なときにすべてを初期化し、必要な依存関係を注入する可能性を残したほうがよいでしょう。
上記の多くの人が示唆しているように、パブリック インターフェイスを介してテストすることをお勧めします。
これを行う場合は、コード カバレッジ ツール (Emma など) を使用して、プライベート メソッドが実際にテストから実行されているかどうかを確認することをお勧めします。
今日、プライベート メソッドとフィールドのテストに役立つ Java ライブラリをプッシュしました。Android を念頭に置いて設計されていますが、実際にはどの Java プロジェクトにも使用できます。
プライベート メソッド、フィールド、またはコンストラクターを含むコードを取得した場合は、BoundBoxを使用できます。それはまさにあなたが探していることをします。以下は、Android アクティビティの 2 つのプライベート フィールドにアクセスしてテストするテストの例です。
@UiThreadTest
public void testCompute() {
// Given
boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
// When
boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
// Then
assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}
BoundBoxを使用すると、プライベート/保護されたフィールド、メソッド、およびコンストラクターを簡単にテストできます。継承によって隠されているものにもアクセスできます。実際、BoundBox はカプセル化を破ります。リフレクションを通じてすべてにアクセスできますが、コンパイル時にすべてがチェックされます。
一部のレガシー コードをテストするのに理想的です。慎重に使用してください。;)
最初に、次の質問を投げかけます。プライベート メンバーに分離テストが必要なのはなぜですか? それらはそれほど複雑で、一般公開されていない場所でのテストが必要なほど複雑な動作を提供しますか? これは単体テストであり、「コード行」テストではありません。小さなことを気にしないでください。
それらが非常に大きく、これらのプライベート メンバーがそれぞれ複雑な「ユニット」になるほど大きい場合は、このクラスからそのようなプライベート メンバーをリファクタリングすることを検討してください。
リファクタリングが不適切または実行不可能な場合、戦略パターンを使用して、単体テスト時にこれらのプライベート メンバー関数/メンバー クラスへのアクセスを置き換えることができますか? 単体テストでは、この戦略は追加の検証を提供しますが、リリース ビルドでは単純なパススルーになります。
私は最近この問題を抱えており、Picklockと呼ばれる小さなツールを作成しました。これは、Java リフレクション API を明示的に使用する際の問題を回避します。2 つの例を次に示します。
メソッドの呼び出し (例: private void method(String s)
Java リフレクションによる)
Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");
メソッドの呼び出し、例えばprivate void method(String s)
- Picklock による
interface Accessible {
void method(String s);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");
フィールドの設定、例えばprivate BigInteger amount;
- Java リフレクションによる
Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));
フィールドの設定、例えばprivate BigInteger amount;
- Picklock による
interface Accessible {
void setAmount(BigInteger amount);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
Javaの場合は、リフレクションを使用します。テストのためだけに、宣言されたメソッドのパッケージへのアクセスを変更するという考えが気に入らないからです。ただし、通常はパブリック メソッドをテストするだけで、プライベート メソッドが正しく機能していることも確認できます。
リフレクションを使用して所有者クラスの外部からプライベート メソッドを取得することはできません。プライベート修飾子はリフレクションにも影響します。
本当じゃない。Cem Catikkas's answerで述べたように、あなたは間違いなくそうすることができます。
リフレクションの Java アクセス制限をオフにして、private が無意味になるようにすることができます。
setAccessible(true)
呼び出しはそれを行います。
唯一の制限は、ClassLoader がそれを禁止する可能性があることです。
Javaでこれを行う方法については、単体テスト用の Java アクセス保護の破壊(Ross Burton) を参照してください。
これが良い手法かどうかはわかりませんが、プライベート メソッドを単体テストするために次のパターンを開発しました。
テストしたいメソッドの可視性を変更せず、追加のメソッドを追加します。代わりに、テストするすべてのプライベート メソッドにパブリック メソッドを追加しています。この追加のメソッドを呼び出しTest-Port
、接頭辞 で示しますt_
。このTest-Port
メソッドは、対応するプライベート メソッドにアクセスするだけです。
さらに、ブール値フラグをメソッドに追加して、外部からTest-Port
メソッドを介してプライベート メソッドへのアクセスを許可するかどうかを決定しTest-Port
ます。このフラグは、アプリケーションの他のグローバル設定などを配置する静的クラスでグローバルに設定されます。したがって、対応する単体テストなど、1 つの場所でプライベート メソッドへのアクセスのオンとオフを切り替えることができます。
ExpectedException を使用する場合の @Cem Catikka のコメントへの簡単な追加:
予想される例外は InvocationTargetException にラップされることに注意してください。そのため、例外に到達するには、受け取った InvocationTargetException の原因をスローする必要があります。(BizService でプライベート メソッド validateRequest() をテストする) のようなもの:
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired(required = true)
private BizService svc;
@Test
public void testValidateRequest() throws Exception {
thrown.expect(BizException.class);
thrown.expectMessage(expectMessage);
BizRequest request = /* Mock it, read from source - file, etc. */;
validateRequest(request);
}
private void validateRequest(BizRequest request) throws Exception {
Method method = svc.getClass().getDeclaredMethod("validateRequest", BizRequest.class);
method.setAccessible(true);
try {
method.invoke(svc, request);
}
catch (InvocationTargetException e) {
throw ((BizException)e.getCause());
}
}
C# では を使用できましたがSystem.Reflection
、Java ではわかりません。とにかくこれに答えたいという衝動を感じますが、「プライベートメソッドを単体テストする必要があると感じた」場合は、他に何か問題があると思います...
私は自分のアーキテクチャをもう一度新鮮な目で見ることを真剣に検討します...
テスト クラスがテスト対象のクラスと同じパッケージに含まれている場合はどうなるでしょうか。
ただし、もちろん、ソースコードとテストクラスのsrc
&は別のディレクトリにあります。そして、あなたのクラスパスにいさせてください。classes
test/src
test/classes
classes
test/classes
PowerMock.Whitebox
私が見た中で最良のオプションですが、そのソースコードを読むと、リフレクションでプライベートフィールドを読み取るので、答えがあると思います:
しばらくして再考したとき、私はまだこれが真実であると信じていますが、より良いアプローチを見つけました.
まず、Powermock.Whitebox
まだまだ使えます。
また、MockitoWhitebox
は v2 の後に非表示になり (私が見つけることができる最新バージョンWhitebox
は ですtestImplementation 'org.mockito:mockito-core:1.10.19'
)、常にorg.mockito.internal
パッケージの一部であり、将来的に重大な変更が発生する可能性があります (この投稿を参照)。なので、今は使わないようにしています。
Gradle/Maven プロジェクトでは、プライベート メソッドまたはフィールドを定義する場合、リフレクション以外にそれらにアクセスする方法がないため、最初の部分はそのままです。ただし、可視性を「パッケージ プライベート」に変更すると、パッケージ内の同じ構造に従うテストtest
がそれらにアクセスできるようになります。main
これは、test
パッケージ内で同じ階層を作成することが推奨されるもう 1 つの重要な理由でもあります。したがって、本番コードとテストを制御できる場合は、そのprivate
アクセス修飾子を削除することが最適なオプションになる可能性があります。これは、比較的大きな影響を与えないためです。そして、これにより、テストとプライベート メソッドのスパイが可能になります。
@Autowired
private SomeService service; // with a package private method "doSomething()"
@Test
void shouldReturnTrueDoSomething() {
assertThat(doSomething(input), is(true)); // package private method testing
}
@Test
void shouldReturnTrueWhenServiceThrowsException() {
SomeService spy = Mockito.spy(service); // spying real object
doThrow(new AppException()).when(spy).doSomething(input); // spy package private method
...
}
内部フィールドに関して言えば、Spring にはReflectionUtils.setField()
.
最後に、問題自体を回避できる場合もあります。満たすべきカバレッジ要件がある場合は、これらのプライベート メソッドを内部の静的クラスに移動し、Jacoco でこのクラスを無視することができます。Jacoco gradle タスクで内部クラスを無視する方法を見つけました。別の質問
JML has a spec_public comment annotation syntax that allows you to specify a method as public during tests:
private /*@ spec_public @*/ int methodName(){
...
}
This syntax is discussed at http://www.eecs.ucf.edu/~leavens/JML/jmlrefman/jmlrefman_2.html#SEC12. There also exists a program that translates JML specifications into JUnit tests. I'm not sure how well that works or what its capabilities are, but it doesn't appear to be necessary since JML is a viable testing framework on its own.
私はパブリック インターフェイスのみをテストしますが、特定のプライベート メソッドを保護することで知られているため、それらを完全にモック アウトするか、単体テストの目的に固有の追加の手順を追加できます。一般的なケースは、単体テストから設定できるフラグをフックして、特定のメソッドが故意に例外を発生させ、フォールト パスをテストできるようにすることです。例外をトリガーするコードは、保護されたメソッドのオーバーライドされた実装のテスト パスにのみ存在します。
ただし、私はこの必要性を最小限に抑え、混乱を避けるために常に正確な理由を文書化しています.