プライベートメソッド、プライベートメソッド、フィールド、または内部クラスを持つクラスをテストするにはどうすればよいですか?


2728

内部プライベートメソッド、フィールド、またはネストされたクラスを持つクラスをユニットテスト(xUnitを使用)するにはどうすればよいですか?または、内部リンケージstaticC / C ++で)によってプライベートにされた関数、またはプライベート(匿名)ネームスペースにある関数?

テストを実行できるようにするためだけに、メソッドまたは関数のアクセス修飾子を変更するのは悪いようです。


257
プライベートメソッドをテストする最良の方法は、直接テストすることではありません
Surya


383
同意しません。長いか理解するのが難しい(パブリック)メソッドはリファクタリングする必要があります。パブリックメソッドだけではなく、取得した小さな(プライベート)メソッドをテストしないのは愚かです。
Michael Piefel、2011年

181
可視性が愚かであるという理由だけでメソッドをテストしない。単体テストでもほぼ最小のコードである必要があり、パブリックメソッドのみをテストする場合は、エラーが発生する場所(そのメソッドなど)がどこであるかがはっきりしなくなります。
Dainius 2012

126
実装ではなく、クラスの機能をテストする必要があります。プライベートメソッドをテストしますか?それらを呼び出すパブリックメソッドをテストします。クラスが提供する機能が徹底的にテストされている場合、その内部は正確で信頼できることが実証されています。内部条件をテストする必要はありません。テストでは、テスト済みのクラスからの分離を維持する必要があります。
Calimar41 2014

回答:


1640

更新:

約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変数を取得/設定し、呼び出しprivateprivate 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)プライベートで遊ぶには必須です。


350
APIを知らない場合に便利ですが、この方法でプライベートメソッドをテストする必要がある場合は、設計に何らかの問題があります。別の投稿者が言うように、ユニットテストはクラスのコントラクトをテストする必要があります。コントラクトが広すぎてシステムのインスタンス化が多すぎる場合は、設計に対処する必要があります。
アンディガビン2009年

21
非常に便利。これを使用することは、テストが難読化の後に実行された場合にひどく失敗することを覚えておくことは重要です。
Rick Minerich

20
サンプルコードは、私のために仕事をしませんでしたが、これはthigs明確に作られた:java2s.com/Tutorial/Java/0125__Reflection/...
ロブ・

35
リフレクションを直接使用するよりもはるかに優れているのは、Powermockなどのライブラリを使用することです
Michael Piefel、2011年

25
これが、テスト駆動設計が役立つ理由です。動作を検証するために何を公開する必要があるかを理解するのに役立ちます。
するThorbjörnRavnアンデルセン

621

プライベートメソッドをテストする最良の方法は、別のパブリックメソッドを使用することです。これを実行できない場合は、次のいずれかの条件が当てはまります。

  1. プライベートメソッドはデッドコードです
  2. あなたがテストしているクラスの近くにデザインのにおいがあります
  3. テストしようとしているメソッドはプライベートであってはなりません

6
さらに、コードカバレッジが100%に近づくことは決してないので、クライアントが実際に直接使用するメソッドに品質テストを行うことに時間をかけないでください。
2013

30
@grinchスポット。プライベートメソッドのテストから得られる唯一のことは、デバッグ情報であり、それがデバッガーの目的です。クラスの契約のテストが完全にカバーされている場合は、必要なすべての情報を入手できます。プライベートメソッドは実装の詳細です。それらをテストする場合、たとえ契約が変更されていなくても、実装が変更されるたびにテストを変更する必要があります。ソフトウェアのライフサイクル全体では、これはソフトウェアがもたらす利点よりもはるかに多くの費用がかかる可能性があります。
Erik Madsen 2013年

9
@AlexWienあなたは正しい。カバレッジは適切な用語ではありませんでした。私が言っておくべきだったのは、「クラスの契約のテストがすべての意味のある入力とすべての意味のある状態をカバーしている場合」です。これは、あなたが正しく指摘したように、コードへのパブリックインターフェイスを介して、またはコードを参照するときに間接的にテストすることが実行不可能であると判明する可能性があります。あなたがテストしているいくつかのコードがそうである場合、私は次のいずれかを考える傾向があります:(1)契約が寛大すぎる(たとえば、メソッドのパラメーターが多すぎる)、または(2)契約が曖昧すぎる(たとえば、メソッドの動作)クラスの状態によって大きく異なります)。私は両方のデザインのにおいを考慮します。
Erik Madsen 2013

16
ほとんどすべてのプライベートメソッドを直接テストしないでください(メンテナンスコストを削減するため)。例外:科学的メソッドには、f(a(b(c(x)、d(y)))、a(e(x、z))、b(f(x、y、z)、z))のような形式があります。ここで、a、b、c、d、e、およびfは恐ろしく複雑な式ですが、この単一の式以外では役に立たないものです。一部の関数(MD5を考える)は反転が難しいため、動作空間を完全にカバーするパラメーターを選択するのは(NP-Hard)難しい場合があります。a、b、c、d、e、fを個別にテストする方が簡単です。それらをパブリックまたはパッケージプライベートにすることは、それらが再利用可能であることを意味します。解決策:それらをプライベートにしますが、テストします。
町の名を冠した

4
@Eponymous私はこれに少し引き裂かれました。私のオブジェクト指向の純粋主義者は、静的なプライベートメソッドではなくオブジェクト指向のアプローチを使用して、パブリックインスタンスメソッドを介してテストできるようにするべきだと言っています。しかし、再び、科学的なフレームワークでは、メモリのオーバーヘッドが完全に問題になる可能性が高いと私は考えています。
Erik Madsen 2013

323

クラスにプライベートメソッドがあり、プライベートメソッドを直接テストする必要があると感じるほど複雑な場合、それはコードのにおいです。私のクラスは複雑すぎます。

そのような問題に対処するための私の通常のアプローチは、興味深いビットを含む新しいクラスを引き出すことです。多くの場合、このメソッドとそれが相互作用するフィールド、そしておそらく別のメソッドまたは2つを新しいクラスに抽出できます。

新しいクラスはこれらのメソッドを「パブリック」として公開しているため、ユニットテストにアクセスできます。新しいクラスと古いクラスの両方が元のクラスよりも単純になりました。これは私にとっては素晴らしいことです(物事を単純にしておく必要がある、または迷ってしまいます!)。

脳を使わずにクラスを作成することはお勧めしません。ここでのポイントは、ユニットテストの力を使用して、優れた新しいクラスを見つけることです。


24
問題は、プライベートメソッドをテストする方法でした。あなたはそのために新しいクラスを作成すると(そしてさらに複雑さを追加する)と言い、新しいクラスを作成しないように提案しました。では、プライベートメソッドをどのようにテストするのでしょうか。
Dainius 2012

27
@Dainius:そのメソッドをテストできるように、単に新しいクラスを作成することはお勧めしません。テストを書くことはデザインの改善に役立つことをお勧めします。良いデザインはテストが簡単です。
Jay Bazuzi

13
しかし、良いOODは、そのクラス/オブジェクトが正しく機能するために必要なメソッドのみを公開(公開)することに同意しますか?その他はすべてプライベート/保護する必要があります。したがって、一部のプライベートメソッドにはロジックがあり、これらのメソッドをテストするIMOはソフトウェアの品質を向上させるだけです。もちろん、コードの一部が複雑である場合、それを個別のメソッド/クラスに分割する必要があることに同意します。
Dainius 2012

6
@ダニウス:新しいクラスを追加すると、ほとんどの場合、複雑さが軽減されます。測定するだけです。場合によっては、テスト容易性がOODと戦う。現代のアプローチはテスト容易性を支持するものです。
AlexWien 2013

5
これはまさに、テストからのフィードバックを使用して、設計を推進および改善する方法です。テストを聞いてください。彼らが書くのが難しい場合、それはあなたのコードについて何か重要なことを伝えています!
pnschofield 2016

289

私はこれまでJavaでこれを行うためにリフレクションを使用ましたが、私の意見ではそれは大きな間違いでした。

厳密に言えば、プライベートメソッドを直接テストする単体テストを作成するべきでありませ。テストする必要があるのは、クラスが他のオブジェクトと持っているパブリックコントラクトです。オブジェクトの内部を直接テストしないでください。別の開発者がクラスの内部契約に影響を及ぼさない小さな内部変更をクラスに加えたい場合、その開発者はリフレクションベースのテストを変更して機能することを確認する必要があります。プロジェクト全体でこれを繰り返し行うと、ユニットテストはコードの正常性の有用な測定ではなくなり、開発の妨げになり始め、開発チームの迷惑になります。

代わりに、Coberturaなどのコードカバレッジツールを使用して、作成した単体テストがプライベートメソッドでコードを適切にカバーできるようにすることをお勧めします。このようにして、プライベートメソッドの動作を間接的にテストし、より高いレベルの俊敏性を維持します。


76
これに+1。私の意見では、それは質問に対する最良の答えです。プライベートメソッドをテストすることで、実装をテストします。これは、クラスのコントラクトの入力/出力をテストするユニットテストの目的に反します。テストでは、必要があるだけ、それはその依存関係に呼び出すメソッドを模擬するために実装について十分に知っています。これ以上何もない。テストを変更せずに実装を変更できない場合、テスト戦略が不十分である可能性があります。
Colin M

10
@Colin Mそれは彼が実際に求めていることではありません;)彼に決めさせてください、あなたはプロジェクトを知りません。
アーロン・マーカス

2
本当ではない。プライベートメソッドの一部を、それを使用するパブリックメソッドを介してテストするには、多くの労力を要する可能性があります。パブリックメソッドは、プライベートメソッドを呼び出すラインに到達する前に重要な設定を必要とする可能性があります
ACV

1
同意する。契約が満たされているかどうかをテストする必要があります。これがどのように行われるかはテストしないでください。(プライベートメソッドで)100%のコードカバレッジに到達せずに連絡先が満たされる場合、それはデッドコードまたは役に立たないコードである可能性があります。
wuppi

207

この記事から:JUnitとSuiteRunner(Bill Venners)を使用したプライベートメソッドのテストには、基本的に4つのオプションがあります。

  1. プライベートメソッドをテストしないでください。
  2. メソッドにパッケージアクセスを付与します。
  3. ネストされたテストクラスを使用します。
  4. リフレクションを使用します。

@JimmyT。、「製品コード」の対象者によって異なります。ターゲットユーザーがsysadminsであるVPN内に配置されたアプリケーション用に生成されたコードを、実動コードと呼びます。
Pacerier 2015

@Pacerierどういう意味ですか?
ジミーT.

1
上記の5番目のオプションは、プライベートメソッドを呼び出すパブリックメソッドのテストですか?正しい?
user2441441 2016

1
@LorenzoLerate私は組み込み開発を行っているのでこれに取り組んできました。ソフトウェアをできるだけ小さくしたいのです。サイズの制約とパフォーマンスは、私が考えることができる唯一の理由です。

リフレクションを使用したテストが本当に好きです。ここでは、パターンMethod method = targetClass.getDeclaredMethod(methodName、argClasses); method.setAccessible(true); return method.invoke(targetObject、argObjects);
2017年

127

一般に、ユニットテストは、クラスまたはユニットのパブリックインターフェイスを実行することを目的としています。したがって、プライベートメソッドは、明示的にテストすることを期待しない実装の詳細です。


16
これがIMOの最良の回答です。または、通常言われているように、メソッドではなくテストの動作です。単体テストは、ソースコードメトリック、静的コード分析ツール、コードレビューの代わりにはなりません。プライベートメソッドが非常に複雑で、個別のテストが必要な場合は、おそらくリファクタリングする必要があり、それ以上テストをスローする必要はありません。
Dan Haynes 2013年

14
プライベートメソッドを記述しないでください。他に10個の小さなクラスを作成してください。
カミソリ

1
いいえ、基本的には、プライベートメソッドを呼び出すパブリックメソッドを使用して、プライベートメソッドの機能をテストできることを意味します。
Akshat Sharda

67

プライベートメソッドをテストしたい場所の2つの例:

  1. 復号化ルーチン -私は、テストのためだけにそれらを誰にでも見えるようにしたくありません。ただし、これらはコードに固有で複雑であり、常に機能する必要があります(明らかな例外はリフレクションでありSecurityManager、これを防止するように構成されていない場合、ほとんどの場合、プライベートメソッドでも表示するために使用できます)。
  2. コミュニティで使用するためのSDK作成します。ここでpublicは完全に異なる意味を帯びます。これは、(アプリケーションの内部だけでなく)全世界が見ることができるコードだからです。SDKユーザーに見られたくない場合は、プライベートメソッドにコードを挿入します。これは、SDKプログラミングのしくみのように、コードのにおいとしては見えません。しかしもちろん、私はまだプライベートメソッドをテストする必要があります。これらのメソッドは、私のSDKの機能が実際に存在する場所です。

「契約」のみをテストするという考えを理解しています。しかし、実際にコードをテストしないことを主張できる人はいないと思います。

したがって、私のトレードオフには、セキュリティとSDKを損なうのではなく、JUnitをリフレクションで複雑にすることが含まれます。


5
メソッドをテストするためだけにパブリックにしてprivateはいけませんが、唯一の方法ではありません。アクセス修飾子はありませんpackage private。ユニットテストが同じパッケージ内にある限り、ユニットテストを実行できます。
ngreen 14

1
私はあなたの答え、特にOPではなくポイント1についてコメントしていました。メソッドをパブリックにしたくないからといって、メソッドをプライベートにする必要はありません。
ngreen 14

1
@ngreen true thx-「public」という言葉に怠惰でした。回答を更新して、パブリック、保護、デフォルトを含めるようにしました(そして、リフレクションについても触れます)。私がしようとしていた点は、いくつかのコードが秘密にされるのには十分な理由があるということですが、それによってコードのテストが妨げられることはないはずです。
Richard Le Mesurier 14

2
みんなに実際の例を挙げてくれてありがとう。ほとんどの回答は適切ですが、理論的な観点からです。実際には、契約から実装を隠す必要がありますが、それでもテストする必要があります。これらすべてを読んで、私は多分、これは別の名前で、テストの全く新しいレベルであるべきだと思う
Z. Khullah

1
もう1つの例- クローラーHTMLパーサー。htmlページ全体をドメインオブジェクトに解析する必要があるため、構造の小さな部分を検証するための大量の重要なロジックが必要です。それをメソッドに分割して1つのパブリックメソッドから呼び出すことは理にかなっていますが、それを構成するすべての小さなメソッドをパブリックにすることは意味がなく、HTMLページごとに10個のクラスを作成することも意味がありません。
ネザー

59

プライベートメソッドはパブリックメソッドによって呼び出されるため、パブリックメソッドへの入力では、これらのパブリックメソッドによって呼び出されるプライベートメソッドもテストする必要があります。パブリックメソッドが失敗した場合、それはプライベートメソッドの失敗である可能性があります。


真の事実。問題は、1つのパブリックメソッドが複数のプライベートメソッドを呼び出す場合など、実装がより複雑な場合です。どちらが失敗しましたか?それとも、それは新しいクラスにさらされたり転送することができない複雑なプライベートな方法である場合
Z. Khullah

プライベートメソッドをテストする必要がある理由についてはすでに説明しました。あなたは「それはそれであるかもしれない」と言ったので、あなたは確信が持てません。IMO、メソッドをテストする必要があるかどうかは、アクセスレベルと直交しています。
Wei Qiu

39

私が使用した別のアプローチは、プライベートメソッドをプライベートまたは保護されたパッケージに変更し、それをGoogle Guavaライブラリの@VisibleForTestingアノテーションで補完することです。

これは、このメソッドを使用しているすべての人に、パッケージ内であっても注意して直接アクセスしないように注意することを伝えます。また、テストクラスは物理的に同じパッケージ内にある必要はありませんが、テストフォルダの下の同じパッケージ内にある必要があります。

たとえば、テストするメソッドが含まれているsrc/main/java/mypackage/MyClass.java場合、テスト呼び出しはに配置する必要がありますsrc/test/java/mypackage/MyClassTest.java。このようにして、テストクラスのテストメソッドにアクセスできます。


1
私はこれについて知りませんでした。それは興味深いものです。そのような注釈が必要な場合は、設計上の問題があると私はまだ思います。
2017年

2
私にとって、これは、消防訓練に一回限りの鍵を与えて消防訓練を行うのではなく、ドアにサインを付けて玄関のドアをロック解除したままにする-「テストのためにロック解除-あなたが当社に入らないでください。」
Jeremy Krieg神父、2017年

@FrJeremyKrieg-それはどういう意味ですか?
MasterJoe2

1
@ MasterJoe2:火災テストの目的は、火災のリスクに対する建物の安全性を向上させることです。しかし、あなたは監視員にあなたの建物へのアクセスを与える必要があります。玄関のドアを常にロック解除したままにしておくと、不正アクセスのリスクが高まり、安全性が低下します。同じことがVisibleForTestingパターンにも当てはまります。「火災」のリスクを減らすために、不正アクセスのリスクを高めています。アクセスを永続的にロック解除したままにする(非公開ではなく)のではなく、必要な場合にのみ(たとえば、リフレクションを使用して)テスト用の1回限りのアクセスを許可する方が適切です。
Jeremy Krieg神父

34

大きくて風変わりなクラスでレガシーコードをテストするには、私が書いている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);

2
Source Forge-Recommended Project PrivateAccessorではなく、必ずJUnit-addons(sourceforge.net/projects/junit-addons)パッケージ全体をダウンロードしてください。
skia.heliou

2
またClass、これらのメソッドで最初のパラメーターとしてa を使用している場合は、staticメンバーにのみアクセスしていることを確認してください。
skia.heliou 2015年

31

Spring Frameworkでは、このメソッドを使用してプライベートメソッドをテストできます。

ReflectionTestUtils.invokeMethod()

例えば:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

これまでで最もクリーンで最も簡潔なソリューションですが、Springを使用している場合のみです。
Denis Nikolaenko

28

リフレクションを使用してCem Catikkasのソリューションを試したことがあるJavaのをたので、彼がここで説明したよりもエレガントなソリューションであると言えるでしょう。ただし、リフレクションを使用する代わりの方法を探していて、テストしているソースにアクセスできる場合でも、これはオプションです。

特にテスト駆動開発では、コードを記述する前に小さなテストを設計したい場合に、クラスのプライベートメソッドをテストすることにメリットがある可能性があります。

プライベートメンバーとメソッドにアクセスできるテストを作成すると、パブリックメソッドのみにアクセスすることで具体的にターゲットにすることが難しいコード領域をテストできます。パブリックメソッドに複数のステップが含まれる場合、それは複数のプライベートメソッドで構成され、個別にテストできます。

利点:

  • より細かい粒度でテストできます

短所:

  • テストコードはソースコードと同じファイルに存在する必要があり、維持がより困難になる可能性があります。
  • .class出力ファイルと同様に、ソースコードで宣言されているのと同じパッケージ内にある必要があります。

ただし、継続的なテストでこのメソッドが必要な場合は、プライベートメソッドを抽出する必要があるという合図であり、従来のパブリックな方法でテストできます。

これがどのように機能するかの複雑な例です:

// 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ます。

参照:Javaヒント106:楽しさと利益のための静的内部クラス


26

他の人が言ったように...プライベートメソッドを直接テストしないでください。ここにいくつかの考えがあります:

  1. すべてのメソッドを小さく集中してください(テストが簡単で、問題点を見つけやすい)
  2. コードカバレッジツールを使用します。私はコベルトゥーラが好きです(ああ幸せな日、新しいバージョンが出たようです!)

単体テストでコードカバレッジを実行します。メソッドが完全にテストされていない場合は、テストに追加してカバレッジを上げてください。100%のコードカバレッジを目指しますが、おそらくそれを取得できないことを理解してください。


コードカバレッジのアップ。プライベートメソッドにどのようなロジックがあるかに関係なく、パブリックメソッドを通じてそれらのロジックを呼び出します。コードカバレッジツールは、テストでカバーされている部分を表示できるため、プライベートメソッドがテストされているかどうかを確認できます。
coolcfan 2012

唯一のパブリックメソッドがmain []であるクラスを見てきました。それらはGUIをポップアップし、データベースと世界中のいくつかのWebサーバーを接続します。「間接的にテストしないでください」と言うのは簡単です...
Audrius Meskauskas 14

26

プライベートメソッドはパブリックメソッドによって使用されます。そうでなければ、それらはデッドコードです。そのため、パブリックメソッドをテストして、パブリックメソッドの期待される結果をアサートし、それによって、プライベートメソッドが消費します。

プライベートメソッドのテストは、パブリックメソッドでユニットテストを実行する前に、デバッグによってテストする必要があります。

テスト駆動開発を使用してデバッグすることもでき、すべてのアサーションが満たされるまでユニットテストをデバッグします。

私は個人的にはTDDを使用してクラスを作成する方が良いと信じています。publicメソッドスタブを作成し、事前に定義されたすべてのアサーションを使用て単体テストを生成します。そのため、メソッドの予想される結果は、コーディングする前に決定されます。このようにして、ユニットテストのアサーションを結果に合わせるという間違った道をたどることはありません。その後、クラスは堅牢になり、すべての単体テストに合格したときに要件を満たします。


2
これは事実ですが、いくつかの非常に複雑なテストにつながる可能性があります。グループ全体ではなく、一度に1つのメソッドをテストする(ユニットテスト)方がよいパターンです。ひどい提案ではありませんが、ここでより良い答えがあると思います-これは最後の手段であるべきです。
ビルK

24

Springを使用する場合、ReflectionTestUtilsは、最小限の労力でここで役立ついくつかの便利なツールを提供します。たとえば、望ましくないパブリックセッターを追加せずにプライベートメンバーにモックを設定するには:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

気が進まない、または変更できない既存のコードをテストする場合は、リフレクションが適しています。

クラスの設計がまだ柔軟で、個別にテストしたい複雑なプライベートメソッドがある場合は、それを個別のクラスに引き出して、そのクラスを個別にテストすることをお勧めします。これは、元のクラスのパブリックインターフェイスを変更する必要はありません。内部でヘルパークラスのインスタンスを作成し、ヘルパーメソッドを呼び出すことができます。

ヘルパーメソッドからの難しいエラー条件をテストする場合は、さらに一歩進んでください。ヘルパークラスからインターフェイスを抽出し、パブリックゲッターとセッターを元のクラスに追加して(そのインターフェイスを通じて使用される)ヘルパークラスをインジェクトし、モックバージョンのヘルパークラスを元のクラスにインジェクトして、元のクラスがどのようにテストするかヘルパーからの例外に応答します。この方法は、ヘルパークラスをテストせずに元のクラスをテストする場合にも役立ちます。


19

プライベートメソッドをテストすると、内部実装を変更するたびにクライアントコード(この場合はテスト)が破損するため、クラスのカプセル化が破損します。

したがって、プライベートメソッドをテストしないでください。


4
単体テストとsrcコードはペアです。srcを変更した場合、おそらくユニットテストを変更する必要があります。それがjunit testの意味です。彼らはすべてが以前と同じように機能することを保証します。コードが変更された場合、それらが壊れても問題ありません。
AlexWien 2013

1
コードが変更された場合に単体テストを変更する必要があることに同意しないでください。クラスの既存の機能を変更せずに機能をクラスに追加するように命令された場合、コードを変更した後でも、存在する単体テストはパスするはずです。Peterが述べているように、単体テストは内部でどのように行われるかではなく、インターフェイスをテストする必要があります。テスト駆動開発では、コードが記述される前にユニットテストが作成され、内部での解決方法ではなく、クラスのインターフェイスに焦点が当てられます。
Harald Coppoolse 2015年

16

JUnit.org FAQページからの回答:

しかし、あなたがしなければならない場合...

JDK 1.3以降を使用している場合は、リフレクションを使用して、PrivilegedAccessorを利用してアクセス制御メカニズムを破壊できます。。使い方の詳細はこちらの記事をご覧ください

JDK 1.6以降を使用していて、@ Testでテストに注釈を付ける場合は、Dp4jを使用して、テストメソッドにリフレクションを挿入できます。使用方法の詳細については、このテストスクリプトを参照してください。

PS私はDp4jの主な貢献者です。助けが必要かどうかに尋ねてください。:)


16

コードを変更できないレガシーアプリケーションのプライベートメソッドをテストする場合、Javaのオプションの1つはjMockitです。これにより、クラスにプライベートであってもオブジェクトのモックを作成できます。


4
jmockitライブラリの一部として、あなたは簡単にプライベートメソッドをテストしますDeencapsulationクラスへのアクセス権を持っている: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

私はいつもこのアプローチを使用しています。非常に役立つ
MedicineMan

14

私はプライベートメソッドをテストしない傾向があります。狂気があります。個人的には、公開されているインターフェース(および保護されたメソッドと内部メソッドを含む)のみをテストする必要があると思います。



13

コードを少しリファクタリングすることをお勧めします。コードをテストするだけで、リフレクションまたはその他の種類のものを使用することを考える必要がある場合、コードに問題があります。

さまざまな種類の問題について言及しました。プライベートフィールドから始めましょう。プライベートフィールドの場合は、新しいコンストラクターを追加してフィールドを注入します。これの代わりに:

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;
}

ほとんどの場合、私が書いたのは依存関係注入パターンのようです。私の個人的な経験では、テスト中にそれは本当に役立ちます。この種のコードはよりクリーンで、保守が容易になると思います。ネストされたクラスについても同様です。ネストされたクラスに重いロジックが含まれている場合は、それをパッケージのプライベートクラスとして移動し、それを必要とするクラスに注入した方がよいでしょう。

他にも、レガシーコードのリファクタリングとメンテナンス中に使用したデザインパターンがいくつかありますが、それはすべて、テストするコードのケースによって異なります。リフレクションの使用はほとんど問題ではありませんが、十分にテストされたエンタープライズアプリケーションがあり、すべての展開の前にテストが実行されると、すべてが非常に遅くなります(それは単に煩わしいだけで、私はそのようなものは好きではありません)。

セッター注入もありますが、使用はお勧めしません。私はコンストラクタを使い、本当に必要なときにすべてを初期化し、必要な依存関係を注入する可能性を残した方がいいです。


1
ClassToTest(Callable)注射に同意しない。それはClassToTestより複雑になります。複雑にしないでおく。また、それは、必要とし、他の誰かを伝えるためにClassToTest何かClassToTestのボスである必要があります。ロジックを注入する場所がありますが、これはそうではありません。クラスを維持するのが難しくなりました。簡単ではありません。
Loduwijk 2017年

また、テストの方法XによってX複雑さが増し、問題が増える可能性があるため、追加のテストが必要になる場合があります。問題を引き起こす可能性のある方法で実装した場合は、さらにテストを行う必要があります。 。(これは無限ループではありません。各反復はその前の反復よりも小さい可能性がありますが、いらいらします)
Loduwijk

2
クラスClassToTestを維持するのが難しくなる理由を説明していただけますか?実際には、アプリケーションのメンテナンスが容易になります。必要なすべての異なる値firstと「2番目」の変数に対して新しいクラスを作成することをどのように提案しますか?
GROX13 2017年

1
テストの方法は複雑さを増しません。不十分に記述されているのはあなたのクラスだけなので、テストすることができません。
GROX13

クラスがより複雑であるため、保守が困難です。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が保守しやすくなると主張できる方法はわかりません。このような注入が理にかなっている場合がありますが、私はあなたの例を「維持しやすい」とは思いません
Loduwijk

12

プライベートメソッドは、同じクラス内でのみアクセスされます。したがって、任意のテストクラスからターゲットクラスの「プライベート」メソッドをテストする方法はありません。方法としては、ユニットテストを手動で実行したり、メソッドを「プライベート」から「保護」に変更したりできます。

そして、保護されたメソッドは、クラスが定義されている同じパッケージ内でのみアクセスできます。したがって、ターゲットクラスの保護されたメソッドをテストするには、ターゲットクラスと同じパッケージでテストクラスを定義する必要があります。

上記のすべてが要件に合わない場合は、リフレクションを使用してプライベートメソッドにアクセスします。


「保護された」と「友好的な」が混在しています。プロテクトメソッドは、オブジェクトがターゲットクラス(つまり、サブクラス)に割り当て可能なクラスによってのみアクセスできます。
Sayo Oladeji、2015年

1
実際、Javaには「Friendly」はありません。用語は「Package」であり、private / public / protected修飾子がないことで示されます。私はこの答えを修正しただけですが、すでにこれを言っている本当に良いものがあります-それで私はそれを削除することをお勧めします。
ビルK

「この答えはまったく間違っている」とずっと考えながら、私はほとんど反対票を投じました。最後の文にたどり着くまでは、残りの答えとは矛盾します。最後の文が最初のはずです。
Loduwijk 2017年

11

上記の多くが示唆しているように、あなたの公開インターフェースを介してそれらをテストすることは良い方法です。

これを行う場合は、コードカバレッジツール(Emmaなど)を使用して、プライベートメソッドが実際にテストから実行されているかどうかを確認することをお勧めします。


間接的にテストしないでください!カバレッジを介して触れるだけでなく、期待どおりの結果が得られることをテストしてください!
AlexWien 2013

11

以下は、プライベートフィールドをテストするための汎用関数です。

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);
}

10

以下の例を参照してください。

次のインポートステートメントを追加する必要があります。

import org.powermock.reflect.Whitebox;

これで、プライベートメソッド、呼び出されるメソッド名、および以下の追加パラメーターを持つオブジェクトを直接渡すことができます。

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

今日、私はプライベートメソッドとフィールドのテストを支援するために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はカプセル化を解除します。それはあなたに反射を通してそれらすべてへのアクセスを提供します、しかしすべてがコンパイル時にチェックされます。

いくつかのレガシーコードのテストに最適です。慎重に使用してください。;)

https://github.com/stephanenicolas/boundbox


1
試してみたところ、BoundBoxはシンプルでエレガントなソリューションです。
2013年

9

まず、私はこの質問を投げ捨てます:なぜプライベートメンバーは分離テストが必要なのですか?それらは複雑で、公共の表面とは別にテストを必要とするような複雑な動作を提供していますか?これはユニットテストであり、「コード行」テストではありません。細かいことは気にしないでください。

それらが非常に大きい場合、これらのプライベートメンバーはそれぞれ、複雑さが非常に大きい「ユニット」です。このようなプライベートメンバーをこのクラスからリファクタリングすることを検討してください。

リファクタリングが不適切または実行不可能である場合、ユニットテスト中にこれらのプライベートメンバー関数/メンバークラスへのアクセスを置き換えるために戦略パターンを使用できますか?単体テストでは、戦略は追加の検証を提供しますが、リリースビルドでは単純なパススルーになります。


3
多くの場合、パブリックメソッドからの特定のコードは内部プライベートメソッドにリファクタリングされ、実際には、間違っているかもしれないロジックの重要な部分です。パブリックメソッドとは別にこれをテストしたい
oxbow_lakes '15

単体テストのない最短のコードでさえ正しくありません。2つの地理的角度の違いを計算してみてください。4行のコード。ほとんどの場合、最初の試行では修正されません。そのようなメソッドは、trustfullコードのベースを形成するため、ユニットテストが必要です。(そのような有用なコードも公開することができます。あまり保護されていません
AlexWien 2013

8

私は最近この問題が発生し、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));

7

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