web-dev-qa-db-ja.com

C#でのプライベートメソッドの単体テスト

Visual Studioでは、自動生成されたアクセサクラスを介してプライベートメソッドの単体テストが可能です。私はうまくコンパイルできるプライベートメソッドのテストを書きましたが、実行時に失敗します。コードとテストのごくわずかなバージョンは次のとおりです。

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

実行時エラーは次のとおりです。

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

インテリセンスによれば - そしてそれ故に私はコンパイラを推測する - ターゲットはTypeA_Accessor型である。しかし実行時にはTypeA型なので、リストの追加は失敗します。

このエラーを止める方法はありますか?あるいは、おそらくもっと他の人が他にどのようなアドバイスをするでしょうか(私は「プライベートメソッドをテストしない」や「ユニットテストでオブジェクトの状態を操作しない」と予測します)。

237
junichiro

はい、プライベートメソッドをテストしないでください。ユニットテストのアイデアは、その公開された「API」によってユニットをテストすることです。

あなたがたくさんの個人的な振る舞いをテストする必要があると思うなら、おそらくテストしようとしているクラスの中に隠れている新しい「クラス」を持っていてそれを抽出してそれのパブリックインターフェースでテストしてください。

一つのアドバイス/思考ツール.....どんな方法も私的なものにするべきではないという考えがあります。すべてのメソッドは、オブジェクトのパブリックインターフェイス上に存在する必要があります。プライベートにする必要があると感じる場合は、おそらく別のオブジェクト上に存在します。

このアドバイスは実際にはうまく機能しませんが、ほとんどの場合良いアドバイスであり、多くの場合、人々を自分のオブジェクトを小さいオブジェクトに分解するように促します。

247
Keith Nicholas

あなたが使用することができます PrivateObject Class

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
557
Scuttle

「標準的またはベストプラクティスと呼ばれるものは何もありません。おそらくそれらは単に一般的な意見です」。

この議論についても同様です。

enter image description here

それはすべてあなたがユニットだと思うものに依存します、あなたがUNITがクラスであると思うなら、あなたはただパブリックメソッドを打つでしょう。あなたがUNITがプライベートメソッドを打つコードの行であると思うなら、あなたは罪悪感を感じることはありません。

プライベートメソッドを呼び出したい場合は、 "PrivateObject"クラスを使用してinvokeメソッドを呼び出すことができます。あなたは "PrivateObject"の使い方を示すこの徹底的なYouTubeビデオ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ を見ることができます)また、プライベートメソッドのテストが論理的かどうかについても説明します。

80

ここでもう一つ考えているのは、テストを「内部の」クラス/メソッドに拡張して、このテストのホワイトボックス感覚をより多くすることです。アセンブリでInternalsVisibleToAttributeを使用して、これらを別々の単体テストモジュールに公開することができます。

シールクラスと組み合わせて使うと、テストメソッドがunittestアセンブリメソッドからしか見えないようなカプセル化に近づくことができます。シールクラスの保護されたメソッドは事実上プライベートであると考えてください。

[Assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

そしてユニットテスト:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
37
Jeff

プライベートメソッドをテストする1つの方法はリフレクションによるものです。これはNUnitとXUnitにも当てはまります。

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
20
Jack Davidson

えーと...まったく同じ問題でここに来ました:simple、しかし極めて重要なprivateメソッドをテストします。このスレッドを読んだ後、「この単純な金属片にこの単純な穴を開けたい、そして品質が仕様を満たしていることを確認したい」のように見え、それから来ます」まず、そうするための適切なツールはありませんが、庭に重力波観測所を建てることができます。私の記事を読んでください http://foobar.brigther-than -einstein.org/ 最初に、もちろん、いくつかの高度な量子物理学コースに参加する必要があります。次に、大量の超クールなニトロニウムが必要です。そして、もちろん、私の本はAmazonで入手できます」...

言い換えると...

いいえ、まず最初に。

すべてのメソッドは、プライベート、内部、保護、パブリックhasでテスト可能です。ここで説明したような苦労せずに、このようなテストを実装する方法が必要です。

どうして?正確にbecauseこれまでに一部の貢献者が行ったアーキテクチャの言及。おそらく、ソフトウェアの原則を単純に繰り返すことで、いくつかの誤解が解消される可能性があります。

この場合、通常の容疑者は次のとおりです:OCP、SRP、およびいつものように、KIS。

しかし、ちょっと待ってください。すべてを公開するという考え方は、政治的ではなく、一種の態度です。しかし。コードに関しては、オープンソースコミュニティであっても、これは教義ではありません。代わりに、特定のAPIに慣れやすくするために、何かを「隠す」ことをお勧めします。たとえば、市販のデジタル温度計ビルディングブロックのコア計算を非表示にします。実際の測定曲線の背後にある数学を好奇心code盛なコードリーダーに非表示にするのではなく、コードが一部に依存するのを防ぐために、おそらく、以前はプライベートで、内部で保護されていたコードを使用して独自のアイデアを実装することに抵抗できなかった、突然重要なユーザー。

私は何について話しているのですか?

private double TranslateMeasurementIntoLinear(double actualMeasurement);

Age of Aquariusや最近呼ばれているものを宣言するのは簡単ですが、センサーの部分が1.0から2.0になった場合、Translate ...の実装は、簡単に理解でき、「再-分析などを使用する非常に洗練された計算に、すべての人にとって有用であるため、他のコードを壊すことになります。どうして?彼らはソフトウェアコーディングの基本原則を理解していなかったため、KISさえも理解していなかったからです。

このおとぎ話を短くするために、私たちはsimpleを使用してプライベートメソッドをテストする必要があります。

最初:明けましておめでとうございます!

2番目:建築家のレッスンをリハーサルします。

第三に、「パブリック」修飾子は宗教であり、解決策ではありません。

9
CP70

テストしたいプライベートメソッドを含むクラスの周りにラッパークラスを作ることができます。このラッパークラスには、Call_MyPrivateFunctionというパブリックメソッドが含まれており、このメソッドがその基本クラスのプライベート関数を呼び出します。

メソッドのアクセスレベルは[protected]に変更する必要があります。

コード例:

public class Order
{
    public int Quantity { get; set; }

    protected bool OrderIsBig ()
    {
        //This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
        return Quantity > 100;
    }
}

//Use this wrapper class in your unit test.
public class FakeOrder : Order
{

    public bool Call_OrderIsBig()
    {
        //This makes the actual call to the protected method "OrderIsBig"
        return OrderIsBig();
    }
}

単体テストコードは次のようになります。

FakeOrder order = new FakeOrder();
order.Quantity = 200;

bool isBig = order.Call_OrderIsBig();   //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.
6
HerbalMart

TL; DR:プライベートメソッドを別のクラスに抽出し、そのクラスでテストします。 SRP原則(単一責任原則)についてもっと読む

privateメソッドを別のクラスに展開する必要があるようです。これでpublicになります。 privateメソッドをテストするのではなく、この別のクラスのpublicメソッドをテストする必要があります。

次のようなシナリオがあります。

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

_someLogicのロジックをテストする必要があります。しかしClass Aは必要以上の役割を果たすようです(SRPの原則に違反します)。 2つのクラスにリファクタリングするだけです

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

このようにしてsomeLogicをA2でテストすることができます。 A1では、偽のA2を作成し、コンストラクタに注入して、A2がsomeLogicという名前の関数に呼び出されることをテストします。

2

本からレガシーコードで効果的に作業する

「プライベートメソッドをテストする必要がある場合、それを公開する必要があります。公開する場合、ほとんどの場合、クラスがやりすぎであるため、修正する必要があります。」

著者によると、それを修正する方法は、新しいクラスを作成し、publicとしてメソッドを追加することです。

著者はさらに説明します:

「良いデザインはテスト可能であり、テスト可能でないデザインは悪いです。」

したがって、これらの制限内で、唯一の本当の選択肢は、メソッドpublicを現在のクラスまたは新しいクラスに作成することです。

1
Kai Hartmann