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


291

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]'.

intellisenseによると-そして私はコンパイラーだと-ターゲットはTypeA_Accessor型です。ただし、実行時にはタイプAであるため、リストの追加は失敗します。

このエラーを停止する方法はありますか?または、おそらくより可能性が高い、他の人々が他のアドバイスを持っている(おそらく「プライベートメソッドをテストしない」および「ユニットテストでオブジェクトの状態を操作しない」と予測しています)。


プライベートクラスTypeBのアクセサーが必要です。アクセサーTypeA_Accessorは、TypeAのプライベートおよび保護されたメソッドへのアクセスを提供します。ただし、TypeBはメソッドではありません。クラスです。
ディマ2013

アクセサーは、プライベート/保護されたメソッド、メンバー、プロパティ、およびイベントへのアクセスを提供します。クラス内のプライベート/保護クラスへのアクセスは提供されません。また、プライベート/保護クラス(TypeB)は、クラス(TypeA)を所有するメソッドによってのみ使用されることを目的としています。基本的に、TypeAの外部からプライベートである "myList"にプライベートクラス(TypeB)を追加しようとしています。アクセサーを使用しているため、myListにアクセスしても問題ありません。ただし、アクセサーを介してTypeBを使用することはできません。考えられる解決策は、TypeBをTypeAの外に移動することです。ただし、デザインが壊れる可能性があります。
ディマ2013

プライベートメソッドをテストする以下で行われるべきであると感じ stackoverflow.com/questions/250692/...
nate_weldon

回答:


274

はい、プライベートメソッドをテストしないでください...単体テストのアイデアは、そのパブリック 'API'によってユニットをテストすることです。

多くのプライベート動作をテストする必要があるとわかった場合は、テストしようとしているクラス内に新しい「クラス」が隠れている可能性が高く、それを抽出して、パブリックインターフェイスでテストします。

一つのアドバイス/思考ツール.....決してプライベートにすべきではないという考えがあります。すべてのメソッドがオブジェクトのパブリックインターフェイス上に存在する必要があることを意味します。プライベートにする必要があると感じた場合、別のオブジェクト上に存在する可能性が高くなります。

このアドバイスは実際にはうまくいきませんが、ほとんどの場合良いアドバイスであり、多くの場合、オブジェクトをより小さなオブジェクトに分解するように強いられます。


385
同意しません。OODでは、プライベートメソッドとプロパティは自分自身を繰り返さないための本質的な方法です(en.wikipedia.org/wiki/Don%27t_repeat_yourself)。ブラックボックスプログラミングとカプセル化の背後にあるアイデアは、技術的な詳細をサブスクライバーから隠すことです。そのため、コードに重要なプライベートメソッドとプロパティを含める必要があります。そして、それが重要でない場合は、テストする必要があります。
AxD

8
組み込みではなく、一部のオブジェクト指向言語にはプライベートメソッドがありません。プライベートプロパティには、テスト可能なパブリックインターフェイスを持つオブジェクトを含めることができます。
キースニコラス

7
このアドバイスの要点は、オブジェクトが1つのことを実行し、DRYである場合、プライベートメソッドを使用する理由はほとんどありません。多くの場合、プライベートメソッドは、オブジェクトが実際に責任を負わないことを行いますが、非常に便利です。それは、自明ではない場合、SRPに違反している可能性があるため、通常は別のオブジェクトです。
キース・ニコラス・

28
違う。コードの重複を避けるために、プライベートメソッドを使用することもできます。または検証のため。または、公の世界が知らない他の多くの目的のために。
ジョルジ

37
恐ろしく設計されたOOコードベースにダンプされ、「徐々に改善する」ように求められたとき、私がプライベートメソッドの最初のテストを行うことができなかったことがわかったのはがっかりしました。ええ、おそらく教科書ではこれらの方法はここにはありませんが、現実の世界では製品の要件を持つユーザーがいます。プロジェクトでいくつかのテストを構築せずに大規模な「Look at ma clean code」リファクタリングを実行することはできません。練習しているプログラマーに、素朴には良さそうに見えるが、実際の厄介なたわごとを考慮に入れない道を強制する別の例のように感じます。
dune.rocks 2017

666

PrivateObjectクラスを使用できます

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

25
これが正解です。MicrosoftがPrivateObjectを追加したためです。
Zoey 2014年

4
良い答えですが、PrivateMethodは「private」ではなく「protected」にする必要があることに注意してください。
HerbalMart 14年

23
@HerbalMart:おそらく私はあなたを誤解しているかもしれませんが、PrivateObjectが保護されたメンバーにのみアクセスでき、プライベートメンバーにはアクセスできないと示唆している場合、それは間違いです。
kmote 2014年

17
@JeffPearce静的メソッドの場合は、「PrivateType pt = new PrivateType(typeof(MyClass));」を使用して、プライベートオブジェクトでInvokeを呼び出すのと同じように、ptオブジェクトでInvokeStaticを呼び出すことができます。
スティーブヒバート2017年

12
場合には誰もがMSTest.TestFramework v1.2.1デベロッパーのと思っていた- PrivateObjectとPrivateTypeクラスは、.NETのコア2.0をターゲットプロジェクトのために利用できない-このためのgithubの問題があります:github.com/Microsoft/testfx/issues/366
シイタケ

98

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

同じことがこの議論にも当てはまります。

ここに画像の説明を入力してください

それはすべて、ユニットであると考えるものに依存します。UNITがクラスであると考える場合は、パブリックメソッドのみを実行します。UNITがプライベートメソッドにヒットするコード行であると考える場合、罪悪感を感じることはありません。

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


55

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

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

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

私は上手く理解できていない気がします。InternalsVisibleToAttributeは、「内部」としてマークされているメソッドと属性にアクセスできるようにしますが、私のフィールドとメソッドは「プライベート」です。プライベートから内部に変更することを提案していますか?誤解していると思います。
junichiro

2
はい、それは私が提案していることです。少し「ハッキー」ですが、少なくとも「公開」されていません。
ジェフ

27
これは「プライベートメソッドをテストしないでください」と書かれていないからといって素晴らしい答えですが、はい、かなり「ハッキー」です。解決策があったらいいのに。IMO「プライベートメソッドをテストするべきではない」と言うのは悪いことです。私がそれ見ると、「プライベートメソッドは正しくないはずです」と同じです。
MasterMastic 2013

5
また、私は、プライベートメソッドは単体テストでテストすべきではないと主張する人々にも混乱しました。パブリックAPIが出力ですが、間違った実装でも正しい出力が得られる場合があります。または、実装がいくつかの悪い副作用を引き起こしました。たとえば、不要なリソースを保持している、オブジェクトを参照しているため、gcなどで収集されないようにしています。ユニットテストではなくプライベートメソッドをカバーできる他のテストを提供しない限り、100%テストされたコードを維持できないと思います。
mr.Pony 2013年

私はMasterMasticに同意します。これは受け入れられる答えになるはずです。
XDS 2018年

27

プライベートメソッドをテストする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);

call methods 静的および非静的
Kiquenet

2
リフレクションに依存するメソッドの欠点は、R#を使用してメソッドの名前を変更すると壊れる傾向があることです。小規模なプロジェクトでは大きな問題ではないかもしれませんが、巨大なコードベースでは、ユニットテストをそのような方法で壊し、回避して迅速に修正しなければならないのは、やや厄介なことになります。この意味で、私のお金はジェフの答えに使われます。
XDS 2018年

2
@XDS残念すぎるnameof()は、クラスの外部からプライベートメソッドの名前を取得するように機能しません。
ガブリエルモリン

12

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

言い換えると...

いいえ、まず最初に。

プライベートメソッド、内部メソッド、保護メソッド、パブリックメソッドのいずれも、テスト可能でなければなりません。ここで紹介したような面倒なことなく、そのようなテストを実装する方法がなければなりません。

どうして?丁度は、これまでに一部の貢献者が行ったアーキテクチャに関する言及があるためです。おそらく、ソフトウェアの原則を単純に繰り返すだけで、誤解が解消されるかもしれません。

この場合、通常の容疑者はOCP、SRP、そしていつものようにKISです。

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

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

private double TranslateMeasurementIntoLinear(double actualMeasurement);

Age of Aquariusまたは今日の呼称を宣言するのは簡単ですが、私のセンサーが1.0から2.0に変化した場合、Translate ...の実装は、簡単に理解できる「再誰にとっても使用可能」、分析などを使用するかなり洗練された計算に使用できるため、他のコードを壊します。どうして?彼らはソフトウェアコーディングの原理そのものを理解していなかったため、KISさえも理解していなかったからです。

このおとぎ話を短くするには、シンプルなものが必要ですプライベートメソッドを方法でテストます-面倒なしに。

まずは、みんなお正月!

次に、建築家のレッスンをリハーサルします。

第三に、「公開」修飾子は宗教であり、解決策ではありません。


5

言及されていないもう1つのオプションは、テストするオブジェクトの子として単体テストクラスを作成することです。NUnitの例:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

これにより、プライベートおよび保護された(継承されたプライベートではない)メソッドの簡単なテストが可能になり、すべてのテストを実際のコードから分離して、テストアセンブリを本番環境にデプロイしないようにすることができます。プライベートメソッドを保護されたメソッドに切り替えることは、多くの継承されたオブジェクトで受け入れ可能であり、行うのは非常に簡単な変更です。

しかしながら...

これは隠されたメソッドをテストする方法の問題を解決する興味深いアプローチですが、これがすべての場合の問題に対する正しい解決策であることを私が主張するかどうかはわかりません。オブジェクトを内部でテストするのは少し奇妙に思われ、このアプローチがあなたに爆破するいくつかのシナリオがあるかもしれないと思います。(たとえば、不変オブジェクトは、いくつかのテストを本当に難しくするかもしれません)。

このアプローチについては触れますが、これは正当な解決策というよりはブレインストーミングの提案のほうが多いと思います。一粒の塩と一緒に服用してください。

編集:私はこれを悪い考えとして明確に説明しているので、人々がこの答えを投票することは本当に陽気だと思います。それは人々が私に同意しているということですか?私はとても混乱しています...


クリエイティブなソリューションですが、ちょっとハッキーです。
しんぞう

3

レガシーコードで効果的に働くという本から:

「プライベートメソッドをテストする必要がある場合は、それをパブリックにする必要があります。パブリックにすると、ほとんどの場合、クラスの処理が多すぎて、修正する必要があります。」

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

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

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

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


2

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

privateメソッドから別のクラスに抽出する必要があるようです。これである必要がありますpublicprivateメソッドでテストするのではなく、テストする必要があります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ます。


0

VS 2005/2008では、プライベートアクセサーを使用してプライベートメンバーをテストできますが、この方法はVSの後のバージョンではなくなりました。


1
2008年から2010年初頭にかけてのよい回答です。次に、PrivateObjectおよびReflectionの代替案を参照してください(上記のいくつかの回答を参照)。VS2010にはアクセッサバグがあり、MSはそれをVS2012で非推奨にしました。VS2010以前(18年以上のビルドツール)にとどまらざるを得ない場合を除き、プライベートアクセサーを使用しないことで時間を節約してください。:-)。
Zephan Schroeder
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.