プライベート メソッドの単体テストはどのように行うのでしょうか?
-
05-07-2019 - |
質問
いくつかのパブリックメソッドとプライベートメソッドを持つクラスライブラリを構築しています。プライベート メソッドを単体テストできるようにしたいと考えています (主に開発中ですが、将来のリファクタリングにも役立つ可能性があります)。
これを行う正しい方法は何ですか?
解決
.netを使用している場合は、 InternalsVisibleToAttribute 。
他のヒント
プライベートメソッドを単体テストする場合は、何かが間違っている可能性があります。ユニットテストは(一般的に)クラスのインターフェイスをテストすることを意味します。つまり、パブリック(および保護された)メソッドを意味します。もちろん、「ハッキング」できます。これに対する解決策(メソッドを公開するだけでも)を検討することもできます。
- テストするメソッドが本当にテストする価値がある場合、独自のクラスに移動する価値があるかもしれません。
- プライベートメソッドを呼び出すパブリックメソッドにテストを追加し、プライベートメソッドの機能をテストします。 (コメンテーターが示したように、これらのプライベートメソッドの機能が実際にパブリックインターフェイスの一部である場合にのみ、これを行う必要があります。
プライベート メソッドをテストするのは役に立たない可能性があります。ただし、テスト メソッドからプライベート メソッドを呼び出したい場合もあります。ほとんどの場合、テストデータ生成のためのコードの重複を防ぐため...
Microsoft は、このために 2 つのメカニズムを提供しています。
アクセサ
- クラス定義のソースコードに移動します
- クラス名を右クリックします
- 「プライベート・アクセサーの作成」を選択します。
- アクセサーを作成するプロジェクトを選択します=> foo_accessorという名前の新しいクラスになります。このクラスはコンパイル中に動的に生成され、すべてのメンバーをパブリックに利用できるようにします。
ただし、元のクラスのインターフェイスを変更する場合、このメカニズムは少し扱いにくい場合があります。したがって、ほとんどの場合、私はこれを使用することを避けます。
PrivateObject クラスもう 1 つの方法は、Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject を使用することです。
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
「外部インターフェースのテストのみに関心があるべき」には同意しません。哲学。車の修理工場では、車輪が回転するかどうかを確認するためのテストのみを行うべきだと言っているようなものです。はい、最終的に私は外部の振る舞いに興味がありますが、私は自分自身のプライベートな内部テストがもう少し具体的で、要点であるのが好きです。はい、リファクタリングする場合、テストの一部を変更する必要がありますが、大規模なリファクタリングでない限り、変更する必要はありません。他の(変更されていない)内部テストがまだ機能しているという事実は、リファクタリングは成功しました。
パブリックインターフェースのみを使用してすべての内部ケースをカバーしようとすることができ、理論的にはパブリックインターフェースを使用してすべての内部メソッド(または少なくとも重要なすべてのメソッド)を完全にテストすることができますが、これを達成するためには、パブリックインターフェースを介して実行されるテストケースとテスト用に設計されたソリューションの内部部分との接続を識別するのが困難または不可能な場合があります。指摘したように、内部機械が適切に動作していることを保証する個々のテストは、リファクタリングで生じる小さなテスト変更の価値があります-少なくともそれは私の経験です。リファクタリングごとにテストに大きな変更を加える必要がある場合、これは意味をなさないかもしれませんが、その場合は、デザインを完全に再考する必要があります。優れた設計は、大規模な再設計なしでほとんどの変更を可能にするのに十分な柔軟性を備えている必要があります。
プライベート関数をテストしたいまれなケースでは、通常、代わりにそれらを保護するように変更し、パブリックラッパー関数を使用してサブクラスを作成しました。
クラス:
...
protected void APrivateFunction()
{
...
}
...
テスト用のサブクラス:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
より根本的な質問をする必要があると思うのは、そもそもなぜプライベートメソッドをテストしようとしているのかということです。それは、そのクラスのパブリックインターフェイスを介してプライベートメソッドをテストしようとしているコードの匂いですが、そのメソッドは実装の詳細であるため、理由はプライベートです。カバーの下でどのように実装されるかではなく、パブリックインターフェイスの動作にのみ注意する必要があります。
一般的なリファクタリングを使用してプライベートメソッドの動作をテストする場合、コードを別のクラスに抽出できます(パッケージレベルの可視性があるため、パブリックAPIの一部ではないことを確認します)。その後、その動作を単独でテストできます。
リファクタリングの結果は、プライベートメソッドが元のクラスの共同作業者となった別のクラスになったことを意味します。その動作は、独自の単体テストで十分に理解されるようになります。
元のクラスをテストしようとすると、パブリックインターフェイスの組み合わせの爆発とすべての動作をテストするのではなく、そのクラスのパブリックインターフェイスの動作のテストに集中できるように、その動作をモックできます。そのプライベートメソッド。
これは車の運転に似ています。車を運転するとき、ボンネットを上げたまま運転しないので、エンジンが作動していることがわかります。エンジンが作動していることを知るために、車が提供するインターフェース、つまり回転数カウンターと速度計に依存しています。アクセルペダルを踏むと車が実際に動くという事実に頼っています。エンジンをテストしたい場合は、それを単独でチェックできます。 :D
もちろん、レガシーアプリケーションがある場合、プライベートメソッドを直接テストすることは最後の手段かもしれませんが、より良いテストを可能にするためにレガシーコードをリファクタリングすることを望みます。マイケル・フェザーズは、まさにこの主題について素晴らしい本を書いています。 http://www.amazon.co.uk/Working-Effectively-Legacy- Robert-Martin / dp / 0131177052
プライベートタイプ、内部メンバー、およびプライベートメンバーは何らかの理由でそうなっているため、多くの場合、直接それらをいじりたくありません。そうした場合、それらのアセンブリを作成した人がプライベート/内部実装をそのまま維持する保証がないため、後で中断する可能性があります。
しかし、時には、コンパイル済みまたはサードパーティアセンブリのハッキング/調査を行うときに、プライベートクラスまたはプライベートまたは内部コンストラクタを持つクラスを初期化することになりました。または、時には変更できないプリコンパイル済みのレガシーライブラリを扱うとき-プライベートメソッドに対していくつかのテストを書くことになります。
AccessPrivateWrapperが誕生- http://amazedsaint.blogspot .com / 2010/05 / accessprivatewrapper-c-40-dynamic.html -これは、C#4.0の動的機能とリフレクションを使用して作業を簡単にするクイックラッパークラスです。
次のような内部/プライベートタイプを作成できます
//Note that the wrapper is dynamic
dynamic wrapper = AccessPrivateWrapper.FromType
(typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
//Access the private members
wrapper.PrivateMethodInPrivateClass();
2つの方法でプライベートメソッドを単体テストできます
-
PrivateObject
クラスのインスタンスを作成できます。構文は次のとおりですPrivateObject obj= new PrivateObject(PrivateClass); //now with this obj you can call the private method of PrivateCalss. obj.PrivateMethod("Parameters");
-
リフレクションを使用できます。
PrivateClass obj = new PrivateClass(); // Class containing private obj Type t = typeof(PrivateClass); var x = t.InvokeMember("PrivateFunc", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, obj, new object[] { 5 });
InternalsVisibleToAttributeメソッドも使用しました。これを達成するために以前プライベートなメソッドを内部に作成することに不快感を感じた場合は、とにかく直接の単体テストの対象にすべきではないことにも言及する価値があります。
結局のところ、クラスの特定の実装ではなく、クラスの動作をテストしています-前者を変更せずに後者を変更でき、テストは引き続き実行されるはずですパス。
プライベートメソッドには2つのタイプがあります。静的プライベートメソッドと非静的プライベートメソッド(インスタンスメソッド)。次の2つの記事では、プライベートメソッドをユニットテストする方法を例とともに説明します。
MS Testには、VSCodeGenAccessorsというファイルを作成することにより、プライベートメンバーとメソッドをプロジェクトで使用可能にする優れた機能が組み込まれています
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
BaseAccessorから派生したクラスを使用
など
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
CodeProjectには、プライベートメソッドのテストの長所と短所を簡単に説明する記事があります。次に、プライベートメソッドにアクセスするためのリフレクションコードを提供します(上記のMarcusが提供するコードと同様です)。サンプルで見つかった唯一の問題は、コードがオーバーロードされたメソッドを考慮しないことです。
こちらの記事をご覧ください:
internal
を宣言し、 InternalsVisibleToAttribute
を使用して、単体テストアセンブリがそれらを表示できるようにします。
コンパイラ指令は、物事がすぐに乱雑になるため、使用しない傾向があります。本当に必要な場合にそれを緩和する1つの方法は、それらを部分クラスに入れ、実稼働バージョンを作成するときにビルドでその.csファイルを無視することです。
最初にコードのプライベートメソッドをテストしないでください。クラスのパブリックなものである「パブリックインターフェイス」またはAPIをテストする必要があります。 APIは、外部の呼び出し元に公開するすべてのパブリックメソッドです。
理由は、クラスのプライベートメソッドと内部のテストを開始すると、クラスの実装(プライベートなもの)をテストに結合するからです。これは、実装の詳細を変更する場合、テストも変更する必要があることを意味します。
このため、InternalsVisibleToAtrributeを使用しないでください。
Ian Cooperによるこのテーマに関する素晴らしい講演があります: Ian Cooper:TDD、どこでうまくいかなかったのですか
プライベート宣言をテストすることが良い場合があります。 基本的に、コンパイラには、Compile(string outputFileName、params string [] sourceSFileNames)という1つのパブリックメソッドしかありません。各「隠し」をテストせずにそのようなメソッドをテストすることは難しいことを理解していると確信しています。宣言!
だからこそ、テストを簡単にするためにVisual T#を作成しました。無料の.NETプログラミング言語(C#v2.0互換)です。
「.-」演算子を追加しました。 「。」のように動作します。ただし、テストされたプロジェクトで何も変更せずに、テストから隠された宣言にアクセスすることもできます。
当社のWebサイトをご覧ください:ダウンロード 無料。
これについて誰もまだ言っていないことに驚いていますが、私が採用した解決策は、クラス内に静的メソッドを作成してテストすることです。これにより、テスト対象のパブリックおよびプライベートすべてにアクセスできます。
さらに、スクリプト言語(Python、Ruby、PHPなどのオブジェクト指向機能)では、実行時にファイル自体をテストできます。あなたの変更が何も壊さないことを確実にする素早い手っ取り早い方法。これは明らかに、すべてのクラスをテストするためのスケーラブルなソリューションになります。すべてを実行するだけです。 (他の言語でも、テストを常に実行するvoid mainを使用してこれを行うことができます。)
ここで、プライベートメソッドをテストする任意のクラスで使用できる明確なコード例を作成します。
テストケースクラスにこれらのメソッドを含めるだけで、指示どおりにそれらを使用します。
/**
*
* @var Class_name_of_class_you_want_to_test_private_methods_in
* note: the actual class and the private variable to store the
* class instance in, should at least be different case so that
* they do not get confused in the code. Here the class name is
* is upper case while the private instance variable is all lower
* case
*/
private $class_name_of_class_you_want_to_test_private_methods_in;
/**
* This uses reflection to be able to get private methods to test
* @param $methodName
* @return ReflectionMethod
*/
protected static function getMethod($methodName) {
$class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
$method = $class->getMethod($methodName);
$method->setAccessible(true);
return $method;
}
/**
* Uses reflection class to call private methods and get return values.
* @param $methodName
* @param array $params
* @return mixed
*
* usage: $this->_callMethod('_someFunctionName', array(param1,param2,param3));
* {params are in
* order in which they appear in the function declaration}
*/
protected function _callMethod($methodName, $params=array()) {
$method = self::getMethod($methodName);
return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
}
$ this-> _callMethod( '_ someFunctionName'、array(param1、param2、param3));
元のプライベート関数に表示される順序でパラメーターを発行するだけです
すべての混乱や混乱なしにプライベートメソッドを実行したい人のために。これは、古き良きReflectionを使用する単体テストフレームワークで機能します。
public class ReflectionTools
{
// If the class is non-static
public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
{
Type t = objectUnderTest.GetType();
return t.InvokeMember(method,
BindingFlags.InvokeMethod |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static,
null,
objectUnderTest,
args);
}
// if the class is static
public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
{
MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
foreach(var member in members)
{
if (member.Name == method)
{
return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
}
}
return null;
}
}
実際のテストでは、次のようなことができます:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
MbUnitは、Reflectorと呼ばれるこのための素晴らしいラッパーを取得しました。
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
プロパティから値を設定および取得することもできます
dogReflector.GetProperty("Age");
「プライベートをテスト」について私はそれに同意します。完璧な世界で。プライベートユニットテストを行う意味はありません。しかし、現実の世界では、コードをリファクタリングする代わりにプライベートテストを書きたいと思うかもしれません。
こちらは良い記事プライベートメソッドの単体テストについて。しかし、テスト用に特別に設計されたアプリケーションを作成する(テスト専用のテストを作成するようなもの)か、テストに反射を使用するのが良いのかわかりません。 私たちのほとんどが2番目の方法を選択することを確信しています。
PrivateObject を使用しますクラス。ただし、前述のように、プライベートメソッドのテストを避ける方が適切です。
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public
" CC"私が使用するシステムのコマンドラインコンパイラです。 -Dfoo = bar
は、 #define foo bar
と同等の機能を果たします。したがって、このコンパイルオプションは、すべてのプライベートなものをパブリックに効果的に変更します。
例は、最初のメソッドシグネチャです。
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
テストは次のとおりです。
/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
object[] values = new object[] { 2, "Martin" };
XPathString xp = new XPathString(path, values);
PrivateObject param0 = new PrivateObject(xp);
XPathString_Accessor target = new XPathString_Accessor(param0);
string[] expected = new string[] {
"pair[path/to/@Key={0}]",
"Items",
"Item[Name={1}]",
"Date"
};
string[] actual;
actual = target.SplitInternal();
CollectionAssert.AreEqual(expected, actual);
}
これを行う方法は、メソッドを protected
にして、テストするクラスを継承するテストフィクスチャを記述することです。この方法では、メソッドを public
に変えることはありませんが、テストを有効にします。
1)レガシーコードがある場合、プライベートメソッドをテストする唯一の方法はリフレクションです。
2)新しいコードの場合、次のオプションがあります:
- リフレクションを使用する(複雑にする)
- 同じクラスで単体テストを記述します(量産コードが見苦しくなります テストコードも含まれています)
- リファクタリングして、何らかの種類のutilクラスでメソッドをパブリックにします
- @VisibleForTestingアノテーションを使用してプライベートを削除
私は、最も簡単で複雑でないアノテーション方法を好みます。唯一の問題は、私たちが大きな関心事ではないと思う可視性を高めたことです。 常にインターフェイスにコーディングする必要があるため、インターフェイスMyServiceと実装MyServiceImplがある場合、対応するテストクラスはMyServiceTest(テストインターフェイスメソッド)およびMyServiceImplTest(テストプライベートメソッド)になります。とにかくすべてのクライアントはインターフェースを使用する必要があるため、プライベートメソッドの可視性が向上したとしても、それは実際には重要ではありません。
デバッグモードでビルドするときに、パブリックまたは内部(InternalsVisibleToAttributeを使用)として宣言することもできます。
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
コードは肥大化しますが、リリースビルドでは private
になります。
私の意見では、クラスのパブリックAPIの単体テストのみを行うべきです。
単体テストのためにメソッドを公開すると、カプセル化が解除され、実装の詳細が公開されます。
優れたパブリックAPIは、クライアントコードの当面の目標を解決し、その目標を完全に解決します。
Visual Studio 2008からプライベートメソッドのテストメソッドを生成できます。プライベートメソッドの単体テストを作成すると、Test Referencesフォルダーがテストプロジェクトに追加され、アクセサーがそのフォルダーに追加されます。アクセサーは、単体テストメソッドのロジックでも参照されます。このアクセサを使用すると、単体テストでテストしているコード内のプライベートメソッドを呼び出すことができます。 詳細については、次をご覧ください