0

最初に、TDD の初心者であるにも関わらず、TDD の利点を十分に理解しています。モックの使用を検討するのに十分な進歩があり、モックが OOP に適合する場所を理解することになると、実際のレンガの壁にぶつかったように感じます。

この件に関する関連する投稿/記事をできるだけ多く読んだことがありますが ( FowlerMiller )、どのように、またはいつモックするかについてはまだ完全には明確ではありません。

具体例を挙げましょう。私のアプリにはサービス レイヤー クラス (アプリケーション レイヤーと呼ぶ人もいますか?) があり、メソッドは特定のユース ケースに大まかにマッピングされます。これらのクラスは、永続層、ドメイン層、さらには他のサービス クラスと連携する場合があります。私は良い小さな DI 少年であり、依存関係を適切に分解して、テスト目的などでサブベッドできるようにしました。

サンプル サービス クラスは次のようになります。

public class AddDocumentEventService : IAddDocumentEventService
{
    public IDocumentDao DocumentDao
    {
        get { return _documentDao; }
        set { _documentDao = value; }
    }
    public IPatientSnapshotService PatientSnapshotService
    {
        get { return _patientSnapshotService; }
        set { _patientSnapshotService = value; }
    }

    public TransactionResponse AddEvent(EventSection eventSection)
    {
        TransactionResponse response = new TransactionResponse();
        response.Successful = false;

        if (eventSection.IsValid(response.ValidationErrors))
        {

            DocumentDao.SaveNewEvent( eventSection,  docDataID);

            int patientAccountId = DocumentDao.GetPatientAccountIdForDocument(docDataID);
            int patientSnapshotId =PatientSnapshotService.SaveEventSnapshot(patientAccountId, eventSection.EventId);

            if (patientSnapshotId == 0)
            {
                throw new Exception("Unable to save Patient Snapshot!");
            }

            response.Successful = true;
        }
        return response;
    }

}

NMock を使用して、依存関係 (DocumentDao、PatientSnapshotService) を分離してこのメ​​ソッドをテストするプロセスを実行しました。テストの様子はこちら

 [Test]
 public void AddEvent()
    {
        Mockery mocks = new Mockery();
        IAddDocumentEventService service = new AddDocumentEventService();
        IDocumentDao mockDocumentDao = mocks.NewMock<IDocumentDao>();
        IPatientSnapshotService mockPatientSnapshot = mocks.NewMock<IPatientSnapshotService>();

        EventSection eventSection = new EventSection();

        //set up our mock expectations
        Expect.Once.On(mockDocumentDao).Method("GetPatientAccountIdForDocument").WithAnyArguments();
        Expect.Once.On(mockPatientSnapshot).Method("SaveEventSnapshot").WithAnyArguments();
        Expect.Once.On(mockDocumentDao).Method("SaveNewEvent").WithAnyArguments();

        //pass in our mocks as dependencies to the class under test
        ((AddDocumentEventService)service).DocumentDao = mockDocumentDao;
        ((AddDocumentEventService)service).PatientSnapshotService = mockPatientSnapshot;

        //call the method under test
        service.AddEvent(eventSection);

        //verify that all expectations have been met
        mocks.VerifyAllExpectationsHaveBeenMet();
    }

嘲笑へのこの小さな進出についての私の考えは次のとおりです。

  1. このテストは、多くの基本的な OO の原則を破っているように見えます。カプセル化は特にそうです。私のテストは、テスト対象のクラスの特定の実装の詳細 (つまり、メソッド呼び出し) を完全に認識しています。クラスの内部が変更されるたびに、テストの更新に多くの非生産的な時間が費やされているのを目にします。
  2. 現時点では、私のサービス クラスがかなり単純化されているためかもしれませんが、これらのテストがどのような価値をもたらすかはよくわかりません。特定のユースケースが指示するように、コラボレートするオブジェクトが呼び出されることを保証しているということですか? コードの重複は、このようなわずかなメリットに対してとてつもなく高いようです。

私は何が欠けていますか?

4

6 に答える 6

2

マーティン・ファウラーからの非常に良い投稿についておっしゃいました。彼が言及している1つのポイントは、モキストは行動をテストし、ものを分離するのが好きな人であるということです。

古典的なTDDスタイルは、可能であれば実物を使用し、本物を使用するのが面倒な場合はdoubleを使用します。したがって、古典的なTDDerは、実際の倉庫とメールサービスにdoubleを使用します。doubleの種類は実際には重要ではありません。それだけ。

ただし、モックのTDD実践者は、興味深い動作をするオブジェクトには常にモックを使用します。この場合、倉庫とメールサービスの両方に使用されます。「」

この種のものが気に入らない場合は、おそらく古典的なTDDerであり、厄介な場合(メールサービスやクレジットカードへの請求など)にのみモックを使用する必要があります。それ以外の場合は、独自のdoubleを作成します(インメモリデータベースの作成など)。

特に私はモック主義者ですが、特定のメソッドが呼び出されているかどうかはあまり確認していません(値を返さない場合を除く)。いずれにせよ、私はインターフェースをテストしているでしょう。関数が何かを返すとき、私はモックフレームワークを使用してスタブを作成します。

最後に、それはすべて、何をどのようにテストするかによって決まります。それらのメソッドが本当に呼び出されたかどうかを確認することが重要だと思いますか(モックを使用)?呼び出しの前後の状態を確認しますか(偽物を使用)?それが機能していると見なすのに十分なものを選び、それを正確にチェックするためのテストを作成してください!

テストの価値について、私はいくつかの意見を持っています:

  • 短期的には、TDDを使用すると、通常はより良い設計が得られますが、時間がかかる場合があります。
  • 長期的には、後でこのコードを変更して維持することを恐れることはありません(詳細をよく覚えていない場合)。すぐに赤字になり、ほぼ瞬時にフィードバックが表示されます。

ちなみに、テストコードのサイズは本番コードのサイズと同じくらい大きいのが普通です。

于 2009-08-31T18:13:47.180 に答える
1

モックを使用したテストは、コラボレーションするオブジェクト間の関係、つまりオブジェクトが相互に通信する方法を説明するプロトコルを理解するのに役立ちます。この場合、イベントが到着したときにいくつかのことが持続することを知りたいと思います。オブジェクトの外部関係を記述している場合、カプセル化を破ることはありません。DAOおよびサービスタイプはそれらの外部サービスを記述しますが、それらがどのように実装されるかを定義しません。

これはほんの小さな例ですが、コードはOOではなく手続き型のように感じます。あるオブジェクトから単純な値が抽出され、別のオブジェクトに渡される場合がいくつかあります。おそらく、ある種のPatientオブジェクトがイベントを直接処理している必要があります。わかりにくいですが、おそらくテストはこれらの設計上の問題を示唆していますか?

それまでの間、自己宣伝を気にせず、もう1か月待つことができる場合は、 http://www.growing-object-ientified-software.com/

于 2009-10-04T23:07:51.087 に答える
1

カプセル化を破り、テストをコードとより緊密に結合させることは、モックを使用する上で確実に不利になる可能性があります。テストがリファクタリングに対して脆弱になることは望ましくありません。これはあなたが歩かなければならない紙一重です。個人的には、本当に難しい、ぎこちない、または遅い場合を除き、モックの使用を避けています。コードを見て、最初に BDD スタイルを使用します。テスト メソッドはメソッドの特定の動作をテストする必要があり、そのように名前を付ける必要があります (おそらく AddEventShouldSaveASnapshot のようなもの)。第 2 の経験則は、発生するはずだったすべてのメソッド呼び出しをカタログ化するのではなく、予期した動作が発生したことのみを確認することです。

于 2009-08-31T18:35:12.627 に答える
1

このようなテストを書くとき、私は同じ不安を感じています。期待値をコピーして関数本体に貼り付けることで関数を実装すると、特に驚かされます (これは、モックに LeMock を使用する場合に機能します)。

でも、大丈夫です。それは起こります。このテストは、テスト対象のシステムがその依存関係とどのように相互作用するかを文書化して検証するようになりました。これは良いことです。ただし、このテストには他にも問題があります。

  1. 一度に多くのことをテストしています。このテストでは、3 つの依存関係が正しく呼び出されていることを確認します。これらの依存関係のいずれかが変更された場合、このテストは変更する必要があります。各依存関係が適切に処理されていることを確認して、3 つの個別のテストを実行することをお勧めします。テストしていない依存関係のスタブ オブジェクトを渡します (失敗するため、モックではなく)。

  2. 依存関係に渡されるパラメーターの検証がないため、これらのテストは不完全です。

このサンプルでは、​​モッキング ライブラリとして Moq を使用します。このテストは、すべての依存関係の動作を指定するわけではなく、1 つの呼び出しのみをテストします。また、渡されたパラメーターが入力に対して期待されるものであることを確認します。入力のバリエーションは、個別のテストを正当化します。

public void AddEventSavesSnapshot(object eventSnaphot)
{
    Mock<IDocumentDao> mockDocumentDao = new Mock<IDocumentDao>();
    Mock<IPatientSnapshotService> mockPatientSnapshot = new Mock<IPatientSnapshotService>();

    string eventSample = Some.String();
    EventSection eventSection = new EventSection(eventSample);

    mockPatientSnapshot.Setup(r => r.SaveEventSnapshot(eventSample));

    AddDocumentEventService sut = new AddDocumentEventService();
    sut.DocumentDao = mockDocumentDao;
    sut.PatientSnapshotService = mockPatientSnapshot;

    sut.AddEvent(eventSection);

    mockPatientSnapshot.Verify();
}

渡された未使用の依存関係は、AddEvent() がそれらを使用する可能性がある場合にのみ、このテストで必要になることに注意してください。クラスには、このテストに関係のない依存関係がある可能性がありますが、これは表示されていません。

于 2010-01-04T19:56:50.887 に答える
0

これらのタイプのテストには、偽の(メモリ内の)永続性レイヤーがあると便利です。次に、特定の呼び出しが行われたことを確認する代わりに、最終結果(アイテムがリポジトリに存在すること)を確認できます。あなたがモックの使用に取り組んでいることは知っていますが、私はこれがそれを行うのに最適な場所ではないと言っていると思います。

例として、このテストの擬似コードは次のようになります。

Instantiate the fake repositories.
Run your test method.
Check the fake repository to see if the new elements exist in it.

これにより、テストは実装の詳細を認識しなくなります。ただし、これは偽の永続層を維持することを意味するので、考慮しなければならないトレードオフだと思います。

于 2009-08-31T18:24:14.943 に答える
0

コード内の利害関係者を分離することが価値がある場合があります。

カプセル化とは、潜在的な依存関係の数を最小限に抑え、変更が伝播する可能性を最大限に高めることです。これは、静的ソース コードに基づいています。

単体テストは、実行時の動作が意図せず変更されないことを確認するためのものです。これは、静的ソース コードに基づいていません。

これは、ユニット テスターが元のカプセル化されたソース コードではなく、すべてのプライベート アクセサーが自動的にパブリック アクセサーに変更されたソース コードのコピーに対して作業する場合に役立ちます (これは単なる 4 行のシェル スクリプトです)。

これにより、カプセル化が単体テストから明確に分離されます。

残っているのは、単体テストをどれだけ低くするか、つまり、テストするメソッドの数だけです。これは好みの問題です。

カプセル化の詳細については (単体テストについては何もありません)、 http ://www.edmundkirwan.com/encap/overview/paper7.html を参照してください。

于 2009-09-01T17:32:56.187 に答える