ASP.NET архитектура MVC3 и Entity Framework Code first


мой предыдущий вопрос заставил меня снова подумать о слоях, репозитории, инъекции зависимостей и архитектурных вещах, подобных этому.

моя архитектура теперь выглядит так:
Сначала я использую код EF, поэтому я просто сделал классы POCO и контекст. Это создает БД и модель.
Уровень выше-это классы бизнес-уровня (провайдеры). Я использую разных провайдеров для каждого домена... как MemberProvider, RoleProvider, TaskProvider и т. д. и я делаю новые экземпляр моего DbContext в каждом из этих поставщиков.
Затем я создаю экземпляры этих поставщиков в своих контроллерах, получаю данные и отправляю их в представления.

моя начальная архитектура включала репозиторий, от которого я избавился, потому что мне сказали, что он просто добавляет сложность, поэтому я не просто использую EF. Я хотел это сделать.. работа с EF непосредственно с контроллеров, но я должен писать тесты, и это было немного усложнить с реальной базой данных. Мне нужно было как - то подделать данные. Поэтому я сделал интерфейс для каждого провайдера и сделал поддельные провайдеры с жестко закодированными данными в списках. И с этим я вернулся к чему-то, где я не уверен, как правильно поступить.

эти вещи начинают слишком быстро усложняться... много подходов и"паттернов"... он создает слишком много шума и бесполезного кода.

есть ли простая и проверяемая архитектура для создания и ASP.NET приложение MVC3 с Entity Framework?

4 53

4 ответа:

если вы хотите использовать 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, вы получите исключение, потому что перегрузка select с индексами не поддерживается в L2E.

Это был простой пример, но то же самое может произойти с использованием методов в запросах и другие распространенные ошибки. Кроме того, это также влияет на такие методы, как добавление, обновление, удаление, обычно выставляемые в репозитории. Если вы не написать макет, который будет точно имитировать поведение контекста EF и ссылочной целостности, вы не будете тестировать свою реализацию.

другая часть истории-это проблемы с ленивой загрузкой, которые также вряд ли могут быть обнаружены с помощью модульных тестов против насмешек.

из-за этого вы также должны ввести интеграцию или сквозные тесты, которые будут работать против реальной базы данных с использованием реального контекста EF ane L2E. Btw. для правильного использования TDD необходимо использовать сквозные тесты. Для записи сквозные испытания внутри ASP.NET MVC вы можете WatiN и возможно SpecFlow для BDD, но это действительно добавит много работы, но у вас будет действительно протестировано ваше приложение. Если вы хотите узнать больше о TDD, я рекомендую книги (единственным недостатком является то, что примеры на Java).

интеграционные тесты имеют смысл, если вы не используете общий репозиторий и скрываете свои запросы в каком-то классе, который не будет выставлять IQueryable но возвращает непосредственно данные.

пример:

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

теперь вы можете просто написать интеграционный тест для проверки реализации этого репозитория, потому что запросы скрыты в этом классе и не подвергаются воздействию верхних слоев. Но этот тип репозитория почему-то считается старой реализацией, используемой с хранимыми процедурами. Вы потеряете много функций ORM с этой реализацией или вам придется сделать много дополнительной работы - например, добавить спецификация шаблон чтобы иметь возможность определить запрос в верхнем слое.

In ASP.NET MVC вы можете частично заменить сквозные тесты на интеграционные тесты на уровне контроллера.

редактировать на основе комментария:

Я не говорю, что вам нужны модульные тесты, интеграционные тесты и сквозные тесты. Я говорю, что создание проверенных приложений требует гораздо больше усилий. Количество и типы необходимых тестов зависят от сложности вашего приложения, ожидаемого будущее приложения, ваши навыки и навыки других членов команды.

небольшие проекты straighforward могут быть созданы вообще без тестов (хорошо, это не очень хорошая идея, но мы все это сделали, и в конце концов это сработало), но как только проект проходит некоторый treshold, вы можете обнаружить, что введение новых функций или поддержание проекта очень сложно, потому что вы никогда не уверены, что он сломает что - то, что уже работало, - это называется регрессией. Лучшая защита от регрессии-это хороший набор автоматизированных тестов.

  • модульные тесты помогут вам протестировать метод. Такие тесты должны в идеале охватывать все пути выполнения в методе. Эти тесты должны быть очень короткими и простыми в написании - к сложной части можно настроить зависимости (насмешки, факты, заглушки).
  • интеграционные тесты помогают тестировать функциональность на нескольких уровнях и обычно на нескольких процессах (приложение, база данных). Вам не нужно иметь их все, это больше о опыта, чтобы выбрать, где они полезны.
  • сквозные тесты-это что-то вроде проверки прецедента / истории пользователя / функции. Они должны охватывать весь поток требования.

не нужно тестировать feture несколько раз - если вы знаете, что функция тестируется в сквозном тесте, вам не нужно писать интеграционный тест для одного и того же кода. Также, если вы знаете, что метод имеет только один путь выполнения, который покрывается интеграционным тестом вы не нужно писать модульный тест для этого. Это работает намного лучше с подходом TDD, где вы начинаете с большого теста (сквозного или интеграции) и углубляетесь в модульные тесты.

в зависимости от вашего подхода к разработке вам не нужно начинать с нескольких типов тестов с самого начала, но вы можете ввести их позже, когда ваше приложение станет более сложным. Исключением является TDD/BDD, где вы должны начать использовать по крайней мере сквозные и модульные тесты, прежде чем даже писать одну строку другой код.

Так вы задаете неправильный вопрос. Вопрос не в том, что проще? Вопрос в том, что поможет вам в конце и какая сложность подходит для вашего приложения? Если вы хотите легко протестировать приложение и бизнес-логику, вы должны обернуть EF-код в некоторые другие классы, которые можно высмеять. Но в то же время вы должны ввести другой тип тестов, чтобы убедиться, что код EF работает.

Я не могу сказать, какой подход будет соответствовать вашей среде / проект / Команда / etc. Но я могу объяснить пример из моего прошлого проекта:

Я работал над проектом около 5-6 месяцев с двумя коллегами. Проект был основан на ASP.NET MVC 2 + jQuery + EFv4 и он был разработан инкрементным и итерационным способом. Было много сложной бизнес-логики и много сложных запросов к базе данных. Мы начали с общих репозиториев и высокого покрытия кода модульными тестами + интеграционными тестами для проверки сопоставления (простые тесты для вставки, удаление, обновление и выбор объекта). Через несколько месяцев мы обнаружили, что наш подход не работает. У нас более 1.200 юнит-тесты, покрытие кода около 60% (что не очень хорошо) и много проблем регрессии. Изменение чего-либо в модели EF может привести к неожиданным проблемам в частях, которые не были затронуты в течение нескольких недель. Мы обнаружили, что нам не хватает интеграционных тестов или сквозных тестов для нашей логики приложения. Такой же вывод был сделан и по параллельной команде работавшей над другой проект и использование интеграционных тестов рассматривались как рекомендации для новых проектов.

добавляет ли использование шаблона репозитория сложность? В вашем сценарии я так не думаю. Это делает TDD проще и ваш код более управляемым. Попробуйте использовать общий шаблон репозитория для большего разделения и более чистого кода.

Если вы хотите узнать больше о TDD и шаблонах проектирования в Entity Framework, взгляните на: http://msdn.microsoft.com/en-us/ff714955.aspx

однако кажется, что вы ищете подход к макету тестового объекта Рамки. Одним из решений было бы использование метода виртуального семени для создания данных при инициализации базы данных. Взгляните на Seed раздел на: http://blogs.msdn.com/b/adonet/archive/2010/09/02/ef-feature-ctp4-dbcontext-and-databases.aspx

также вы можете использовать несколько таких платформ. Самые известные из них я знаю:

чтобы увидеть более полный список .NET mocking Framework, проверьте: 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

как можно подделать мой слой базы данных в модульном тесте?

то, что я делаю, это я использую простой объект ISession и EFSession, witch легко издеваться в моем контроллере, легко получить доступ к Linq и строго типизирован. Вкачать с Ди с помощью Ninject.

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, мне нужно только сделать MongoSession, который реализует ISession...

У меня была такая же проблема, решая общий дизайн моего приложения MVC. этой CodePlex проект Шиджу Варгезе был много помощи. Это делается в ASP.net MVC3, EF CodeFirst, а также использует уровень сервиса и уровень репозитория. Инъекция зависимостей выполняется с использованием Unity. Это просто и очень легко следовать. Он также поддерживается некоторыми 4 очень хорошими сообщениями в блоге. Его стоит проверить. И, не отказывайтесь от хранилища..еще.