Модульное тестирование DbContext

Я исследовал некоторую информацию о методах, которые я мог бы использовать для модульного тестирования DbContext. Я хотел бы добавить в контекст некоторые данныев памяти, чтобы мои тесты могли работать с ним. Я использую подход Database-First.

Две статьи, которые я нашел наиболее полезными, были этим и этим. Этот подход основан на создании интерфейса IContext, который будут реализованы как MyContext, так и FakeContext, позволяя имитировать контекст.

Однако я стараюсь избегать использования репозиториев для абстрагирования EF, как указывалось некоторыми людьми, поскольку EF 4.1 уже реализует шаблоны репозитория и единицы работы через DbSet и DbContext, и я действительно хотел бы сохранить все функции, реализованные в EF Команда без необходимости поддерживать их с помощью общего репозитория, как я уже делал в другом проекте (и это было довольно болезненно).

Работа с IContext приведет меня к тому же пути (или не так?).

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

Я делаю что-то не так или это может привести к некоторым проблемам, которых я не ожидаю?

5 ответов

Решение

Задайте себе один вопрос: что вы собираетесь тестировать?

Ты упомянул FakeContext и издевательство над контекстом - зачем использовать оба? Это просто разные способы сделать то же самое - обеспечить только тестирование реализации контекста.

Есть еще одна большая проблема - фальсификация или подделка контекста или набора имеют только один результат: вы больше не тестируете свой реальный код.

Простой пример:

public interface IContext : IDisposable
{
     IDbSet<MyEntity> MyEntities { get; }
}

public class MyEntity
{
    public int Id { get; set; }
    public string Path { get; set; } 
}

public class MyService
{
    private bool MyVerySpecialNetMethod(e)
    {
        return File.Exists(e.Path);
    }

    public IEnumerable<MyEntity> GetMyEntities()
    {
        using (IContext context = CreateContext())
        { 
            return context.MyEntities
                          .Where(e => MyVerySpecialNetMethod(e))
                          .Select(e)
                          .ToList();
        }
    }
}

Теперь представьте, что у вас есть это в вашей SUT (тестируемая система - в случае модульного тестирования это блок = обычно метод). В коде теста вы предоставляете FakeContext а также FakeSet и это будет работать - у вас будет зеленый тест. Теперь в рабочем коде вы предоставите еще один производный DbContext а также DbSet и вы получите исключение во время выполнения.

Зачем? Потому что с помощью FakeContext вы также изменили поставщика LINQ и вместо LINQ to Entities вы запускаете LINQ to Objects, поэтому работает локальный метод.NET, который не может быть преобразован в SQL, а также многие другие функции LINQ, которые недоступны в LINQ to Entities! Есть и другие проблемы, с которыми вы можете столкнуться при модификации данных - ссылочная целостность, каскадное удаление и т. Д. Именно поэтому я считаю, что код, работающий с контекстом / LINQ to Entities, должен быть покрыт интеграционными тестами и выполнен на реальной базе данных.

Я занимаюсь разработкой библиотеки с открытым исходным кодом для решения этой проблемы.

http://effort.codeplex.com/

Маленький тизер:

Вам не нужно добавлять какой-либо шаблонный код, просто вызовите соответствующий API библиотеки, например:

var context = Effort.ObjectContextFactory.CreateTransient<MyContext>();

Сначала это может показаться волшебным, но созданный объект ObjectContext будет взаимодействовать с базой данных в памяти и вообще не будет общаться с исходной реальной базой данных. Термин "временный" относится к жизненному циклу этой базы данных, он живет только при наличии созданного объекта ObjectContext. Одновременно созданные объекты ObjectContext взаимодействуют с выделенными экземплярами базы данных, данные между ними не передаются. Это позволяет легко писать автоматизированные тесты.

Библиотека предоставляет различные функции для настройки создания: обмениваться данными между экземплярами, устанавливать исходные данные базы данных, создавать поддельные базы данных на разных уровнях данных... проверьте сайт проекта для получения дополнительной информации.

Начиная с EF 4.3, вы можете модульно протестировать свой код, введя фальшивку DefaultConnectionFactory перед созданием контекста.

Entity Framework 4.1 близка к тому, чтобы быть смоделированной в тестах, но требует немного дополнительных усилий. Шаблон T4 предоставляет вам производный класс DbContext, который содержит свойства DbSet. Я думаю, что вам нужно смоделировать две вещи - это объекты DbSet, которые возвращают эти свойства, а также свойства и методы, которые вы используете в производном классе DbContext. Оба могут быть достигнуты путем изменения шаблона T4.

Брент МакКендрик показал типы изменений, которые должны быть сделаны в этом посте, но не модификации шаблона T4, которые могут достигнуть этого. Грубо говоря, это:

  1. Преобразуйте свойства DbSet в производном классе DbContext в свойства IDbSet.
  2. Добавьте раздел, который генерирует интерфейс для производного класса DbContext, содержащего свойства IDbSet, и любые другие методы (такие как SaveChanges), которые вам нужно будет смоделировать.
  3. Реализуйте новый интерфейс в производном классе DbContext.

Для тех, кто все еще ищет ответы - я написал простую библиотеку для упрощения насмешек над DbContext. Подробности см. В моем другом ответе на SO на аналогичный вопрос: /questions/1030880/draznyaschij-ef-dbcontext-s-moq/1030892#1030892.

PS - Я согласен с тем, что говорит Ладислав Мрнка, но я думаю, что в некоторых случаях совершенно неизбежно, что вам нужно издеваться над DbSet и запускать юнит-тесты против него. Хотя вы должны помнить, что вам не следует тестировать запросы LINQ, чтобы убедиться, что они возвращают правильные данные. Вот где интеграционные тесты более применимы.

Другие вопросы по тегам