Я только начинаю с юнит-тестов и TDD в целом. Раньше я баловался, но теперь я полон решимости добавить его в свой рабочий процесс и написать лучшее программное обеспечение.
Я задал вопрос вчера, что-то вроде этого включало, но, похоже, это вопрос сам по себе. Я сел приступить к реализации класса обслуживания, который буду использовать для абстрагирования бизнес-логики от контроллеров и сопоставления с конкретными моделями и взаимодействиями данных с использованием EF6.
Проблема в том, что я уже заблокировал себя, потому что я не хотел абстрагировать EF в репозитории (он все еще будет доступен за пределами сервисов для конкретных запросов и т. Д.) И хотел бы протестировать мои сервисы (будет использоваться контекст EF) ,
Здесь, наверное, вопрос, есть ли смысл делать это? Если да, то как люди делают это в дикой природе в свете утечек абстракций, вызванных IQueryable, и многих замечательных постов Ладислава Мрнки на тему юнит-тестирования, которые не являются прямыми из-за различий в поставщиках Linq при работе с памятью реализация применительно к конкретной базе данных.
Код, который я хочу проверить, кажется довольно простым. (это просто фиктивный код, чтобы попытаться понять, что я делаю, я хочу управлять созданием, используя TDD)
контекст
public interface IContext
{
IDbSet<Product> Products { get; set; }
IDbSet<Category> Categories { get; set; }
int SaveChanges();
}
public class DataContext : DbContext, IContext
{
public IDbSet<Product> Products { get; set; }
public IDbSet<Category> Categories { get; set; }
public DataContext(string connectionString)
: base(connectionString)
{
}
}
обслуживание
public class ProductService : IProductService
{
private IContext _context;
public ProductService(IContext dbContext)
{
_context = dbContext;
}
public IEnumerable<Product> GetAll()
{
var query = from p in _context.Products
select p;
return query;
}
}
В настоящее время я собираюсь сделать несколько вещей:
- Mocking EF Контекст с чем-то вроде этого подхода - Mocking EF При модульном тестировании или непосредственное использование среды моделирования на интерфейсе, таком как moq, - испытываете боль, которую могут пройти модульные тесты, но не обязательно работают из конца в конец, и резервируйте их с помощью интеграционных тестов?
- Может быть, использовать что-то вроде Effort, чтобы издеваться над EF - я никогда не использовал его и не уверен, что кто-то еще использует его в дикой природе?
- Не беспокойтесь о тестировании чего-либо, что просто вызывает EF - так что, по сути, сервисные методы, которые вызывают EF напрямую (getAll и т. Д.), Не тестируются модулем, а просто проверяются на интеграцию?
Кто-нибудь на самом деле делает это без репо и имеет успех?
Ответы:
Это тема, которая меня очень интересует. Многие пуристы говорят, что не стоит тестировать такие технологии, как EF и NHibernate. Они правы, они уже очень строго проверены, и, как говорилось в предыдущем ответе, часто бессмысленно тратить огромное количество времени на проверку того, чем вы не владеете.
Тем не менее, у вас есть база данных внизу! Вот где этот подход, по моему мнению, нарушается, вам не нужно проверять, что EF / NH выполняют свою работу правильно. Вы должны проверить, что ваши отображения / реализации работают с вашей базой данных. На мой взгляд, это одна из самых важных частей системы, которую вы можете протестировать.
Строго говоря, мы выходим из области модульного тестирования и в интеграционное тестирование, но принципы остаются теми же.
Первое, что вам нужно сделать, это уметь имитировать ваш DAL, чтобы ваш BLL мог быть протестирован независимо от EF и SQL. Это ваши юнит-тесты. Затем вам нужно разработать свои интеграционные тесты, чтобы подтвердить свой DAL, на мой взгляд, они так же важны.
Есть несколько вещей для рассмотрения:
Существует два основных подхода к настройке вашей базы данных, первый - запустить скрипт создания базы данных UnitTest. Это гарантирует, что ваша база данных модульных тестов всегда будет в одном и том же состоянии в начале каждого теста (вы можете сбросить это или запустить каждый тест в транзакции, чтобы убедиться в этом).
Другой вариант - то, что я делаю, - запускаю конкретные настройки для каждого отдельного теста. Я считаю, что это лучший подход по двум основным причинам:
К сожалению, ваш компромисс здесь - скорость. Требуется время, чтобы выполнить все эти тесты, чтобы запустить все эти сценарии установки / демонтажа.
И наконец, написать такой большой объем SQL для тестирования ORM может быть очень тяжелой работой. Здесь я применяю очень противный подход (пуристы здесь не согласятся со мной). Я использую свой ORM для создания своего теста! Вместо того, чтобы иметь отдельный скрипт для каждого теста DAL в моей системе, у меня есть фаза настройки теста, которая создает объекты, присоединяет их к контексту и сохраняет их. Затем я запускаю свой тест.
Это далеко не идеальное решение, однако на практике я считаю, что им ОЧЕНЬ проще управлять (особенно если у вас несколько тысяч тестов), в противном случае вы создаете огромное количество скриптов. Практичность за чистотой.
Я, без сомнения, посмотрю на этот ответ через несколько лет (месяцев / дней) и не соглашусь с собой, поскольку мои подходы изменились - однако это мой нынешний подход.
Чтобы подытожить все, что я сказал выше, это мой типичный тест интеграции БД:
Ключевым моментом, на который следует обратить внимание, является то, что сеансы двух циклов полностью независимы. В вашей реализации RunTest вы должны убедиться, что контекст зафиксирован и уничтожен, и ваши данные могут поступать только из вашей базы данных для второй части.
Изменить 13/10/2014
Я сказал, что, вероятно, пересмотрю эту модель в ближайшие месяцы. Несмотря на то, что я в основном придерживаюсь подхода, который я защищал выше, я немного обновил свой механизм тестирования. Теперь я склонен создавать сущности в TestSetup и TestTearDown.
Затем проверьте каждое свойство в отдельности
Есть несколько причин для такого подхода:
Я чувствую, что это делает тестовый класс проще, а тесты более детальными ( отдельные утверждения хороши )
Изменить 3/5/2015
Еще один пересмотр этого подхода. Хотя настройки уровня класса очень полезны для таких тестов, как загрузка свойств, они менее полезны, когда требуются разные настройки. В этом случае установка нового класса для каждого случая является излишним.
Чтобы помочь с этим, у меня теперь есть два базовых класса
SetupPerTest
иSingleSetup
. Эти два класса предоставляют структуру по мере необходимости.У
SingleSetup
нас очень похожий механизм, как описано в моем первом редактировании. Примером может бытьОднако ссылки, которые гарантируют, что загружены только правильные объекты, могут использовать подход SetupPerTest
В итоге оба подхода работают в зависимости от того, что вы пытаетесь проверить.
источник
Усилие Опыт Обратная связь здесь
После долгих чтений я использовал Effort в своих тестах: во время тестов Context создается фабрикой, которая возвращает версию в памяти, что позволяет мне каждый раз тестировать с чистого листа. Вне тестов фабрика преобразуется в ту, которая возвращает весь контекст.
Однако у меня есть ощущение, что тестирование на основе полнофункционального макета базы данных приводит к затягиванию тестов; вы понимаете, что вам нужно позаботиться о настройке целого ряда зависимостей, чтобы протестировать одну часть системы. Вы также склоняетесь к организации вместе тестов, которые могут быть не связаны, просто потому, что есть только один огромный объект, который обрабатывает все. Если вы не обращаете внимания, вы можете провести интеграционное тестирование вместо юнит-тестирования
Я бы предпочел тестирование против чего-то более абстрактного, а не огромного DBContext, но я не мог найти сладкое место между значимыми тестами и тестами «без костей». Мел до моей неопытности.
Так что я нахожу усилия интересными; если вам нужно быстро начать работу, это хороший инструмент для быстрого начала работы и получения результатов. Однако я думаю, что следующим шагом должно быть что-то более элегантное и абстрактное, и это то, что я собираюсь исследовать дальше. Любим этот пост, чтобы увидеть, куда он пойдет дальше :)
Редактировать, чтобы добавить : Усилие действительно занимает некоторое время, чтобы согреться, так что вы смотрите на ок. 5 секунд при запуске теста. Это может быть проблемой для вас, если вы хотите, чтобы ваш набор тестов был очень эффективным.
Отредактировано для уточнения:
Я использовал Effort для тестирования приложения веб-сервиса. Каждое входящее сообщение M направляется в
IHandlerOf<M>
Windsor через a . Castle.Windsor разрешает,IHandlerOf<M>
который восстанавливает зависимости компонента. Одна из этих зависимостей - это тоDataContextFactory
, что позволяет обработчику запрашивать фабрикуВ своих тестах я непосредственно создаю экземпляр компонента IHandlerOf, макетирую все подкомпоненты SUT и обрабатывает Effort, заключенный
DataContextFactory
в обработчик.Это означает, что я не занимаюсь модульным тестом в строгом смысле, поскольку БД поражен моими тестами. Однако, как я уже сказал выше, это позволило мне взяться за дело, и я смог быстро проверить некоторые моменты в приложении
источник
Если вы хотите выполнить модульное тестирование кода, вам необходимо изолировать свой код, который вы хотите протестировать (в данном случае ваш сервис), от внешних ресурсов (например, баз данных). Вероятно, вы могли бы сделать это с каким-либо провайдером EF в памяти , однако гораздо более распространенным способом является абстрагирование вашей реализации EF, например, с помощью некоторого шаблона репозитория. Без этой изоляции любые написанные вами тесты будут интеграционными, а не модульными.
Что касается тестирования кода EF - я пишу автоматические интеграционные тесты для моих репозиториев, которые записывают различные строки в базу данных во время их инициализации, а затем вызываю мои реализации репозитория, чтобы убедиться, что они ведут себя так, как ожидалось (например, чтобы убедиться, что результаты отфильтрованы правильно, или что они отсортированы в правильном порядке).
Это интеграционные тесты, а не модульные тесты, поскольку тесты основаны на наличии соединения с базой данных и на том, что в целевой базе данных уже установлена самая последняя обновленная схема.
источник
Итак, вот в чем дело, Entity Framework - это реализация, поэтому, несмотря на то, что она абстрагирует сложность взаимодействия с базой данных, непосредственное взаимодействие все еще тесно связано, и поэтому проводить тестирование сложно.
Модульное тестирование - это тестирование логики функции и каждого из ее потенциальных результатов в отрыве от любых внешних зависимостей, которые в данном случае являются хранилищем данных. Для этого вам нужно иметь возможность контролировать поведение хранилища данных. Например, если вы хотите утверждать, что ваша функция возвращает false, если выбранный пользователь не удовлетворяет некоторому набору критериев, тогда ваше хранилище данных [mocked] должно быть настроено так, чтобы всегда возвращать пользователя, который не соответствует критериям, и наоборот наоборот для противоположного утверждения.
С учетом сказанного и принимая тот факт, что EF является реализацией, я, скорее всего, предпочел бы идею абстрагирования хранилища. Кажется немного излишним? Это не так, потому что вы решаете проблему, которая изолирует ваш код от реализации данных.
В DDD репозитории всегда возвращают только совокупные корни, а не DAO. Таким образом, пользователь хранилища никогда не должен знать о реализации данных (как это не должно быть), и мы можем использовать это как пример того, как решить эту проблему. В этом случае объект, сгенерированный EF, является DAO и поэтому должен быть скрыт от вашего приложения. Это еще одно преимущество репозитория, который вы определяете. Вы можете определить бизнес-объект как его тип возврата вместо объекта EF. Теперь репо скрывает вызовы EF и отображает ответ EF на этот бизнес-объект, определенный в подписи репо. Теперь вы можете использовать этот репозиторий вместо зависимости DbContext, которую вы вводите в свои классы, и, следовательно, теперь вы можете смоделировать этот интерфейс, чтобы дать вам контроль, который вам нужен для тестирования вашего кода в изоляции.
Это немного больше работы, и многие суют свой нос в это, но это решает реальную проблему. Есть провайдер в памяти, который был упомянут в другом ответе, который может быть вариантом (я не пробовал), и само его существование свидетельствует о необходимости практики.
Я полностью не согласен с основным ответом, потому что он обходит реальную проблему, которая изолирует ваш код, а затем идет по пути тестирования вашего отображения. Обязательно протестируйте свое отображение, если хотите, но решите настоящую проблему здесь и получите реальное покрытие кода.
источник
Я бы не стал тестировать код, который мне не принадлежит. Что вы тестируете здесь, что компилятор MSFT работает?
Тем не менее, чтобы сделать этот код тестируемым, вы почти ДОЛЖНЫ отделить свой уровень доступа к данным от кода бизнес-логики. Что я делаю, так это беру все свои EF и помещаю их в (или несколько) классов DAO или DAL, которые также имеют соответствующий интерфейс. Затем я пишу свой сервис, в который будет вставлен объект DAO или DAL в виде зависимости (предпочтительно для конструктора), на которую ссылается интерфейс. Теперь ту часть, которую необходимо протестировать (ваш код), можно легко протестировать, смоделировав интерфейс DAO и вставив его в экземпляр службы в модульном тесте.
Я считаю, что живые уровни доступа к данным являются частью интеграционного тестирования, а не модульного тестирования. Я видел, как парни проверяли, сколько поездок в спящий режим базы данных совершал раньше, но они были в проекте, который включал миллиарды записей в своем хранилище данных, и эти дополнительные поездки действительно имели значение.
источник
Я как-то возился где-то, чтобы прийти к следующим соображениям:
1- Если мое приложение обращается к базе данных, почему тест не должен? Что если что-то не так с доступом к данным? Тесты должны знать это заранее и предупредить себя о проблеме.
2- Шаблон репозитория довольно сложен и требует много времени.
Поэтому я пришел к такому подходу, который я не считаю лучшим, но оправдал мои ожидания:
Для этого необходимо:
1- Установите EntityFramework в тестовый проект. 2. Вставьте строку подключения в файл app.config тестового проекта. 3- Ссылка на dll System.Transaction в тестовом проекте.
Уникальный побочный эффект заключается в том, что начальное значение идентификатора будет увеличиваться при попытке вставить, даже когда транзакция будет прервана. Но поскольку тесты проводятся на базе данных разработки, это не должно быть проблемой.
Образец кода:
источник
Короче говоря, я бы сказал, нет, сок не стоит того, чтобы тестировать метод обслуживания с помощью одной строки, которая извлекает данные модели. По моему опыту, люди, которые плохо знакомы с TDD, хотят протестировать абсолютно все. Старый каштан абстрагирования фасада от сторонних фреймворков просто для того, чтобы вы могли создать макет этого API фреймворка, с помощью которого вы убираете / расширяете, так что вы можете вводить фиктивные данные, на мой взгляд, мало что значит. У каждого свое мнение о том, сколько юнит-тестирования лучше. Я склонен быть более прагматичным в наши дни и спрашиваю себя, действительно ли мой тест добавляет ценность конечному продукту и какой ценой.
источник
Я хочу поделиться подходом, который прокомментирован и кратко обсужден, но покажу реальный пример, который я сейчас использую, чтобы помочь юнит-тестированию сервисов на основе EF.
Во-первых, я хотел бы использовать провайдера в памяти от EF Core, но это касается EF 6. Кроме того, для других систем хранения, таких как RavenDB, я также был бы сторонником тестирования через провайдера базы данных в памяти. Опять же - это специально, чтобы помочь тестировать код на основе EF без особых церемоний .
Вот цели, которые я ставил, когда придумывал шаблон:
Я согласен с предыдущими утверждениями о том, что EF все еще является деталью реализации, и вполне нормально чувствовать, что вам необходимо абстрагировать его, чтобы выполнить «чистый» модульный тест. Я также согласен с тем, что в идеале я хотел бы убедиться, что сам код EF работает, но это включает в себя базу данных песочницы, провайдера в памяти и т. Д. Мой подход решает обе проблемы - вы можете безопасно выполнить модульный тест EF-зависимого кода и создать Интеграционные тесты для тестирования вашего кода EF.
Я достиг этого благодаря простой инкапсуляции кода EF в выделенные классы запросов и команд. Идея проста: просто оберните любой код EF в класс и зависите от интерфейса в классах, который первоначально использовал бы его. Основной проблемой, которую мне нужно было решить, было избежать добавления многочисленных зависимостей к классам и настройки большого количества кода в моих тестах.
Здесь полезная и простая библиотека: Mediatr . Он допускает простой обмен сообщениями в процессе и делает это путем отделения «запросов» от обработчиков, которые реализуют код. Это имеет дополнительное преимущество, заключающееся в том, чтобы отделить «что» от «как». Например, инкапсулируя код EF в небольшие порции, это позволяет вам заменить реализации другим провайдером или совершенно другим механизмом, потому что все, что вы делаете, это отправляете запрос на выполнение действия.
Используя внедрение зависимостей (с или без фреймворка - ваши предпочтения), мы можем легко смоделировать посредник и управлять механизмами запроса / ответа, чтобы включить модульное тестирование кода EF.
Во-первых, скажем, у нас есть сервис с бизнес-логикой, который мы должны протестировать:
Вы начинаете видеть преимущества этого подхода? Вы не только явно инкапсулируете весь код, связанный с EF, в дескриптивные классы, но и разрешаете расширяемость, устраняя проблему реализации того, «как» обрабатывается этот запрос - этому классу не важно, приходят ли соответствующие объекты из EF, MongoDB, или текстовый файл.
Теперь для запроса и обработчика через MediatR:
Как видите, абстракция проста и инкапсулирована. Это также абсолютно пригодно для тестирования, потому что в интеграционном тесте вы можете протестировать этот класс индивидуально - здесь нет никаких проблем для бизнеса.
Так как же выглядит юнит-тест нашего сервиса объектов? Это очень просто. В этом случае я использую Moq для насмешек (используйте все, что вас радует):
Вы можете видеть, что все, что нам нужно, - это одна настройка, и нам даже не нужно ничего настраивать - это очень простой модульный тест. Давайте проясним : это вполне возможно обойтись без чего-то вроде Mediatr (вы просто реализовали бы интерфейс и смоделировали бы его, например
IGetRelevantDbObjectsQuery
, для тестов ), но на практике для большой кодовой базы со многими функциями и запросами / командами я люблю инкапсуляцию и Врожденная поддержка DI Mediatr предлагает.Если вам интересно, как я организую эти классы, это довольно просто:
Организация по частям функций не имеет смысла, но это сохраняет весь соответствующий / зависимый код вместе и легко обнаруживаемым. Самое главное, я отделяю запросы от команд - следуя принципу разделения команд / запросов .
Это соответствует всем моим критериям: это непритязательная церемония, ее легко понять и есть дополнительные скрытые преимущества. Например, как вы справляетесь с сохранением изменений? Теперь вы можете упростить ваш Db Context, используя ролевый интерфейс (
IUnitOfWork.SaveChangesAsync()
) и имитировать вызовы к интерфейсу с одной ролью, или вы можете инкапсулировать фиксацию / откат внутри ваших RequestHandlers - однако вы предпочитаете делать это сами, если это поддерживается. Например, у меня возник соблазн создать один общий запрос / обработчик, в котором вы просто передадите объект EF, и он сохранит / обновит / удалит его - но вы должны спросить, каково ваше намерение, и помнить, что если вы хотите поменяйте обработчик с другим поставщиком / реализацией хранилища, вам, вероятно, следует создать явные команды / запросы, которые представляют то, что вы собираетесь делать. Чаще всего одному сервису или функции нужно что-то конкретное - не создавайте общие вещи, пока они вам не понадобятся.Конечно, у этого паттерна есть предостережения - вы можете зайти слишком далеко с помощью простого механизма pub / sub. Я ограничил свою реализацию только абстрагированием кода, связанного с EF, но авантюрные разработчики могли бы начать использовать MediatR для того, чтобы идти за борт и сообщать обо всем - кое-что, что должны поймать хорошие практики проверки кода и рецензирования. Это проблема процесса, а не проблема MediatR, так что просто знайте, как вы используете этот шаблон.
Вы хотели конкретный пример того, как люди тестируют / насмехаются над EF, и этот подход успешно работает для нас в нашем проекте - и команда очень довольна тем, насколько легко его принять. Надеюсь, это поможет! Как и во всех вещах в программировании, существует несколько подходов, и все зависит от того, чего вы хотите достичь. Я ценю простоту, простоту использования, ремонтопригодность и открываемость - и это решение отвечает всем этим требованиям.
источник
Существует Effort, который является провайдером базы данных структуры памяти. Я на самом деле не пробовал ... Хаа только что заметил это было упомянуто в вопросе!
В качестве альтернативы вы можете переключиться на EntityFrameworkCore, который имеет встроенный поставщик базы данных в памяти.
https://blog.goyello.com/2016/07/14/save-time-mocking-use-your-real-entity-framework-dbcontext-in-unit-tests/
https://github.com/tamasflamich/effort
Я использовал фабрику, чтобы получить контекст, чтобы я мог создать контекст, близкий к его использованию. Кажется, это работает локально в visual studio, но не на моем сервере сборки TeamCity, пока не знаю почему.
источник
Мне нравится отделять свои фильтры от других частей кода и тестировать их, как я обрисовал в общих чертах в своем блоге здесь http://coding.grax.com/2013/08/testing-custom-linq-filter-operators.html
При этом тестируемая логика фильтра не идентична логике фильтра, выполняемой при запуске программы из-за преобразования между выражением LINQ и базовым языком запросов, таким как T-SQL. Тем не менее, это позволяет мне проверить логику фильтра. Я не слишком беспокоюсь о переводах, которые происходят, и о таких вещах, как чувствительность к регистру и обработка нуля, пока не протестирую интеграцию между слоями.
источник
Важно проверить, что вы ожидаете от структуры сущности (т.е. подтвердить свои ожидания). Одним из способов сделать это, которое я успешно использовал, является использование moq, как показано в этом примере (чтобы долго копировать в этот ответ):
https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking
Однако будьте осторожны ... Не гарантируется, что контекст SQL возвращает вещи в определенном порядке, если у вас нет соответствующего «OrderBy» в вашем запросе linq, поэтому можно писать вещи, которые проходят при тестировании с использованием списка в памяти ( linq-to-entity), но терпят неудачу в вашей среде uat / live, когда (linq-to-sql) используется.
источник