Единичное тестирование DbContext
Я изучил некоторую информацию о тех методах, которые я мог бы использовать для unit test DbContext. Я хотел бы добавить некоторые данные в памяти в контекст, чтобы мои тесты могли работать против него. Я использую подход Database-First.
Две статьи, которые я нашел наиболее полезными, были this и this.
Этот подход основан на создании интерфейса IContext, который будут реализовываться как MyContext, так и FakeContext, позволяя Mock контекст.
Однако я стараюсь избегать использования репозиториев для абстрактного EF, поскольку указал несколько человек, поскольку EF 4.1 уже реализует репозиторий и единицу рабочих шаблонов через DbSet и DbContext, и я действительно хотел бы сохранить все функции, реализованные EF Team, не имея необходимости поддерживать их с помощью общего хранилища, как я уже делал в другом проекте (и это было болезненно).
Работа с IContext приведет меня к одному и тому же пути (или не так ли?).
Я думал о создании FakeContext, который наследуется от основного MyContext и, таким образом, использует DbContext под ним для запуска моих тестов без попадания в базу данных.
Я не мог найти подобных реализаций, поэтому я надеюсь, что кто-то может мне помочь в этом.
Я делаю что-то неправильно, или это может привести меня к некоторым проблемам, которые я не ожидаю?
Ответы
Ответ 1
Задайте себе один вопрос: что вы собираетесь тестировать?
Вы упомянули 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 (система под тестированием - в случае unit test это единица = обычно метод). В тестовом коде вы предоставляете FakeContext
и FakeSet
, и он будет работать - у вас будет зеленый тест. Теперь в производственном коде вы предоставите другой полученный DbContext
и DbSet
, и вы получите исключение во время выполнения.
Почему? Поскольку, используя FakeContext
, вы также изменили провайдера LINQ, а вместо LINQ to Entities вы используете LINQ to Objects, поэтому вызываете локальные методы .NET, которые не могут быть преобразованы в SQL, а также многие другие функции LINQ, которые недоступны в LINQ к сущностям! Существуют и другие проблемы, которые вы можете найти с изменением данных: ссылочная целостность, каскадные удаления и т.д. Именно поэтому я считаю, что код, связанный с контекстом /LINQ to Entities, должен быть охвачен интеграционными тестами и выполнен с реальной базой данных.
Ответ 2
Как и в EF 4.3, вы можете unit test ваш код путем ввода поддельного DefaultConnectionFactory
перед созданием контекста.
Ответ 3
Я разрабатываю библиотеку с открытым исходным кодом для решения этой проблемы.
http://effort.codeplex.com
Маленький тизер:
Вам не нужно добавлять какой-либо шаблонный код, просто вызовите соответствующий API библиотеки, например:
var context = Effort.ObjectContextFactory.CreateTransient<MyContext>();
Сначала это может показаться волшебным, но созданный объект ObjectContext будет связываться с в памяти базы данных и вообще не будет разговаривать с исходной реальной базой данных, Термин "переходный" относится к жизненному циклу этой базы данных, он живет только при наличии созданного объекта ObjectContext. Одновременно созданные объекты ObjectContext обмениваются данными с выделенными экземплярами базы данных, данные не передаются по ним. Это позволяет легко писать автоматизированные тесты.
Библиотека предоставляет различные функции для настройки создания: обмениваться данными между экземплярами, устанавливать исходные данные в базе данных, создавать фальшивую базу данных на разных уровнях данных... проверить сайт для получения дополнительной информации.
Ответ 4
Entity Framework 4.1 близок к тому, чтобы быть в курсе тестов, но требует немного дополнительных усилий. Шаблон T4 предоставляет вам производный класс DbContext, который содержит свойства DbSet. Две вещи, которые, как мне кажется, вам нужно высмеять, - это объекты DbSet, которые возвращают эти свойства и их свойства и методы, используемые в производном классе DbContext. Оба могут быть достигнуты путем изменения шаблона T4.
Брент Маккендрик показал типы модификаций, которые должны быть сделаны в этом сообщении, но не модификации шаблона T4, которые могут это сделать. Примерно:
- Преобразование свойств DbSet в производный класс DbContext в свойства IDbSet.
- Добавить раздел, который генерирует интерфейс для производного класса DbContext, содержащего свойства IDbSet и любые другие методы (такие как SaveChanges), которые вам нужно высмеять.
- Реализовать новый интерфейс в производном классе DbContext.
Ответ 5
Для тех, кто все еще ищет ответы - я написал простую библиотеку, чтобы облегчить насмешку над DbContext очень простым способом. Подробнее см. Мой другой ответ на SO по аналогичному вопросу: fooobar.com/info/111916/....
PS Я согласен с тем, что говорит Ladislav Mrnka, однако я думаю, что в некоторых случаях совершенно неизбежно, что вам нужно высмеивать свой DbSet и запускать модульные тесты против него. Хотя вам нужно иметь в виду, что вы не должны тестировать свои запросы LINQ, чтобы проверить, вернули ли они правильные данные. То, что интеграционные тесты более применимы.