web-dev-qa-db-ja.com

ASP.NET MVC3およびEntity Framework Codeの最初のアーキテクチャ

私の以前の質問 レイヤー、リポジトリ、依存関係の注入、およびこのようなアーキテクチャに関することをもう一度考えさせられました。

私のアーキテクチャは次のようになります。
私は最初にEFコードを使用しているので、POCOクラスとコンテキストを作成しました。これにより、dbとモデルが作成されます。
より高いレベルは、ビジネスレイヤークラス(プロバイダー)です。 MemberProvider、RoleProvider、TaskProviderなど、ドメインごとに異なるプロバイダーを使用しており、これらの各プロバイダーでDbContextの新しいインスタンスを作成しています。
次に、これらのプロバイダーをコントローラーでインスタンス化し、データを取得してビューに送信します。

私の最初のアーキテクチャーにはリポジトリーが含まれていましたが、リポジトリーは複雑さを追加するだけだと言われたので、EFだけを使用しないので、それを取り除きました。コントローラーから直接EFを操作したかったのですが、テストを作成する必要があり、実際のデータベースとは少し複雑でした。私は偽造しなければならなかった-どういうわけかデータを模擬した。そこで、各プロバイダーのインターフェイスを作成し、ハードコードされたデータをリストにして偽のプロバイダーを作成しました。そして、これで私は何かに戻りましたが、正しく進める方法がわかりません。

これらのことは、あまりに複雑すぎて急速に始まります...多くのアプローチと「パターン」...それは、あまりにも多くのノイズと役に立たないコードを作成します。

Entity Frameworkを使用してASP.NET MVC3アプリケーションを作成するためのシンプルでテスト可能なアーキテクチャはありますか?

53
Damb

TDD(またはテストカバレッジが高いその他のテストアプローチ)とEFを併用する場合は、統合テストまたはエンドツーエンドテストを作成する必要があります。ここでの問題は、コンテキストまたはリポジトリのいずれかをモックするどのアプローチでも、アプリケーションではなく、上位層ロジック(これらのモックを使用)をテストできるテストを作成するだけです。

簡単な例:

一般的なリポジトリを定義しましょう:

public interface IGenericRepository<TEntity> 
{
    IQueryable<TEntity> GetQuery();
    ...
}

そして、いくつかのビジネスメソッドを書きましょう:

public IEnumerable<MyEntity> DoSomethingImportant()
{
    var data = MyEntityRepo.GetQuery().Select((e, i) => e);
    ...
}

リポジトリをモックすると、Linq-To-Objectsが使用され、グリーンテストが実行されますが、Linq-To-Entitiesでアプリケーションを実行すると、L2Eではインデックス付きの選択オーバーロードがサポートされないため、例外が発生します。

これは単純な例ですが、クエリでのメソッドの使用やその他の一般的な間違いでも同じことが起こります。さらに、これは通常リポジトリに公開されているAdd、Update、Deleteなどのメソッドにも影響します。 EFコンテキストの動作と参照整合性を正確にシミュレートするモックを作成しないと、実装をテストできません。

ストーリーの別の部分は、モックに対するユニットテストではほとんど検出できないレイジーロードの問題です。

そのため、実際のEFコンテキストane L2Eを使用して実際のデータベースに対して機能する統合テストまたはエンドツーエンドテストも導入する必要があります。ところでTDDを正しく使用するには、エンドツーエンドのテストを使用する必要があります。 ASP.NET MVCでエンドツーエンドのテストを作成するには、BDDに WatiNSpecFlow を使用することもできますが、これにより多くの作業が追加されますが、アプリケーションは実際にテストされました。 TDDについてさらに詳しく知りたい場合は、 この本 をお勧めします(唯一の欠点は、例がJavaにあることです)。

統合リポジトリは、汎用リポジトリを使用せず、IQueryableを公開せずに直接データを返すクラスでクエリを非表示にする場合に意味があります。

例:

public interface IMyEntityRepository
{
    MyEntity GetById(int id);
    MyEntity GetByName(string name); 
}

クエリはこのクラスに隠されており、上位層に公開されていないため、このリポジトリの実装をテストするための統合テストを作成できます。ただし、このタイプのリポジトリは、ストアドプロシージャで使用される古い実装と見なされています。この実装ではORM機能の多くが失われるか、追加の作業を多く行う必要があります。たとえば、上位層でクエリを定義できるようにするには、 指定パターン を追加します。

ASP.NET MVCでは、エンドツーエンドテストをコントローラーレベルの統合テストで部分的に置き換えることができます。

コメントに基づいて編集:

単体テスト、統合テスト、エンドツーエンドテストが必要だとは言いません。テスト済みのアプリケーションを作成するには、さらに多くの労力が必要だと私は言います。必要なテストの量とタイプは、アプリケーションの複雑さ、予想されるアプリケーションの将来、スキル、および他のチームメンバーのスキルによって異なります。

小さな単純なプロジェクトはテストなしで作成できます(まあ、それは良いアイデアではありませんが、すべて実行し、最終的には機能しました)。ただし、プロジェクトがいくつかのしきい値を超えると、新しい機能の導入やプロジェクトの保守が容易になることがわかります。それがすでに機能している何かを壊すかどうか決して確信できないので、それは非常に難しいです-それは回帰と呼ばれます。回帰に対する最善の防御策は、一連の優れた自動テストです。

  • 単体テストはメソッドのテストに役立ちます。そのようなテストは、メソッドのすべての実行パスを理想的にカバーする必要があります。これらのテストは非常に短く、簡単に記述できる必要があります-複雑な部分には依存関係(モック、ファクテス、スタブ)を設定することができます。
  • 統合テストは、複数の層にわたって、通常は複数のプロセス(アプリケーション、データベース)にわたって機能をテストするのに役立ちます。あなたはすべてのためにそれらを持っている必要はありません、それは彼らが役に立つ場所を選択するための経験についてです。
  • エンドツーエンドのテストは、ユースケース/ユーザーストーリー/機能の検証のようなものです。要件のフロー全体をカバーする必要があります。

機能を複数回テストする必要はありません。機能がエンドツーエンドテストでテストされていることがわかっている場合は、同じコードの統合テストを作成する必要はありません。また、メソッドに統合テストでカバーされている実行パスが1つしかないことがわかっている場合は、そのための単体テストを作成する必要はありません。これは、大きなテスト(エンドツーエンドまたは統合)から始めてユニットテストにさらに深く入るTDDアプローチではるかにうまく機能します。

開発アプローチによっては、最初から複数のタイプのテストから始める必要はありませんが、アプリケーションがより複雑になるので、後で導入することができます。例外はTDD/BDDです。この場合、他のコードの1行を書く前に、少なくともエンドツーエンドと単体テストの使用を開始する必要があります。

だからあなたは間違った質問をしています。問題は簡単なものではないですか?問題は、最終的に何が役立つか、そしてどのような複雑さがアプリケーションに適合するかです。アプリケーションとビジネスロジックを簡単に単体テストしたい場合は、モック可能な他のクラスにEFコードをラップする必要があります。しかし同時に、EFコードが機能することを確認するために、他のタイプのテストを導入する必要があります。

私はあなたの環境/プロジェクト/チーム/などにどのアプローチが適合するかをあなたに言うことはできません。しかし、私は私の過去のプロジェクトの例を説明することができます:

私は2人の同僚と約5〜6か月間このプロジェクトに取り組みました。このプロジェクトはASP.NET MVC 2 + jQuery + EFv4に基づいており、段階的かつ反復的な方法で開発されました。多くの複雑なビジネスロジックと複雑なデータベースクエリがありました。私たちは、一般的なリポジトリと、ユニットテスト+マッピングを検証するための統合テスト(エンティティの挿入、削除、更新、および選択のための簡単なテスト)による高いコードカバレッジから始めました。数か月後、私たちのアプローチが機能しないことがわかりました。 1.200以上のユニットテスト、約60%のコードカバレッジ(あまり良くありません)、多くの回帰問題がありました。 EFモデルで何かを変更すると、数週間触れられなかった部品に予期しない問題が発生する可能性があります。アプリケーションロジックの統合テストまたはエンドツーエンドテストがないことがわかりました。同じ結論が別のプロジェクトで作業している並列チームで行われ、統合テストの使用は新しいプロジェクトの推奨事項と見なされました。

93
Ladislav Mrnka

リポジトリパターンを使用すると複雑さが増しますか?あなたのシナリオではそうは思いません。 TDDが簡単になり、コードが管理しやすくなります。より多くの分離とよりクリーンなコードのためにGenericリポジトリパターンを使用してみてください。

Entity FrameworkのTDDとデザインパターンの詳細については、以下をご覧ください。 http://msdn.Microsoft.com/en-us/ff714955.aspx

ただし、Entity Frameworkの模擬テストへのアプローチを探しているようです。 1つの解決策は、仮想シードメソッドを使用してデータベースの初期化に関するデータを生成することです。 Seedセクションをご覧ください: http://blogs.msdn.com/b/adonet/archive/2010/09/ 02/ef-feature-ctp4-dbcontext-and-databases.aspx

また、いくつかのモックフレームワークを使用できます。私が知っている最も有名なものは:

.NETモックフレームワークのより完全なリストを確認するには、以下をチェックしてください: https://stackoverflow.com/questions/37359/what-c​​-mocking-framework-to-use

別のアプローチは、 SQLite のようなインメモリデータベースプロバイダーを使用することです。 Entity Frameworkのインメモリプロバイダーはありますか?

最後に、Entity Frameworkのユニットテストに関する優れたリンクをいくつか示します(一部のリンクはEntity Framework 4.0を参照しています。ただし、アイデアがわかるでしょう)。

http://social.msdn.Microsoft.com/Forums/en/adodotnetentityframework/thread/678b5871-bec5-4640-a024-71bd4d5c77ff

http://mosesofegypt.net/post/Introducing-Entity-Framework-Unit-Testing-with-TypeMock-Isolator.aspx

単体テストでデータベースレイヤーを偽造する方法は何ですか?

13
Kamyar

私がやっていることは、単純なISessionオブジェクトとEFSessionオブジェクトを使用することです。魔女はコントローラーで簡単にモックアップでき、Linqで簡単にアクセスでき、厳密に型指定されています。 Ninjectを使用してDIで注入します。

public interface ISession : IDisposable
    {
        void CommitChanges();
        void Delete<T>(Expression<Func<T, bool>> expression) where T : class, new();
        void Delete<T>(T item) where T : class, new();
        void DeleteAll<T>() where T : class, new();
        T Single<T>(Expression<Func<T, bool>> expression) where T : class, new();
        IQueryable<T> All<T>() where T : class, new();
        void Add<T>(T item) where T : class, new();
        void Add<T>(IEnumerable<T> items) where T : class, new();
        void Update<T>(T item) where T : class, new();
    }

public class EFSession : ISession
    {
        DbContext _context;

        public EFSession(DbContext context)
        {
            _context = context;
        }


        public void CommitChanges()
        {
            _context.SaveChanges();
        }

        public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new()
        {

            var query = All<T>().Where(expression);
            foreach (var item in query)
            {
                Delete(item);
            }
        }

        public void Delete<T>(T item) where T : class, new()
        {
            _context.Set<T>().Remove(item);
        }

        public void DeleteAll<T>() where T : class, new()
        {
            var query = All<T>();
            foreach (var item in query)
            {
                Delete(item);
            }
        }

        public void Dispose()
        {
            _context.Dispose();
        }

        public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class, new()
        {
            return All<T>().FirstOrDefault(expression);
        }

        public IQueryable<T> All<T>() where T : class, new()
        {
            return _context.Set<T>().AsQueryable<T>();
        }

        public void Add<T>(T item) where T : class, new()
        {
            _context.Set<T>().Add(item);
        }

        public void Add<T>(IEnumerable<T> items) where T : class, new()
        {
            foreach (var item in items)
            {
                Add(item);
            }
        }

        /// <summary>
        /// Do not use this since we use EF4, just call CommitChanges() it does not do anything
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item"></param>
        public void Update<T>(T item) where T : class, new()
        {
            //nothing needed here
        }

EF4からMongoDBに切り替えたい場合は、ISessionを実装するMongoSessionを作成するだけです...

2
VinnyG

私のMVCアプリケーションの一般的な設計を決定するときに同じ問題が発生していました。 This Shiju VargheseによるCodePlexプロジェクトは、たくさんの助けになりました。 ASP.net MVC3、EF CodeFirstで行われ、サービスレイヤーとリポジトリレイヤーも利用します。依存性注入はUnityを使用して行われます。シンプルでとても簡単にフォローできます。また、4つの非常に素晴らしいブログ投稿にも支えられています。チェックアウトする価値があります。そして、リポジトリをあきらめないでください。

1
Ben