内部プライベートメソッド、フィールド、またはネストされたクラスを持つクラスをユニットテスト(xUnitを使用)するにはどうすればよいですか?または、内部リンケージ(static
C / C ++で)によってプライベートにされた関数、またはプライベート(匿名)ネームスペースにある関数?
テストを実行できるようにするためだけに、メソッドまたは関数のアクセス修飾子を変更するのは悪いようです。
内部プライベートメソッド、フィールド、またはネストされたクラスを持つクラスをユニットテスト(xUnitを使用)するにはどうすればよいですか?または、内部リンケージ(static
C / C ++で)によってプライベートにされた関数、またはプライベート(匿名)ネームスペースにある関数?
テストを実行できるようにするためだけに、メソッドまたは関数のアクセス修飾子を変更するのは悪いようです。
回答:
更新:
約10年後、おそらくプライベートメソッドまたはアクセスできないメンバーをテストするための最良の方法は
@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つを新しいクラスに抽出できます。
新しいクラスはこれらのメソッドを「パブリック」として公開しているため、ユニットテストにアクセスできます。新しいクラスと古いクラスの両方が元のクラスよりも単純になりました。これは私にとっては素晴らしいことです(物事を単純にしておく必要がある、または迷ってしまいます!)。
脳を使わずにクラスを作成することはお勧めしません。ここでのポイントは、ユニットテストの力を使用して、優れた新しいクラスを見つけることです。
私はこれまでJavaでこれを行うためにリフレクションを使用しましたが、私の意見ではそれは大きな間違いでした。
厳密に言えば、プライベートメソッドを直接テストする単体テストを作成するべきではありません。テストする必要があるのは、クラスが他のオブジェクトと持っているパブリックコントラクトです。オブジェクトの内部を直接テストしないでください。別の開発者がクラスの内部契約に影響を及ぼさない小さな内部変更をクラスに加えたい場合、その開発者はリフレクションベースのテストを変更して機能することを確認する必要があります。プロジェクト全体でこれを繰り返し行うと、ユニットテストはコードの正常性の有用な測定ではなくなり、開発の妨げになり始め、開発チームの迷惑になります。
代わりに、Coberturaなどのコードカバレッジツールを使用して、作成した単体テストがプライベートメソッドでコードを適切にカバーできるようにすることをお勧めします。このようにして、プライベートメソッドの動作を間接的にテストし、より高いレベルの俊敏性を維持します。
この記事から:JUnitとSuiteRunner(Bill Venners)を使用したプライベートメソッドのテストには、基本的に4つのオプションがあります。
- プライベートメソッドをテストしないでください。
- メソッドにパッケージアクセスを付与します。
- ネストされたテストクラスを使用します。
- リフレクションを使用します。
一般に、ユニットテストは、クラスまたはユニットのパブリックインターフェイスを実行することを目的としています。したがって、プライベートメソッドは、明示的にテストすることを期待しない実装の詳細です。
プライベートメソッドをテストしたい場所の2つの例:
SecurityManager
、これを防止するように構成されていない場合、ほとんどの場合、プライベートメソッドでも表示するために使用できます)。「契約」のみをテストするという考えを理解しています。しかし、実際にコードをテストしないことを主張できる人はいないと思います。
したがって、私のトレードオフには、セキュリティとSDKを損なうのではなく、JUnitをリフレクションで複雑にすることが含まれます。
private
はいけませんが、唯一の方法ではありません。アクセス修飾子はありませんpackage private
。ユニットテストが同じパッケージ内にある限り、ユニットテストを実行できます。
プライベートメソッドはパブリックメソッドによって呼び出されるため、パブリックメソッドへの入力では、これらのパブリックメソッドによって呼び出されるプライベートメソッドもテストする必要があります。パブリックメソッドが失敗した場合、それはプライベートメソッドの失敗である可能性があります。
私が使用した別のアプローチは、プライベートメソッドをプライベートまたは保護されたパッケージに変更し、それをGoogle Guavaライブラリの@VisibleForTestingアノテーションで補完することです。
これは、このメソッドを使用しているすべての人に、パッケージ内であっても注意して直接アクセスしないように注意することを伝えます。また、テストクラスは物理的に同じパッケージ内にある必要はありませんが、テストフォルダの下の同じパッケージ内にある必要があります。
たとえば、テストするメソッドが含まれているsrc/main/java/mypackage/MyClass.java
場合、テスト呼び出しはに配置する必要がありますsrc/test/java/mypackage/MyClassTest.java
。このようにして、テストクラスのテストメソッドにアクセスできます。
大きくて風変わりなクラスでレガシーコードをテストするには、私が書いている1つのプライベート(またはパブリック)メソッドをテストできると非常に便利です。 今。
私はjunitx.util.PrivateAccessor -package for Java を使用します。プライベートメソッドとプライベートフィールドにアクセスするための便利な1行。
import junitx.util.PrivateAccessor;
PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
Class
、これらのメソッドで最初のパラメーターとしてa を使用している場合は、static
メンバーにのみアクセスしていることを確認してください。
Spring Frameworkでは、このメソッドを使用してプライベートメソッドをテストできます。
ReflectionTestUtils.invokeMethod()
例えば:
ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
リフレクションを使用してCem Catikkasのソリューションを試したことがあるJavaのをたので、彼がここで説明したよりもエレガントなソリューションであると言えるでしょう。ただし、リフレクションを使用する代わりの方法を探していて、テストしているソースにアクセスできる場合でも、これはオプションです。
特にテスト駆動開発では、コードを記述する前に小さなテストを設計したい場合に、クラスのプライベートメソッドをテストすることにメリットがある可能性があります。
プライベートメンバーとメソッドにアクセスできるテストを作成すると、パブリックメソッドのみにアクセスすることで具体的にターゲットにすることが難しいコード領域をテストできます。パブリックメソッドに複数のステップが含まれる場合、それは複数のプライベートメソッドで構成され、個別にテストできます。
利点:
短所:
ただし、継続的なテストでこのメソッドが必要な場合は、プライベートメソッドを抽出する必要があるという合図であり、従来のパブリックな方法でテストできます。
これがどのように機能するかの複雑な例です:
// 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を使用してクラスを作成する方が良いと信じています。publicメソッドスタブを作成し、事前に定義されたすべてのアサーションを使用して単体テストを生成します。そのため、メソッドの予想される結果は、コーディングする前に決定されます。このようにして、ユニットテストのアサーションを結果に合わせるという間違った道をたどることはありません。その後、クラスは堅牢になり、すべての単体テストに合格したときに要件を満たします。
Springを使用する場合、ReflectionTestUtilsは、最小限の労力でここで役立ついくつかの便利なツールを提供します。たとえば、望ましくないパブリックセッターを追加せずにプライベートメンバーにモックを設定するには:
ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
気が進まない、または変更できない既存のコードをテストする場合は、リフレクションが適しています。
クラスの設計がまだ柔軟で、個別にテストしたい複雑なプライベートメソッドがある場合は、それを個別のクラスに引き出して、そのクラスを個別にテストすることをお勧めします。これは、元のクラスのパブリックインターフェイスを変更する必要はありません。内部でヘルパークラスのインスタンスを作成し、ヘルパーメソッドを呼び出すことができます。
ヘルパーメソッドからの難しいエラー条件をテストする場合は、さらに一歩進んでください。ヘルパークラスからインターフェイスを抽出し、パブリックゲッターとセッターを元のクラスに追加して(そのインターフェイスを通じて使用される)ヘルパークラスをインジェクトし、モックバージョンのヘルパークラスを元のクラスにインジェクトして、元のクラスがどのようにテストするかヘルパーからの例外に応答します。この方法は、ヘルパークラスをテストせずに元のクラスをテストする場合にも役立ちます。
プライベートメソッドをテストすると、内部実装を変更するたびにクライアントコード(この場合はテスト)が破損するため、クラスのカプセル化が破損します。
したがって、プライベートメソッドをテストしないでください。
JUnit.org FAQページからの回答:
しかし、あなたがしなければならない場合...
JDK 1.3以降を使用している場合は、リフレクションを使用して、PrivilegedAccessorを利用してアクセス制御メカニズムを破壊できます。。使い方の詳細はこちらの記事をご覧ください。
JDK 1.6以降を使用していて、@ Testでテストに注釈を付ける場合は、Dp4jを使用して、テストメソッドにリフレクションを挿入できます。使用方法の詳細については、このテストスクリプトを参照してください。
コードを変更できないレガシーアプリケーションのプライベートメソッドをテストする場合、Javaのオプションの1つはjMockitです。これにより、クラスにプライベートであってもオブジェクトのモックを作成できます。
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
私はプライベートメソッドをテストしない傾向があります。狂気があります。個人的には、公開されているインターフェース(および保護されたメソッドと内部メソッドを含む)のみをテストする必要があると思います。
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;
}
ほとんどの場合、私が書いたのは依存関係注入パターンのようです。私の個人的な経験では、テスト中にそれは本当に役立ちます。この種のコードはよりクリーンで、保守が容易になると思います。ネストされたクラスについても同様です。ネストされたクラスに重いロジックが含まれている場合は、それをパッケージのプライベートクラスとして移動し、それを必要とするクラスに注入した方がよいでしょう。
他にも、レガシーコードのリファクタリングとメンテナンス中に使用したデザインパターンがいくつかありますが、それはすべて、テストするコードのケースによって異なります。リフレクションの使用はほとんど問題ではありませんが、十分にテストされたエンタープライズアプリケーションがあり、すべての展開の前にテストが実行されると、すべてが非常に遅くなります(それは単に煩わしいだけで、私はそのようなものは好きではありません)。
セッター注入もありますが、使用はお勧めしません。私はコンストラクタを使い、本当に必要なときにすべてを初期化し、必要な依存関係を注入する可能性を残した方がいいです。
ClassToTest(Callable)
注射に同意しない。それはClassToTest
より複雑になります。複雑にしないでおく。また、それは、必要とし、他の誰かを伝えるためにClassToTest
何かClassToTest
のボスである必要があります。ロジックを注入する場所がありますが、これはそうではありません。クラスを維持するのが難しくなりました。簡単ではありません。
X
によってX
複雑さが増し、問題が増える可能性があるため、追加のテストが必要になる場合があります。問題を引き起こす可能性のある方法で実装した場合は、さらにテストを行う必要があります。 。(これは無限ループではありません。各反復はその前の反復よりも小さい可能性がありますが、いらいらします)
ClassToTest
を維持するのが難しくなる理由を説明していただけますか?実際には、アプリケーションのメンテナンスが容易になります。必要なすべての異なる値first
と「2番目」の変数に対して新しいクラスを作成することをどのように提案しますか?
class A{ A(){} f(){} }
はより単純ですclass A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
。それが正しくなく、クラスのロジックをコンストラクターの引数として提供する方が維持しやすいことが当てはまる場合、すべてのロジックをコンストラクターの引数として渡します。class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
Aが保守しやすくなると主張できる方法はわかりません。このような注入が理にかなっている場合がありますが、私はあなたの例を「維持しやすい」とは思いません
プライベートメソッドは、同じクラス内でのみアクセスされます。したがって、任意のテストクラスからターゲットクラスの「プライベート」メソッドをテストする方法はありません。方法としては、ユニットテストを手動で実行したり、メソッドを「プライベート」から「保護」に変更したりできます。
そして、保護されたメソッドは、クラスが定義されている同じパッケージ内でのみアクセスできます。したがって、ターゲットクラスの保護されたメソッドをテストするには、ターゲットクラスと同じパッケージでテストクラスを定義する必要があります。
上記のすべてが要件に合わない場合は、リフレクションを使用してプライベートメソッドにアクセスします。
上記の多くが示唆しているように、あなたの公開インターフェースを介してそれらをテストすることは良い方法です。
これを行う場合は、コードカバレッジツール(Emmaなど)を使用して、プライベートメソッドが実際にテストから実行されているかどうかを確認することをお勧めします。
以下は、プライベートフィールドをテストするための汎用関数です。
protected <F> F getPrivateField(String fieldName, Object obj)
throws NoSuchFieldException, IllegalAccessException {
Field field =
obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (F)field.get(obj);
}
以下の例を参照してください。
次のインポートステートメントを追加する必要があります。
import org.powermock.reflect.Whitebox;
これで、プライベートメソッド、呼び出されるメソッド名、および以下の追加パラメーターを持つオブジェクトを直接渡すことができます。
Whitebox.invokeMethod(obj, "privateMethod", "param1");
今日、私はプライベートメソッドとフィールドのテストを支援するために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はカプセル化を解除します。それはあなたに反射を通してそれらすべてへのアクセスを提供します、しかしすべてがコンパイル時にチェックされます。
いくつかのレガシーコードのテストに最適です。慎重に使用してください。;)
まず、私はこの質問を投げ捨てます:なぜプライベートメンバーは分離テストが必要なのですか?それらは複雑で、公共の表面とは別にテストを必要とするような複雑な動作を提供していますか?これはユニットテストであり、「コード行」テストではありません。細かいことは気にしないでください。
それらが非常に大きい場合、これらのプライベートメンバーはそれぞれ、複雑さが非常に大きい「ユニット」です。このようなプライベートメンバーをこのクラスからリファクタリングすることを検討してください。
リファクタリングが不適切または実行不可能である場合、ユニットテスト中にこれらのプライベートメンバー関数/メンバークラスへのアクセスを置き換えるために戦略パターンを使用できますか?単体テストでは、戦略は追加の検証を提供しますが、リリースビルドでは単純なパススルーになります。
私は最近この問題が発生し、JavaリフレクションAPIを明示的に使用する問題を回避するPicklockという小さなツールを作成しました。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));
PowerMockitoはこのために作られています。Maven依存関係を使用する
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-mockito-release-full</artifactId>
<version>1.6.4</version>
</dependency>
その後、行うことができます
import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);