web-dev-qa-db-ja.com

プライベート変数値の単体テストとチェック

私はC#、NUnit、およびRhino Mocksを使用して単体テストを書いています。私がテストしているクラスの関連部分は次のとおりです。

public class ClassToBeTested
{
    private IList<object> insertItems = new List<object>();

    public bool OnSave(object entity, object id)
    {
        var auditable = entity as IAuditable;
        if (auditable != null) insertItems.Add(entity);

        return false;            
    }
}

OnSaveの呼び出し後にinsertItemsの値をテストしたい:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to insertItems array            
}

これのベストプラクティスは何ですか? insertItemsをパブリックgetを持つプロパティとして追加するか、ListをClassToBeTestedに挿入することを検討しましたが、テストのためにコードを変更する必要があるかどうかはわかりません。

私はプライベートメソッドのテストとリファクタリングに関する多くの投稿を読んでいますが、これはとてもシンプルなクラスであり、最良の選択肢は何だろうと思いました。

46
TonE

簡単な答えは、ユニットテストから非パブリックメンバーにアクセスしないでください。テストスイートを持つという目的を完全に覆します。なぜなら、それはあなたがその方法を維持したくないかもしれない内部実装の詳細にあなたを閉じ込めるからです。

長い答えは、次に何をすべきかに関係しますか?この場合、実装が現状のままである理由を理解することが重要です(これが、TDDが非常に強力な理由です。テストを使用してspecify期待される動作を行いますが、 TDDを使用していないように感じます)。

あなたの場合、頭に浮かぶ最初の質問は、「IAuditableオブジェクトが内部リストに追加される理由」です。または、別の言い方をすれば、「この実装の予想される外部から見える結果は何ですか?」それらの質問への答えに応じて、それはテストする必要があるものです。

後で監査ログに書き込むためにIAuditableオブジェクトを内部リストに追加する場合(単なる推測)、ログを書き込むメソッドを呼び出して、予想されるデータが書き込まれたことを確認します。

何らかの後の制約に対する証拠を蓄積するためにIAuditableオブジェクトを内部リストに追加する場合は、それをテストしてみてください。

測定可能な理由なしにコードを追加した場合は、再度削除してください:)

重要な部分は、実装の代わりにbehaviorをテストすることが非常に有益であることです。また、より堅牢で保守可能なテスト形式です。

テスト対象のシステム(SUT)を変更して、テストしやすくすることを恐れないでください。ドメインでの追加が意味を成し、オブジェクト指向のベストプラクティスに従う限り、問題はありません- オープン/クローズの原則に従うだけです

90
Mark Seemann

アイテムが追加されたリストをチェックするべきではありません。その場合、リスト上のAddメソッドの単体テストを作成し、yourコードのテストは作成しません。 OnSaveの戻り値を確認してください。テストしたいのはこれだけです。

あなたが本当に追加について心配しているなら、方程式からそれをあざける。

編集:

@TonE:コメントを読んだ後、現在のOnSaveメソッドを変更して、失敗について知らせることができます。キャストが失敗した場合などに例外をスローすることを選択できます。その後、例外を予期するユニットテストとそうでないユニットテストを作成できます。

6
Esteban Araya

「ベストプラクティス」とは、エンティティをリストに格納したことで、オブジェクトの重要性をテストすることです。

言い換えると、クラスを保存した今、クラスについてどのような動作が異なるのか、その動作をテストします。ストレージは実装の詳細です。

そうは言っても、それが常に可能であるとは限りません。

必要に応じて reflection を使用できます。

2
Yishai

間違っていなければ、本当にテストしたいのは、IAuditableにキャストできる場合にのみリストにアイテムを追加するということです。そのため、次のようなメソッド名でいくつかのテストを書くことができます。

  • NotIAuditableIsNotSaved
  • IAuditableInstanceIsSaved
  • IAuditableSubclassInstanceIsSaved

...など。

問題は、ご指摘のとおり、質問内のコードが与えられた場合、間接的にのみ-プライベートinsertItems IList<object>メンバー(リフレクションによって、またはテストのみの目的でプロパティを追加することによって)またはクラスにリストを挿入します。

public class ClassToBeTested
{
    private IList _InsertItems = null;

    public ClassToBeTested(IList insertItems) {
      _InsertItems = insertItems;
    }
}

それから、テストするのは簡単です:

[Test]
public void OnSave_Adds_Object_To_InsertItems_Array()
{
     Setup();

     List<object> testList = new List<object>();
     myClassToBeTested     = new MyClassToBeTested(testList);

     // ... create audiableObject here, etc.
     myClassToBeTested.OnSave(auditableObject, null);

     // Check auditableObject has been added to testList
}

リストがパブリックインターフェイスの重要な部分であると考える理由がない限り、注入は最も前向きで控えめなソリューションです(この場合、プロパティの追加が優れている可能性があります-もちろん、プロパティの注入も完全に合法です)。デフォルトの実装を提供する引数なしのコンストラクター(new List())を保持することもできます。

それは確かに良い習慣です。単純なクラスであることを考えると、少し過剰に設計されているように思われるかもしれませんが、テスト性だけでも価値があります。それに加えて、クラスを使用したい別の場所を見つけた場合、IListの使用に限定されないため、ケーキにアイシングがかかります(後で変更するのに多大な労力がかかるわけではありません) )。

1
Jeff Sternal

リストが内部実装の詳細である場合(およびそうであるように思われる場合)、テストするべきではありません。

良い質問は、アイテムがリストに追加された場合に予想される動作は何ですか?これをトリガーするには別の方法が必要になる場合があります。

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        MyOtherClass other = new MyOtherClass();
        c.Save(other);

        var result = c.Retrieve();
        Assert.IsTrue(result.Contains(other));
    }

この場合、外部から見える正しい動作は、オブジェクトを保存した後、取得されたコレクションに含まれることだと断言しています。

結果として、将来、渡されたオブジェクトが特定の状況で呼び出されるはずである場合、次のようなものになる可能性があります(擬似APIをお許しください):

    public void TestMyClass()
    {
        MyClass c = new MyClass();
        IThing other = GetMock();
        c.Save(other);

        c.DoSomething();
        other.AssertWasCalled(o => o.SomeMethod());
    }

どちらの場合も、内部実装ではなく、クラスの外部から見える動作をテストしています。

1
kyoryu

必要なテストの数は、コードの複雑さ、つまり大体の決定ポイントの数に依存します。アルゴリズムが異なれば、実装の複雑さが異なるため、同じ結果を得ることができます。実装に依存しないテストをどのように作成し、それでも意思決定ポイントを十分にカバーしていることを確認しますか?

さて、統合レベルなど、より大きなテストを設計する場合、いいえ、実装への書き込みやプライベートメソッドのテストはしたくないでしょうが、質問は小さな単体テストの範囲に向けられました。

0
Chris Golledge