498

私はいくつかのパブリックメソッドとプライベートメソッドを持つクラスライブラリを構築しています。プライベートメソッドをユニットテストできるようにしたいと思います(主に開発中ですが、将来のリファクタリングにも役立つ可能性があります)。

これを行う正しい方法は何ですか?

4

31 に答える 31

356

プライベートメソッドをユニットテストしたい場合は、何かが間違っている可能性があります。単体テストは、(一般的に言えば)クラスのインターフェースをテストすることを意味します。つまり、そのパブリック(および保護された)メソッドを意味します。もちろん、これに対する解決策を「ハック」することはできますが(メソッドを公開するだけでも)、次のことも検討する必要があります。

  1. テストしたいメソッドが本当にテストする価値がある場合は、それを独自のクラスに移動する価値があるかもしれません。
  2. プライベートメソッドを呼び出すパブリックメソッドにさらにテストを追加して、プライベートメソッドの機能をテストします。(コメンテーターが示したように、これらのプライベートメソッドの機能が実際にパブリックインターフェイスの一部である場合にのみこれを行う必要があります。実際にユーザーから隠された機能(単体テスト)を実行する場合、これはおそらく悪いことです)。
于 2008-10-30T15:54:26.683 に答える
127

.netを使用している場合は、InternalsVisibleToAttributeを使用する必要があります。

于 2008-10-30T15:52:37.793 に答える
120

private メソッドをテストするのは役に立たないかもしれません。ただし、テスト メソッドからプライベート メソッドを呼び出すことも時々あります。ほとんどの場合、テスト データ生成のコードの重複を防ぐために...

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[] {/* ... */} );
于 2010-10-29T13:50:23.910 に答える
82

「外部インターフェイスのテストだけに関心があるべきだ」という哲学には同意しません。これは、自動車修理工場は車輪が回転するかどうかを確認するためだけのテストを行うべきだと言っているのと少し似ています。はい、最終的には外部の動作に興味がありますが、私自身のプライベートな内部テストがもう少し具体的で要点が合っているのが好きです。はい、リファクタリングする場合、いくつかのテストを変更する必要があるかもしれませんが、大規模なリファクタリングでない限り、変更する必要があるのはいくつかだけであり、他の (変更されていない) 内部テストがまだ機能しているという事実は、リファクタリングは成功しました。

パブリック インターフェイスのみを使用してすべての内部ケースをカバーしようとすることができます。理論的には、パブリック インターフェイスを使用してすべての内部メソッド (または少なくとも重要なすべてのメソッド) を完全にテストすることは可能ですが、達成するために頭を悩ませる必要がある場合があります。これと、パブリック インターフェイスを介して実行されるテスト ケースと、テスト対象として設計されたソリューションの内部部分との間の接続は、識別が困難または不可能な場合があります。内部機構が適切に動作していることを保証する個々のテストは、リファクタリングに伴うマイナーなテスト変更に十分価値があることを指摘しました - 少なくともそれは私の経験です。リファクタリングごとにテストに大きな変更を加える必要がある場合、これは意味をなさないかもしれませんが、その場合は、設計を完全に再考する必要があります。

于 2010-10-19T06:34:08.920 に答える
52

まれに、プライベート関数をテストしたいことがありましたが、通常は代わりに保護されるように変更し、パブリック ラッパー関数を使用してサブクラスを作成しました。

クラス:

...

protected void APrivateFunction()
{
    ...
}

...

テスト用のサブクラス:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...
于 2008-10-30T16:04:04.777 に答える
22

もっと根本的な質問をする必要があると思うのは、なぜそもそもプライベートメソッドをテストしようとしているのかということです。これは、そのクラスのパブリックインターフェイスを介してプライベートメソッドをテストしようとしているコードの臭いですが、そのメソッドは実装の詳細であるため、理由によりプライベートです。パブリックインターフェイスの動作のみに関心があり、内部でどのように実装されているかには関心がありません。

一般的なリファクタリングを使用してプライベートメソッドの動作をテストする場合は、そのコードを別のクラスに抽出できます(おそらく、パッケージレベルの可視性を備えているため、パブリックAPIの一部ではないことを確認してください)。次に、その動作を個別にテストできます。

リファクタリングの成果は、プライベートメソッドが元のクラスのコラボレーターとなった別個のクラスになったことを意味します。その動作は、独自の単体テストによって十分に理解されるようになります。

その後、元のクラスをテストしようとしたときにその動作をモックできるので、パブリックインターフェイスの組み合わせ爆発とそのすべてのプライベートメソッドの動作をテストするのではなく、そのクラスのパブリックインターフェイスの動作のテストに集中できます。 。

これは車の運転に似ていると思います。車を運転するときはボンネットを上げたまま運転しないので、エンジンが作動していることがわかります。私は車が提供するインターフェース、つまりタコメーターとスピードメーターに頼ってエンジンが作動していることを確認します。アクセルペダルを踏むと実際に車が動くという事実に頼っています。エンジンをテストしたい場合は、それを個別にチェックできます。:D

もちろん、レガシーアプリケーションを使用している場合は、プライベートメソッドを直接テストすることが最後の手段になる可能性がありますが、より良いテストを可能にするためにレガシーコードをリファクタリングすることをお勧めします。マイケル・フェザーズは、まさにこの主題について素晴らしい本を書いています。http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

于 2012-10-24T16:30:01.230 に答える
16

プライベート型、内部、およびプライベート メンバーは、何らかの理由でそうであり、多くの場合、それらを直接いじりたくない場合があります。そして、そうすると、後で壊れる可能性があります。これらのアセンブリを作成した人がプライベート/内部実装をそのまま保持するという保証はないからです。

しかし、コンパイル済みまたはサードパーティのアセンブリのハック/調査を行っているときに、プライベート クラスまたはプライベート コンストラクターまたは内部コンストラクターを使用してクラスを初期化する必要が生じることがあります。または、変更できないプリコンパイル済みのレガシー ライブラリを扱う場合、プライベート メソッドに対していくつかのテストを作成することになります。

このようにして誕生した 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();
于 2010-05-25T07:44:15.870 に答える
13

さて、あなたは2つの方法でプライベートメソッドをユニットテストすることができます

  1. クラスのインスタンスを作成できますPrivateObject構文は次のとおりです

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
    
  2. リフレクションを使用できます。

    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 });
    
于 2012-03-27T07:51:36.880 に答える
10

InternalsVisibleToAttributeメソッドも使用しました。これを達成するために以前のプライベートメソッドを内部に作成することに不快感を感じる場合は、とにかく直接単体テストの対象にすべきではないことにも言及する価値があります。

結局のところ、特定の実装ではなく、クラスの動作をテストしているのです。前者を変更せずに後者を変更でき、テストは引き続き合格するはずです。

于 2008-10-30T15:55:36.773 に答える
9

2 種類のプライベート メソッドがあります。静的プライベート メソッドと非静的プライベート メソッド (インスタンス メソッド)。次の 2 つの記事では、例を使用してプライベート メソッドを単体テストする方法について説明します。

  1. 静的プライベート メソッドの単体テスト
  2. 非静的プライベート メソッドの単体テスト
于 2011-07-22T12:32:05.833 に答える
8

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;
    }
于 2008-10-30T16:10:23.483 に答える
5

CodeProject には、プライベート メソッドのテストの長所と短所について簡単に説明している記事があります。次に、プライベート メソッドにアクセスするためのリフレクション コードをいくつか提供します (Marcus が上記で提供したコードに似ています)。サンプルで見つかった唯一の問題は、コードがオーバーロードされたメソッドを考慮していないことです。

ここで記事を見つけることができます:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

于 2008-11-04T18:35:05.543 に答える
4

それらを宣言してinternalから、を使用しInternalsVisibleToAttributeて、単体テストアセンブリがそれらを表示できるようにします。

于 2008-10-30T15:51:10.590 に答える
4

私は、コンパイラ ディレクティブを使用しない傾向があります。本当に必要な場合に軽減する 1 つの方法は、それらを部分クラスに配置し、製品版を作成するときにビルドでその .cs ファイルを無視することです。

于 2008-10-30T15:59:57.623 に答える
3

まだ誰もこれを言っていないことに驚いていますが、私が採用した解決策は、クラス内に静的メソッドを作成してそれ自体をテストすることです。これにより、テストするパブリックおよびプライベートのすべてにアクセスできます。

さらに、スクリプト言語(Python、Ruby、PHPなどのOO機能を備えた)では、実行時にファイル自体をテストすることができます。変更によって何も壊れていないことを確認するための簡単な方法です。これは明らかに、すべてのクラスをテストするためのスケーラブルなソリューションになります。すべてを実行するだけです。(これは、常にテストを実行するvoid mainを使用して、他の言語でも実行できます)。

于 2011-06-06T09:24:59.727 に答える
3

場合によっては、private 宣言をテストするとよい場合があります。基本的に、コンパイラには、Compile( string outputFileName, params string[] sourceSFileNames ) というパブリック メソッドが 1 つしかありません。それぞれの「隠された」宣言をテストせずにそのようなメソッドをテストするのは難しいことを理解していると思います!

そのため、テストを簡単にするために Visual T#: を作成しました。無料の .NET プログラミング言語です (C# v2.0 互換)。

「.-」演算子を追加しました。「.」のように振る舞うだけです。ただし、テストされたプロジェクトで何も変更せずに、テストから非表示の宣言にアクセスすることもできます。

当社の Web サイトをご覧ください。無料でダウンロードしてください。

于 2010-05-01T17:24:05.783 に答える
3

ここでは、プライベート メソッドをテストする任意のクラスで使用できる明確なコード例を作成したいと思います。

テスト ケース クラスにこれらのメソッドを含めて、指示どおりに使用します。

  /**
   *
   * @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));

元のプライベート関数に表示される順序でパラメーターを発行するだけです

于 2015-07-16T19:34:25.740 に答える
3

面倒なことをせずにプライベートメソッドを実行したい人向け。これは、古き良きリフレクションだけを使用する任意の単体テスト フレームワークで機能します。

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");
于 2015-12-16T18:32:30.460 に答える
2

MbUnit には、Reflector と呼ばれるこのための優れたラッパーがあります。

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

プロパティから値を設定および取得することもできます

dogReflector.GetProperty("Age");

「テストプライベート」に関しては、完璧な世界で..ということに同意します。プライベートユニットテストを行う意味はありません。しかし、現実の世界では、コードをリファクタリングするのではなく、プライベート テストを書きたくなるかもしれません。

于 2009-08-24T07:12:54.437 に答える
2

これは、プライベート メソッドの単体テストに関する優れた記事です。しかし、テスト用に特別に設計されたアプリケーションを作成する (テスト専用のテストを作成するようなものです) か、テストにリフレクションを使用するかのどちらが良いかはわかりません。私たちのほとんどが2番目の方法を選択することは間違いありません。

于 2012-04-11T15:14:21.753 に答える
2
CC -Dprivate=public

「CC」は、私が使用しているシステムのコマンド ライン コンパイラです。-Dfoo=barと同等です#define foo bar。そのため、このコンパイル オプションは、すべての非公開データを実質的に公開データに変更します。

于 2008-10-31T07:44:44.207 に答える
1

次に例を示します。最初にメソッド シグネチャを示します。

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);
}
于 2012-05-17T18:05:35.183 に答える
1

1) レガシー コードがある場合、プライベート メソッドをテストする唯一の方法はリフレクションです。

2) 新しいコードの場合、次のオプションがあります。

  • リフレクションを使用する (複雑に)
  • 同じクラスで単体テストを作成します (テスト コードも含まれているため、製品コードが見苦しくなります)。
  • メソッドをリファクタリングして、ある種の util クラスで公開する
  • @VisibleForTesting アノテーションを使用して非公開を削除する

私は、最も単純で複雑でない注釈方法を好みます。唯一の問題は、可視性が向上したことですが、これは大きな問題ではないと思います。常にインターフェイスにコーディングする必要があるため、インターフェイス MyService と実装 MyServiceImpl がある場合、対応するテスト クラスである MyServiceTest (テスト インターフェイス メソッド) と MyServiceImplTest (テスト プライベート メソッド) を使用できます。いずれにせよ、すべてのクライアントがインターフェイスを使用する必要があるため、ある意味で、プライベート メソッドの可視性が向上したとしても、実際には問題にならないはずです。

于 2015-06-18T15:40:29.023 に答える
1

これを行う方法は、メソッドprotectedを用意し、テスト対象のクラスを継承するテスト フィクスチャを作成することです。このように、あなたはあなたの方法publicを変えていませんが、テストを有効にします.

于 2014-09-22T18:34:04.820 に答える
1

You could also declare it as public or internal (with InternalsVisibleToAttribute) while building in debug-Mode:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

It bloats the code, but it will be private in a release build.

于 2015-09-28T07:36:57.987 に答える
0

また、InternalsVisibleToAtrribute には、アセンブリが厳密な名前の . アクセサーを使用してプライベート メソッドをテストします。この例については、この質問を参照してください。

于 2011-04-12T20:08:57.847 に答える
0

Visual Studio 2008 からプライベート メソッドのテスト メソッドを生成できます。プライベート メソッドの単体テストを作成すると、Test References フォルダーがテスト プロジェクトに追加され、アクセサーがそのフォルダーに追加されます。アクセサは、単体テスト メソッドのロジックでも参照されます。このアクセサーを使用すると、単体テストで、テストしているコード内のプライベート メソッドを呼び出すことができます。詳細については、をご覧ください

http://msdn.microsoft.com/en-us/library/bb385974.aspx

于 2010-05-28T20:39:14.550 に答える