5

私の仕事では、アプリによって呼び出される Web サービスを作成しています。私たちは、ドメイン駆動設計を使用したアジャイルな考え方で取り組んでいます。DDD と同様に、ドメイン層とアプリケーション層があります。ただし、これらのレイヤーの単体テストを作成する際に問題が発生しました。これは、ドメイン ロジックを 2 回 (ドメイン単体テストとアプリケーション単体テストで) テストしているように見えるためです。

アプリケーション単体テスト

    [TestMethod]
    public void UserApplicationService_SignOut_ForExistingUserWithBalance_ShouldClearBalanceAndSignOut()
    {
        //Arrange
        long merchantId = 1;
        long userId = 1;

        var transactionId = "001";
        var id = "122";            
        var user = Help.SetId<User>(User.Register(id, new DateTime(2015, 01, 01, 00, 00, 01)), userId);

        _usersDb.Add(user);
        var userBonusBalanceRepository = _testContext.MoqUnitOfWork.MockUnitOfWork.Object.GetUserBonusAccountRepository();

        UserBonusAccount uba = userBonusBalanceRepository.GetForUser(user);
        uba.PayTo(
            new Domain.Core.Money { TotalAmount = 10, BonusAmount = 0 },
            new Domain.Core.Outlet
            {
                BonusPercentage = 50,
                IsLoyalty = true,
                Id = id,
                OutletId = "111"
            },
            transactionId,
            DateTime.Now);
        userBonusBalanceRepository.Update(uba);          

        //Act
        _testContext.UserApplicationService.SignOut(id);

        //Assert
        var firstOrDefault = this._balances.FirstOrDefault(x => x.UserId == user.Id && x.MerchantId == merchantId);
        Assert.IsTrue(firstOrDefault != null && firstOrDefault.Balance == 0);
        Assert.IsNotNull(this._transactions.Where(x => x.Id == transactionId && x.Type == BonusTransactionType.EraseFunds));
    }

ドメイン単体テスト

    [TestMethod]
    public void UserBonusAccount_ClearBalances_shouldClearBalancesForAllMerchants()
    {
        long userId = 1;
        long firstMerchantId = 1;
        long secondMerchantId = 2;
        User user = User.Register("111", new DateTime(2015, 01, 01, 00, 00, 01));
        Shared.Help.SetId(user, userId);
        List<BonusTransaction> transactions = new List<BonusTransaction>();
        List<BonusBalance> balances = new List<BonusBalance>();

        var userBonusAccount = UserBonusAccount.Load(transactions.AsQueryable(), balances.AsQueryable(), user);

        userBonusAccount.PayTo(new Money {TotalAmount = 100, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = firstMerchantId,
                OutletId = "4512345678"
            }, "001", DateTime.Now);

        userBonusAccount.PayTo(new Money {TotalAmount = 200, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = secondMerchantId,
                OutletId = "4512345679"
            }, "002", DateTime.Now);

        userBonusAccount.ClearBalances();

        Assert.IsTrue(userBonusAccount.GetBalanceAt(firstMerchantId) == 0);
        Assert.IsTrue(userBonusAccount.GetBalanceAt(secondMerchantId) == 0);
    }

ご覧のとおり、これらの両方のテストは、ドメインの責任であるユーザー バランスが 0 であるかどうかをチェックします。したがって、問題は、アプリケーション層の単体テストがどのように見えるべきか、そして何をテストすべきかということです。どこかで、単体テストは「フロー制御のアプリケーション サービスとビジネス ルールのドメイン モデル」でテストする必要があると読みました。誰かがさらに詳しく説明し、アプリケーション層の単体テストがどのようにテストされ、どのように見えるべきかの例を挙げてもらえますか?

4

1 に答える 1

10

App Service 単体テスト

アプリ サービスの責任には、入力の検証、セキュリティ、およびトランザクション制御が含まれます。だから、これはあなたがテストすべきものです!

アプリ サービスの単体テストで提供して回答する必要がある質問の例を次に示します。

私のアプリサービスは...

  • ガベージを渡すと正しく動作しますか (たとえば、予想されるエラーを返します)?
  • 管理者だけがアクセスできるようにしますか?
  • 成功した場合にトランザクションを正しくコミットしますか?

これらの側面をどのように正確に実装するかによって、それらをテストする意味がある場合とない場合があります。たとえば、セキュリティは多くの場合、宣言型のスタイルで実装されます (たとえば、C# 属性を使用)。その場合、単体テストを使用してすべてのアプリ サービスのセキュリティ属性をチェックするよりも、コード レビュー アプローチの方が適切であることがわかる場合があります。しかしYMMV。

また、ユニット テストが実際のユニット テストであること、つまり、すべて (特にドメイン オブジェクト) をスタブまたはモックにすることを確認してください。あなたのテストでは、これが事実であることは明らかではありません(以下の補足事項を参照)。

一般的な App Services のテスト戦略

アプリ サービスの単体テストがあることは良いことです。ただし、アプリ サービス レベルでは、長期的には統合テストの方が価値があると思います。したがって、私は通常、アプリ サービスをテストするための次の組み合わせ戦略を提案します。

  1. 良いケースと悪いケースの単体テストを作成します (必要に応じて TDD スタイル)。入力の検証は重要なので、悪いケースをスキップしないでください。
  2. 良いケースの統合テストを作成します。
  3. 必要に応じて、追加の統合テストを作成します。

サイドノート

単体テストにコードの臭いがいくつか含まれています。

たとえば、ユニット テストでは常に SUT (テスト対象のシステム) を直接インスタンス化します。そのように、どの依存関係があり、どれがスタブ化されているか、モック化されているか、または実際の依存関係が使用されているかを正確に把握できます。あなたのテストでは、これはまったく明確ではありません。

また、テスト出力を収集するためのフィールドに依存しているようです(this._balancesたとえば)。これは通常、テスト クラスに含まれるテストが 1 つだけの場合は問題になりませんが、それ以外の場合は問題になる可能性があります。フィールドに依存することで、テスト メソッドの「外部」である状態に依存します。これにより、テスト メソッドを理解するのが難しくなる可能性があります。テスト メソッドを読み通すだけではなく、クラス全体を考慮する必要があるためです。これは、セットアップ メソッドとティアダウン メソッドを使いすぎると発生する問題と同じです。

于 2015-12-28T10:31:06.560 に答える