web-dev-qa-db-ja.com

Entity Framework 6を​​使った人々の単体テストはどのようなものですか。

私は単体テストとTDD一般から始めたばかりです。私は前に手を出したが、今私はそれを私のワークフローに追加し、より良いソフトウェアを書くことにした。

私は昨日、このような種類のものを含めるように質問しましたが、それはそれだけで問題のようです。私は、コントローラーからビジネスロジックを抽象化し、EF6を使用して特定のモデルとデータのやり取りにマップするために使用するサービスクラスの実装に着手しました。

問題は、リポジトリにEFを抽象化したくないため(特定のクエリなどのためにサービス外で利用できるようになる予定で)、自分のサービスをテストしたいため(EFコンテキストが使用される) 。

ここで私は問題だと思います、これをすることへのポイントがありますか?もしそうなら、Linqの違いのために単体テストの話題についてのIQueryableと Ladislav Mrnka による漏れやすい抽象化に照らして、どうやってそれを野生の人がやっているのでしょうか。特定のデータベースに割り当てられているメモリ内実装で作業する場合のプロバイダ。

テストしたいコードは非常に単純に思えます。 (これは私がしていることを試して理解するための単なるダミーコードです。TDDを使用して作成を推進したいです)

コンテキスト

public interface IContext
{
    IDbSet<Product> Products { get; set; }
    IDbSet<Category> Categories { get; set; }
    int SaveChanges();
}

public class DataContext : DbContext, IContext
{
    public IDbSet<Product> Products { get; set; }
    public IDbSet<Category> Categories { get; set; }

    public DataContext(string connectionString)
                : base(connectionString)
    {

    }
}

サービス

public class ProductService : IProductService
{
    private IContext _context;

    public ProductService(IContext dbContext)
    {
        _context = dbContext;
    }

    public IEnumerable<Product> GetAll()
    {
        var query = from p in _context.Products
                    select p;

        return query;
    }
}

現在私はいくつかのことをすることを考えています。

  1. このアプローチのようなEFコンテキストのモック- 単体テスト時のモックEF またはmoqのようなインターフェース上でのモックフレームワークの直接使用 - 単体テストに合格するが必ずしもエンドツーエンドで動作するとは限らないという苦痛統合テストでそれらをバックアップしますか?
  2. たぶん Effort のようなものを使ってEFを偽造する - 私はそれを使ったことがないし、他の誰かがそれを実際に使っているかどうかわからないのですが。
  3. EFを直接呼び出すサービスメソッド(getAllなど)は単体テストではなく、統合テストだけで済みます。

Repoがなくて成功している人はいませんか。

158
Modika

これは私が非常に興味を持っているトピックです。EFやNHibernateのような技術をテストすべきではないと言う多くの純粋主義者がいます。それらは正しいです、それらはすでに非常に厳格にテストされています、そして前の答えが述べたように、あなたが所有していないものをテストするために膨大な時間を費やすことはしばしば無意味です。

しかし、あなたはその下にデータベースを所有しています!これが私の意見ではこのアプローチが破綻するところです、EF/NHがあることをテストする必要はありません。正しく仕事をしている。マッピングや実装がデータベースと連携していることをテストする必要があります。私の意見では、これはあなたがテストできるシステムの最も重要な部分の1つです。

厳密には、単体テストの領域から統合テストに移行しつつありますが、原則は変わりません。

あなたがしなければならない最初のことはあなたのBLLがEFとSQLから独立してテストされることができるようにあなたのDALをモックできることです。 これらはあなたの単体テストです。次に統合テストを設計する必要がありますあなたのDALを証明するために、私の意見では、これらはすべて同じくらい重要です。

考慮すべきことがいくつかあります。

  1. データベースは各テストで既知の状態になっている必要があります。ほとんどのシステムはこのためにバックアップを使うかスクリプトを作成します。
  2. 各テストは繰り返し可能でなければなりません
  3. 各テストはアトミックでなければなりません

データベースを設定するには、主に2つの方法があります。最初の方法は、UnitTestのDB作成スクリプトを実行することです。これにより、単体テストデータベースが各テストの開始時に常に同じ状態になります(これをリセットするか、トランザクションで各テストを実行してこれを確実にすることができます)。

あなたの他のオプションは私がしていることです、それぞれの個々のテストのために特定のセットアップを実行します。私はこれが2つの主な理由のための最善のアプローチであると思います:

  • あなたのデータベースはより単純です、あなたはそれぞれのテストのために全体のスキーマを必要としません
  • それぞれのテストはより安全です、あなたがあなたの作成スクリプトで1つの値を変更したとしても、それは他の多くのテストを無効にしません。

残念ながら、ここでの妥協はスピードです。これらすべてのセットアップ/破棄スクリプトを実行するには、これらすべてのテストの実行に時間がかかります。

最後に、ORMをテストするためにこのような大量のSQLを記述するのは非常に難しい作業です。これは私が非常に厄介なアプローチをとる場所です(ここの純粋主義者は私とは同意しないでしょう)。私は自分のテストを作成するために私のORMを使います!私のシステムでは、DALテストごとに別々のスクリプトを用意するのではなく、オブジェクトを作成し、それらをコンテキストに関連付けて保存するテスト設定フェーズがあります。それから私はテストを実行します。

これは理想的なソリューションからは程遠いものですが、実際には(特に数千のテストがある場合は)管理が簡単であることがわかります。それ以外の場合は、大量のスクリプトを作成しています。純度よりも実用性.

私は数年(数ヶ月/日)の間この答えを振り返り、私のやり方が変わったので私自身と意見が一致しないでしょう - しかしこれは私の現在のやり方です。

これまでに述べたことすべてを試してまとめると、これは私の典型的なDB統合テストです。

[Test]
public void LoadUser()
{
  this.RunTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    return user.UserID;
  }, id => // the ID of the entity we need to load
  {
     var user = LoadMyUser(id); // load the entity
     Assert.AreEqual("Mr", user.Title); // test your properties
     Assert.AreEqual("Joe", user.Firstname);
     Assert.AreEqual("Bloggs", user.Lastname);
  }
}

ここで気をつけるべき重要なことは2つのループのセッションが完全に独立しているということです。 RunTestの実装では、コンテキストがコミットされ破棄されていることを確認する必要があります。また、データはデータベースからのみ取得できます。

編集13/10/2014

私はおそらく今後数カ月でこのモデルを修正すると言っていました。私が上で提唱したアプローチを大いに支持している間、私は私のテストメカニズムをわずかに更新しました。私は今、TestSetupとTestTearDownでエンティティを作成する傾向があります。

[SetUp]
public void Setup()
{
  this.SetupTest(session => // the NH/EF session to attach the objects to
  {
    var user = new UserAccount("Mr", "Joe", "Bloggs");
    session.Save(user);
    this.UserID =  user.UserID;
  });
}

[TearDown]
public void TearDown()
{
   this.TearDownDatabase();
}

その後、各プロパティを個別にテストします

[Test]
public void TestTitle()
{
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Mr", user.Title);
}

[Test]
public void TestFirstname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Joe", user.Firstname);
}

[Test]
public void TestLastname()
{
     var user = LoadMyUser(this.UserID);
     Assert.AreEqual("Bloggs", user.Lastname);
}

このアプローチにはいくつかの理由があります。

  • 追加のデータベース呼び出しはありません(セットアップ1回、ティアダウン1回)。
  • テストははるかにきめ細かく、各テストは1つのプロパティを検証します
  • Setupメソッド/ TearDownロジックはTestメソッド自体から削除されています

私はこれがテストクラスをより単純にし、テストをよりきめ細かくすると感じています( シングルアサーションは良いです

2015年5月3日編集

このアプローチに関するもう1つの改訂。クラスレベルの設定はプロパティのロードなどのテストには非常に役立ちますが、異なる設定が必要な場合はあまり役に立ちません。この場合、各ケースに新しいクラスを設定するのはやり過ぎです。

これを手助けするために、私は2つの基本クラスSetupPerTestSingleSetupを持つ傾向があります。これら2つのクラスは必要に応じてフレームワークを公開します。

SingleSetupには、最初の編集で説明したのと非常によく似たメカニズムがあります。例は

public TestProperties : SingleSetup
{
  public int UserID {get;set;}

  public override DoSetup(ISession session)
  {
    var user = new User("Joe", "Bloggs");
    session.Save(user);
    this.UserID = user.UserID;
  }

  [Test]
  public void TestLastname()
  {
     var user = LoadMyUser(this.UserID); // load the entity
     Assert.AreEqual("Bloggs", user.Lastname);
  }

  [Test]
  public void TestFirstname()
  {
       var user = LoadMyUser(this.UserID);
       Assert.AreEqual("Joe", user.Firstname);
  }
}

ただし、正しいエンティティーのみが確実にロードされるようにする参照では、SetupPerTestアプローチを使用できます。

public TestProperties : SetupPerTest
{
   [Test]
   public void EnsureCorrectReferenceIsLoaded()
   {
      int friendID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriend();
         session.Save(user);
         friendID = user.Friends.Single().FriendID;
      } () =>
      {
         var user = GetUser();
         Assert.AreEqual(friendID, user.Friends.Single().FriendID);
      });
   }
   [Test]
   public void EnsureOnlyCorrectFriendsAreLoaded()
   {
      int userID = 0;
      this.RunTest(session =>
      {
         var user = CreateUserWithFriends(2);
         var user2 = CreateUserWithFriends(5);
         session.Save(user);
         session.Save(user2);
         userID = user.UserID;
      } () =>
      {
         var user = GetUser(userID);
         Assert.AreEqual(2, user.Friends.Count());
      });
   }
}

要約すると、どちらのアプローチもテストしようとしているものに応じて機能します。

165
Liath

努力経験フィードバックはこちら

たくさんの記事を読んだ後、私はテストで Effort を使ってきました。テスト中、Contextはインメモリバージョンを返すファクトリによって作られています。テスト以外では、ファクトリはコンテキスト全体を返すものに解決されます。

しかし、私はデータベースのフル機能のモックに対するテストはテストを引き下げる傾向があると感じています。システムの一部をテストするには、一連の依存関係をすべて設定するように注意する必要があります。また、すべてを処理する巨大なオブジェクトが1つしかないという理由だけで、関係のないテストをまとめて整理することにもなりがちです。あなたが注意を払わないならば、あなたはあなた自身が単体テストの代わりに統合テストをやっているのを見つけるかもしれません

私は巨大なDBContextよりももっと抽象的なものに対するテストを好むでしょうが、私は意味のあるテストと裸のテストの間のスイートスポットを見つけることができませんでした。私の不慣れなところまでそれをチョークしてください。

だから私は努力を面白いと思います。走り出す必要がある場合は、すぐに使い始めて結果を得るための良いツールです。しかし、もう少しエレガントで抽象的なものが次のステップであるべきだと私は思います。それが私が次に調査することになるものです。それが次に行く場所を見るためにこの投稿を支持する:)

追加して編集する:ウォームアップに時間がかかるので、約60分を見てください。テスト起動時に5秒テストスイートを非常に効率的にする必要がある場合、これは問題になる可能性があります。


明確にするために編集しました。

私はEffortを使用してWebサービスアプリをテストしました。入ってくる各メッセージMは、Windsorを介してIHandlerOf<M>にルーティングされます。 Castle.Windsorは、コンポーネントの依存関係を解決するIHandlerOf<M>を解決します。これらの依存関係の1つは、ハンドラがファクトリを要求することを可能にするDataContextFactoryです。

私のテストでは、IHandlerOfコンポーネントを直接インスタンス化し、SUTのすべてのサブコンポーネントをモックし、EffortでラップされたDataContextFactoryをハンドラーに処理します。

DBが私のテストに見舞われるので、厳密な意味で私は単体テストをしないということです。しかし、私が上で言ったようにそれは私が走り始めたようにしました、そして私はすぐにアプリケーションのいくつかのポイントをテストすることができました

20
samy

nitコードをテストしたい場合は、テストしたいコード(この場合はサービス)を外部リソース(データベースなど)から分離する必要があります。あなたはおそらくこれをある種の メモリ内EFプロバイダ で行うことができますが、もっと一般的な方法はあなたのEF実装を抽象化することです。ある種のリポジトリパターンで。この分離がなければ、あなたが書くテストはユニットテストではなく統合テストになります。

EFコードのテストに関して - 私は初期化中にデータベースに様々な行を書く私のリポジトリのための自動統合テストを書き、そしてそれらが期待通りに振る舞うことを確かめるために私のリポジトリ実装を呼び出します。それらが正しい順序でソートされていること。

これらのテストはデータベース接続が存在することを前提としており、ターゲットデータベースには最新のスキーマがインストールされているため、これらは単体テストではなく統合テストです。

12
Justin

つまり、Entity Frameworkはデータベース対話の複雑さを抽象化し、直接対話することは依然として密接に結び付いているという事実にもかかわらず、Entity Frameworkが実装であるため、テストが混乱します。

単体テストとは、関数のロジックをテストすることであり、それぞれの潜在的な結果を、外部の依存関係(この場合はデータストア)から分離してテストすることです。そのためには、データストアの動作を制御できる必要があります。たとえば、取得したユーザーが何らかの基準を満たしていない場合に関数がfalseを返すと主張する場合は、[偽装]データストアを常に基準を満たさないユーザーを返すように設定する必要があります。反対の主張についても同様です。

そうは言っても、EFが実装であるという事実を受け入れれば、リポジトリを抽象化するという考えをお勧めします。少し冗長に思える?そうではありません。コードをデータ実装から分離するという問題を解決しているからです。

DDDでは、リポジトリは集約ルートのみを返し、DAOは返しません。そうすれば、リポジトリの利用者はデータの実装について知る必要がなくなり(そうではないはずです)、それをこの問題を解決する方法の例として使用できます。この場合、EFによって生成されるオブジェクトはDAOなので、アプリケーションから隠す必要があります。これはあなたが定義したリポジトリのもう一つの利点です。 EFオブジェクトの代わりにビジネスオブジェクトを戻り型として定義できます。これで、リポジトリーが行うことは、EFへの呼び出しを非表示にし、EF応答をreposシグニチャーで定義されているそのビジネス・オブジェクトにマップすることです。クラスに注入したDbContext依存関係の代わりにそのリポジトリを使用できるようになりました。その結果、コードを単独でテストするために必要なコントロールを提供するためにそのインタフェースを模擬できるようになりました。

それはもう少し手間がかかり、多くの人がそれに目を向けていますが、それは本当の問題を解決します。選択肢となる可能性がある別の回答で言及されているメモリ内プロバイダがあります(それを試したことはありません)。それが存在することが、実践の必要性を証明する証拠です。

それはあなたのコードを隔離しているあなたのマッピングをテストすることについて正接に行くという本当の問題を回避するので、私は完全にトップ答えに反対します。必要に応じてマッピングをテストしてください。ただし、実際の問題についてはここで対処し、実際のコードについて説明してください。

8
Sinaesthetic

私は自分が所有していないコードを単体テストしません。ここで何をテストしていますか?MSFTコンパイラが機能することを確認しますか?

それでも、このコードをテスト可能にするには、データアクセス層をビジネスロジックコードとは別にする必要があります。私がしていることは私のEFのものをすべて取り、対応するインターフェースも持っている(または複数の)DAOまたはDALクラスに入れることです。それから、DAOまたはDALオブジェクトを依存としてインジェクトする(コンストラクタインジェクションが望ましい)インターフェースとして参照されるサービスを作成します。テストが必要な部分(あなたのコード)は、DAOインターフェースを模擬し、それをあなたのユニットテストの中のあなたのサービスインスタンスに注入することによって簡単にテストすることができます。

//this is testable just inject a mock of IProductDAO during unit testing
public class ProductService : IProductService
{
    private IProductDAO _productDAO;

    public ProductService(IProductDAO productDAO)
    {
        _productDAO = productDAO;
    }

    public List<Product> GetAllProducts()
    {
        return _productDAO.GetAll();
    }

    ...
}

私は、ライブデータアクセスレイヤをユニットテストではなく統合テストの一部と見なすことにします。私は、Hibernateがデータベースに何度アクセスしたかを検証する人たちを見てきましたが、彼らはデータストアの何十億ものレコードを含むプロジェクトに参加していました。

7
Jonathan Henson

私はこれらの考慮事項に到達するためにしばらくの間遭遇しました:

1-私のアプリケーションがデータベースにアクセスする場合、なぜテストはそうすべきではないのですか?データアクセスに問題があるとどうなりますか?テストは事前にそれを知っていて、問題について自分自身に警告しなければなりません。

2-リポジトリパターンはやや困難で時間がかかります。

それで、私はこのアプローチを思いつきました、私は最善だとは思わないが、私の期待を満たしました:

Use TransactionScope in the tests methods to avoid changes in the database.

それをするためにそれは必要です:

1- EntityFrameworkをテストプロジェクトにインストールします。 2- Test Projectのapp.configファイルに接続文字列を入れます。 3-テストプロジェクトのdll System.Transactionsを参照してください。

ユニークな副作用は、トランザクションが中断された場合でも、挿入しようとするとIDシードが増加することです。しかし、テストは開発用データベースに対して行われるので、これは問題ないはずです。

サンプルコード

[TestClass]
public class NameValueTest
{
    [TestMethod]
    public void Edit()
    {
        NameValueController controller = new NameValueController();

        using(var ts = new TransactionScope()) {
            Assert.IsNotNull(controller.Edit(new Models.NameValue()
            {
                NameValueId = 1,
                name1 = "1",
                name2 = "2",
                name3 = "3",
                name4 = "4"
            }));

            //no complete, automatically abort
            //ts.Complete();
        }
    }

    [TestMethod]
    public void Create()
    {
        NameValueController controller = new NameValueController();

        using (var ts = new TransactionScope())
        {
            Assert.IsNotNull(controller.Create(new Models.NameValue()
            {
                name1 = "1",
                name2 = "2",
                name3 = "3",
                name4 = "4"
            }));

            //no complete, automatically abort
            //ts.Complete();
        }
    }
}
7
Marquinho Peli

手短に言えば、私は「いいえ」と言うでしょう。モデルデータを取得する単一の行でサービスメソッドをテストすることは、絞り込む価値がないということです。私の経験では、TDDに不慣れな人は、すべてを絶対にテストしたいと考えています。サードパーティ製のフレームワークにファサードを抽象化してダミーデータを挿入できるようにするために、そのフレームワークAPIのモックを作成することができます。どの程度の単体テストが最良であるかについては、誰もが異なる見方をしています。私は最近もっと実用的になる傾向があり、私のテストが最終製品に本当に価値を追加しているかどうか、そしてどのくらいのコストで自分自身に尋ねます。

4
ComeIn

インメモリエンティティフレームワークデータベースプロバイダであるEffortがあります。私は実際にそれを試したことがない...ハァはちょうどこれが質問で言及されているのを見つけた!

あるいは、メモリ内データベースプロバイダを内蔵したEntityFrameworkCoreに切り替えることもできます。

https://blog.goyello.com/2016/07/14/save-time-mocking-use-your-real-entity-framework-dbcontext-in-unit-tests/

https://github.com/tamasflamich/effort

コンテキストを取得するためにファクトリを使用したので、その使用に近いコンテキストを作成できます。これはVisual Studioでローカルには機能しているようですが、私のTeamCityビルドサーバーでは機能していないようです。まだ理由はわかりません。

return new MyContext(@"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;");
3
andrew pate

コメントされ、簡単に議論されたアプローチを共有したいのですが、私が現在使用している実際の例を示しますユニットテスト EFベースのサービス。

まず、EF Coreのインメモリプロバイダーを使用したいのですが、これはEF 6についてです。さらに、RavenDBのような他のストレージシステムでは、インメモリデータベースプロバイダーを介したテストの支持者でもあります。繰り返しますが、これは、特にEFベースのコードを多くのセレモニーなしでテストするのに役立ちます。

パターンを思いついたときの目標は次のとおりです。

  • チームの他の開発者が理解するのは簡単でなければなりません
  • EFコードを可能な限りのレベルで分離する必要があります
  • 奇妙な多責任インターフェース(「汎用」または「典型的な」リポジトリパターンなど)の作成を伴うことはできません。
  • 単体テストでの構成とセットアップが簡単でなければなりません

EFはまだ実装の詳細であり、「純粋な」単体テストを実行するためにEFを抽象化する必要があると感じてもよいという以前の声明に同意します。また、理想的には、EFコード自体が機能することを確認したいことに同意しますが、これにはサンドボックスデータベース、インメモリプロバイダーなどが含まれます。私のアプローチは両方の問題を解決します-EF依存コードを安全にユニットテストできます- and EFコードを具体的にテストする統合テストを作成します。

私がこれを達成した方法は、単純にEFコードをカプセル化するを専用のQueryクラスとCommandクラスに入れることでした。考え方は単純です。EFコードをクラスにラップし、元々使用していたクラスのインターフェイスに依存するだけです。私が解決する必要があった主な問題は、クラスに多数の依存関係を追加し、テストで多くのコードを設定することを避けることでした。

これが、便利でシンプルなライブラリの出番です。 Mediatr 。単純なインプロセスメッセージングを可能にし、コードを実装するハンドラーから「リクエスト」を分離することでそれを行います。これには、「what」を「how」から切り離すという追加の利点があります。たとえば、EFコードを小さなチャンクにカプセル化することで、アクションを実行するためのリクエストを送信するだけなので、実装を別のプロバイダーまたはまったく異なるメカニズムに置き換えることができます。

依存性注入(フレームワークの有無にかかわらず、好み)を利用して、メディエーターを簡単にモックし、リクエスト/レスポンスメカニズムを制御して、EFコードの単体テストを有効にすることができます。

最初に、テストする必要があるビジネスロジックを持つサービスがあるとします。

public class FeatureService {

  private readonly IMediator _mediator;

  public FeatureService(IMediator mediator) {
    _mediator = mediator;
  }

  public async Task ComplexBusinessLogic() {
    // retrieve relevant objects

    var results = await _mediator.Send(new GetRelevantDbObjectsQuery());
    // normally, this would have looked like...
    // var results = _myDbContext.DbObjects.Where(x => foo).ToList();

    // perform business logic
    // ...    
  }
}

このアプローチの利点を理解し始めましたか?すべてのEF関連コードを記述クラスにカプセル化するだけでなく、この要求を「どのように」処理するかという実装上の懸念を取り除くことで拡張性を可能にします-このクラスは、関連するオブジェクトがEF、MongoDB、またはテキストファイルからのものであるかどうかを気にしません。

次に、MediatRを介したリクエストとハンドラーについて:

public class GetRelevantDbObjectsQuery : IRequest<DbObject[]> {
  // no input needed for this particular request,
  // but you would simply add plain properties here if needed
}

public class GetRelevantDbObjectsEFQueryHandler : IRequestHandler<GetRelevantDbObjectsQuery, DbObject[]> {
  private readonly IDbContext _db;

  public GetRelevantDbObjectsEFQueryHandler(IDbContext db) {
    _db = db;
  }

  public DbObject[] Handle(GetRelevantDbObjectsQuery message) {
    return _db.DbObjects.Where(foo => bar).ToList();
  }
}

ご覧のとおり、抽象化は単純でカプセル化されています。また、完全にテスト可能です統合テストでは、couldこのクラスを個別にテストします-ビジネス上の懸念は混在していませんここに。

それでは、フィーチャサービスの単体テストはどのようなものでしょうか?とても簡単です。この場合、私は Moq を使ってモックを行います(あなたが幸せになるものなら何でも使ってください):

[TestClass]
public class FeatureServiceTests {

  // mock of Mediator to handle request/responses
  private Mock<IMediator> _mediator;

  // subject under test
  private FeatureService _sut;

  [TestInitialize]
  public void Setup() {

    // set up Mediator mock
    _mediator = new Mock<IMediator>(MockBehavior.Strict);

    // inject mock as dependency
    _sut = new FeatureService(_mediator.Object);
  }

  [TestCleanup]
  public void Teardown() {

    // ensure we have called or expected all calls to Mediator
    _mediator.VerifyAll();
  }

  [TestMethod]
  public void ComplexBusinessLogic_Does_What_I_Expect() {
    var dbObjects = new List<DbObject>() {
      // set up any test objects
      new DbObject() { }
    };

    // arrange

    // setup Mediator to return our fake objects when it receives a message to perform our query
    // in practice, I find it better to create an extension method that encapsulates this setup here
    _mediator.Setup(x => x.Send(It.IsAny<GetRelevantDbObjectsQuery>(), default(CancellationToken)).ReturnsAsync(dbObjects.ToArray()).Callback(
    (GetRelevantDbObjectsQuery message, CancellationToken token) => {
       // using Moq Callback functionality, you can make assertions
       // on expected request being passed in
       Assert.IsNotNull(message);
    });

    // act
    _sut.ComplexBusinessLogic();

    // assertions
  }

}

必要なのは単一のセットアップだけであり、特別な設定は必要ありません。非常に単純な単体テストです。 明確にしましょう:これは完全に可能ですwithout Mediatrのようなもの(単にインターフェースを実装して、それをモックするだけです)テスト、たとえばIGetRelevantDbObjectsQuery)が、実際には多くの機能とクエリ/コマンドを含む大規模なコードベースの場合、Mediatrが提供するカプセル化と生得のDIサポートが大好きです。

これらのクラスをどのように編成するのか疑問に思っているなら、それは非常に簡単です:

- MyProject
  - Features
    - MyFeature
      - Queries
      - Commands
      - Services
      - DependencyConfig.cs (Ninject feature modules)

フィーチャスライスによる整理 はポイントの横にありますが、これによりすべての関連/依存コードがまとめられ、簡単に発見可能になります。最も重要なのは、クエリとコマンドを分離することです。 コマンド/クエリの分離 の原則に従います。

これは私の基準をすべて満たしています。儀式が簡単で、理解しやすく、さらに隠れた利点があります。たとえば、変更の保存をどのように処理しますか?これで、ロールインターフェース(IUnitOfWork.SaveChangesAsync())と単一のロールインターフェースへのモック呼び出しを使用してDbコンテキストを単純化できます。または、RequestHandler内でコミット/ロールバックをカプセル化できます。 、メンテナンス可能な限り。たとえば、EFオブジェクトを渡すだけで、それを保存/更新/削除する単一の汎用リクエスト/ハンドラを作成したいと思いましたが、あなたは意図を尋ねて、ハンドラーを別のストレージプロバイダー/実装と交換する場合は、おそらく、意図したことを表す明示的なコマンド/クエリを作成する必要があります。多くの場合、単一のサービスまたは機能には特定の何かが必要になります。必要になる前に汎用のものを作成しないでください。

もちろんこのパターンには注意点があります-単純なpub/subメカニズムでは行き過ぎです。私は実装をEF関連のコードの抽象化のみに限定しましたが、冒険的な開発者はMediatRを使い始めて、すべてをオーバーボードし、すべてをメッセージ化することができました。これはプロセスの問題であり、MediatRの問題ではないため、このパターンの使用方法を認識してください。

EFの単体テスト/モックの具体例が必要でしたが、これは私たちのプロジェクトで成功しているアプローチであり、チームは採用が非常に簡単であることに非常に満足しています。これがお役に立てば幸いです!プログラミングのすべてのものと同様に、複数のアプローチがあり、それはすべてあなたが達成したいものに依存します。私は、シンプルさ、使いやすさ、保守容易性、発見可能性を大切にしています。このソリューションは、これらすべての要求を満たします。

3
kamranicus

私は自分のフィルタをコードの他の部分から切り離して、ここで私のブログで概説するようにそれらをテストするのが好きです http://coding.grax.com/2013/08/testing-custom-linq-filter-operators)。 html

そうは言っても、LINQ式とT-SQLなどの基になるクエリ言語との間の変換により、テストされるフィルタロジックはプログラムの実行時に実行されるフィルタロジックと同じではありません。それでも、これで私はフィルタのロジックを検証することができます。レイヤー間の統合をテストするまでは、発生する変換や、大文字と小文字の区別、ヌル処理などについてはあまり心配しません。

2
Grax