いくつかのパブリックメソッドとプライベートメソッドを持つクラスライブラリを構築しています。私はプライベートメソッドをユニットテストできるようにしたいと思います(ほとんどは開発中ですが、将来のリファクタリングにも役立つ可能性があります)。
これを行う正しい方法は何ですか?
いくつかのパブリックメソッドとプライベートメソッドを持つクラスライブラリを構築しています。私はプライベートメソッドをユニットテストできるようにしたいと思います(ほとんどは開発中ですが、将来のリファクタリングにも役立つ可能性があります)。
これを行う正しい方法は何ですか?
回答:
.netを使用している場合は、InternalsVisibleToAttributeを使用する必要があります。
#if DEBUG
、InternalsVisibleTo
属性の周りで使用できませんでしたか?
#if RELEASE_TEST
周りのものを使用InternalsVisibleTo
し、を定義するリリースビルド構成のコピーを作成することRELEASE_TEST
です。最適化を使用してリリースコードをテストできますが、実際にリリース用にビルドすると、テストは省略されます。
プライベートメソッドを単体テストする場合は、何かが間違っている可能性があります。ユニットテストは(一般的に言えば)クラスのインターフェースをテストすることを意味しており、それはそのパブリック(そして保護された)メソッドを意味します。もちろん、これに対するソリューションを「ハック」することもできます(たとえメソッドを公開するだけでも)が、次のことも検討する必要があります。
プライベートメソッドのテストは役に立たない場合があります。ただし、テストメソッドからプライベートメソッドを呼び出すことも好きです。ほとんどの場合、テストデータ生成のコードの重複を防ぐために...
Microsoftはこれのために2つのメカニズムを提供します。
アクセサ
ただし、元のクラスのインターフェースの変更に関しては、メカニズムが少し扱いにくい場合があります。したがって、ほとんどの場合、これを使用することは避けます。
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[] {/* ... */} );
「外部インターフェースのテストのみに関心を持つべき」という哲学には同意しません。自動車修理工場は、車輪が回転するかどうかを確認するためのテストだけを行うべきだと言うのと少し似ています。はい、最終的には外部の振る舞いに興味がありますが、私自身のプライベートな内部テストをもう少し具体的かつ的確にしたいです。はい、リファクタリングする場合、いくつかのテストを変更する必要があるかもしれませんが、大規模なリファクタリングでない限り、変更する必要があるのはわずかであり、他の(変更されていない)内部テストが引き続き機能するという事実は、リファクタリングは成功しています。
パブリックインターフェイスのみを使用してすべての内部ケースをカバーすることを試みることができ、理論的にはパブリックインターフェイスを使用してすべての内部メソッド(または少なくとも重要なすべてのメソッド)を完全にテストすることが可能ですが、最終的には頭に立って達成しなければならない場合がありますこれと、パブリックインターフェイスを介して実行されているテストケースと、テスト用に設計されたソリューションの内部部分との間の接続は、識別が困難または不可能である場合があります。指摘したように、内部の機械が適切に動作していることを保証する個々のテストは、リファクタリングに伴う小さなテストの変更に十分価値があります-少なくともそれは私の経験でした。すべてのリファクタリングのためにテストに大きな変更を加える必要がある場合、これは意味をなさないかもしれませんが、その場合は、デザインを完全に再考する必要があります。
FooService
関係していることをX
、あなたが気にする必要があり、すべてはそれが本当にやるんですX
要求されたとき。それがどのように行われるかは重要ではありません。(おそらくない)インターフェースで識別できない問題がクラスにある場合でも、それは有効ですFooService
。それが問題だ場合であるインターフェースを通して見える、公共のメンバーのテストでは、それを検出する必要があります。重要なのは、ホイールが正しく回転する限り、ホイールとして使用できるということです。
PrivMethod
場合、PubMethod
どの呼び出しPrivMethod
でそれを公開する必要があるかをテストしますか?あなたを変更したときに何が起こるSimpleSmtpService
のGmailService
?突然、アプリケーションが設計どおりに正常に動作する場合でも、プライベートテストが存在しないか、動作が異なり、失敗する可能性があるコードを指しています。両方のメール送信者に適用される複雑な処理がある場合、おそらくEmailProcessor
両方で使用でき、個別にテストできるものにすべきでしょうか?
まれに、プライベート関数をテストしたい場合は、通常は保護するように変更し、パブリックラッパー関数を使用してサブクラスを作成しました。
クラス:
...
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 });
PrivateClass
firstのインスタンスを宣言して使用する必要があります。stackoverflow.com/questions/9122708/…–
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
て、単体テストアセンブリがそれらを参照できるようにします。
そもそも、コードのプライベートメソッドをテストするべきではありません。クラスの公開物である「公開インターフェイス」またはAPIをテストする必要があります。APIは、外部の呼び出し元に公開するすべてのパブリックメソッドです。
その理由は、クラスのプライベートメソッドと内部のテストを開始すると、クラスの実装(プライベートのもの)をテストに結合することになるためです。つまり、実装の詳細を変更する場合は、テストも変更する必要があります。
このため、InternalsVisibleToAtrributeの使用は避けてください。
イアンクーパーによるこのテーマを取り上げた素晴らしい講演は次のとおりです。イアンクーパー:TDD、どこがうまくいかなかったか
プライベート宣言をテストするとよい場合があります。基本的に、コンパイラーには1つのパブリックメソッドCompile(string outputFileName、params string [] sourceSFileNames)しかありません。「隠された」各宣言をテストせずにそのようなメソッドをテストするのは難しいと理解していると思います!
これが、テストを簡単にするためにVisual T#を作成した理由です。これは無料の.NETプログラミング言語です(C#v2.0互換)。
「.-」演算子を追加しました。「。」のように動作します。演算子。ただし、テストしたプロジェクトの内容を変更せずに、テストから非表示の宣言にアクセスできます。
当社のウェブサイトを見てみましょう:ダウンロードし、それを無料で。
まだ誰も言っていないことに驚いていますが、私が採用した解決策は、クラス内に静的メソッドを作成して、それ自体をテストすることです。これにより、テスト用にパブリックおよびプライベートのすべてにアクセスできます。
さらに、(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");
「テストプライベート」に関しては、私はそれに同意します。プライベートユニットテストを実行しても意味がありません。しかし、現実の世界では、コードをリファクタリングする代わりにプライベートテストを書きたいと思うかもしれません。
Reflector
より強力に置き換えられていMirror
ガッリオ/ MbUnitのv3.2の中で。(gallio.org/wiki/doku.php?id=mbunit:mirror)
私は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);
}
1)レガシーコードがある場合、プライベートメソッドをテストする唯一の方法はリフレクションによるものです。
2)新しいコードの場合、次のオプションがあります。
私は、最も単純で最も複雑でないアノテーション方式を好みます。唯一の問題は、私があまり気にしないと思う視認性を高めたことです。常にインターフェイスにコーディングする必要があるため、インターフェイスMyServiceと実装MyServiceImplがある場合、対応するテストクラスであるMyServiceTest(テストインターフェイスメソッド)とMyServiceImplTest(テストプライベートメソッド)を使用できます。とにかく、すべてのクライアントはインターフェイスを使用する必要があります。そのため、プライベートメソッドの可視性が向上しても、実際には問題になりません。
Visual Studio 2008からプライベートメソッドのテストメソッドを生成できます。プライベートメソッドの単体テストを作成すると、テスト参照フォルダーがテストプロジェクトに追加され、アクセサーがそのフォルダーに追加されます。アクセサは、単体テストメソッドのロジックでも参照されます。このアクセサーを使用すると、ユニットテストで、テストするコードのプライベートメソッドを呼び出すことができます。詳細については見てください
また、InternalsVisibleToAtrributeには、アセンブリに厳密な名前を付けるという要件があるため、これまでその要件がなかったソリューションで作業している場合は、それ自体に一連の問題が発生します。アクセサーを使用してプライベートメソッドをテストします。この例については、この質問を参照してください。
InternalsVisibleToAttribute
ないない 必要あなたのアセンブリが強く命名されていること。現在、そうではないプロジェクトで使用しています。
pre-historic
インターネット時代の観点から、プライベートメソッドのユニットテストは簡単で簡単です、必要なときにVisual Studioが必要なアクセサクラスを生成し、テストロジックにスニペットを事前に入力すると、単純な機能テストに必要なものに近くなります。たとえばを参照してください。msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx