Модульное тестирование 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, должен быть покрыт интеграционными тестами и выполнен на реальной базе данных.
Я занимаюсь разработкой библиотеки с открытым исходным кодом для решения этой проблемы.
Маленький тизер:
Вам не нужно добавлять какой-либо шаблонный код, просто вызовите соответствующий 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, которые могут достигнуть этого. Грубо говоря, это:
- Преобразуйте свойства DbSet в производном классе DbContext в свойства IDbSet.
- Добавьте раздел, который генерирует интерфейс для производного класса DbContext, содержащего свойства IDbSet, и любые другие методы (такие как SaveChanges), которые вам нужно будет смоделировать.
- Реализуйте новый интерфейс в производном классе DbContext.
Для тех, кто все еще ищет ответы - я написал простую библиотеку для упрощения насмешек над DbContext. Подробности см. В моем другом ответе на SO на аналогичный вопрос: /questions/1030880/draznyaschij-ef-dbcontext-s-moq/1030892#1030892.
PS - Я согласен с тем, что говорит Ладислав Мрнка, но я думаю, что в некоторых случаях совершенно неизбежно, что вам нужно издеваться над DbSet и запускать юнит-тесты против него. Хотя вы должны помнить, что вам не следует тестировать запросы LINQ, чтобы убедиться, что они возвращают правильные данные. Вот где интеграционные тесты более применимы.