Ускорение тестов за счет использования только частичной базы данных в EntityFramework Effort

Пример использования: у нас есть довольно большая база данных (около 200 таблиц), которая используется в большой (устаревшей) системе. Он реализован как подход, ориентированный на базу данных, с одним файлом edmx, определяющим всю базу данных. Мы используем XUnit и Effort для автоматического тестирования. Проблема в том, что эти тесты очень медленные. Для запуска нашего текущего набора тестов требуется около 7-8 минут, хотя покрытие тестами далеко не то, что нам нужно.

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

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

В настоящее время мы настраиваем наше соединение следующим образом:

connection = EntityConnectionFactory.CreateTransient("metadata=res://entities.csdl|res://entities.ssdl|res://entities.msl");

Можем ли мы каким-то образом (например, запустив XML-преобразование во время выполнения) заставить Effort создавать только те структуры данных, которые ему нужны для определенного подмножества таблиц?


person Ropez    schedule 02.11.2019    source источник
comment
Вам действительно нужна база данных для модульного тестирования? А что насмехаться? Проверьте тестовую пирамиду и используйте базу данных для интеграционных тестов, а не для всех модульных тестов.   -  person Julian    schedule 08.11.2019
comment
Да, мы проводим рефакторинг и используем модульные тесты, где можем, но есть два случая, когда было бы очень полезно приложить усилия для ускорения работы. Один, как вы говорите, для так называемых интеграционных тестов. У нас есть большая унаследованная система, и во многих случаях мы не можем легко провести рефакторинг для поддержки модульных тестов, но мы все же хотим реализовать некоторое тестовое покрытие. Другой случай относится к рефакторингу кода, где у нас есть модульные тесты для всей бизнес-логики, которая отделена от уровня БД, нам по-прежнему нужны быстрые тесты для самого нижнего уровня, и не так просто имитировать структуру сущности.   -  person Ropez    schedule 09.11.2019
comment
Затрачивается ли время на создание 200 пустых таблиц или в таблицы также вставляется много данных?   -  person gogognome    schedule 14.11.2019
comment
Избавление от файла edmx может быть лучшей целью рефакторинга. Затем создайте иерархию среди dbcontextes. Затем вы реорганизуете свои тесты, чтобы использовать меньшие части контекста базы данных и оставить бизнес-уровень с контекстом базы данных верхнего уровня. Наличие примесей в С# было бы идеальным для этой ситуации.   -  person Eldar    schedule 14.11.2019
comment
В этой теме есть много обходных путей, некоторые люди разбивают базу данных на отдельные контексты в зависимости от бизнес-логики, например эта тема обсуждает этот подход, но мне интересно, рассматривали ли вы другой подход для модульного тестирования, например, создание API со ссылкой на тот же контекст, который может инициализировать контекст тестовой базы данных только один раз (я полагаю, что это самая трудоемкая часть), когда вы публикуете и загружаете его в первый раз.   -  person Irakli    schedule 28.11.2019


Ответы (2)


Отказ от ответственности: я являюсь владельцем проекта Entity Framework Effort

В нашей библиотеке есть функция, позволяющая создать точку восстановления и откатиться к ней.

Таким образом, используя этот трюк, вы можете использовать CreateRestorePoint() только один раз, когда все таблицы созданы, а затем для каждого теста запускать их с RollbackToRestorePoint. (Есть несколько других способов заставить это работать, но я думаю, вы поняли)

Это, без сомнения, сделает ваш тест НАМНОГО быстрее, так как таблицу не нужно будет создавать каждый раз.

Вот пример:

var conn = Effort.DbConnectionFactory.CreateTransient();

using (var context = new EntityContext(conn))
{
    context.EntitySimples.Add(new EntitySimple { ColumnInt = 1 });
    context.EntitySimples.Add(new EntitySimple { ColumnInt = 2 });
    context.EntitySimples.Add(new EntitySimple { ColumnInt = 3 });
    context.SaveChanges();
}

// Create a RestorePoint that will save all current entities in the "Database"
conn.CreateRestorePoint();


// Make any change
using (var context = new EntityContext(conn))
{
    context.EntitySimples.RemoveRange(context.EntitySimples);
    context.SaveChanges();
}

// Rollback to the restore point to make more tests
conn.RollbackToRestorePoint();
person Jonathan Magnan    schedule 21.11.2019
comment
Я изо всех сил пытался заставить ваше предложение работать в нашем проекте. Если я создам точку восстановления сразу после создания подключения, она запустится, но производительность при этом не улучшится. Тем не менее, я попытался добавить несколько фикстур в базу данных перед созданием точки восстановления. Всякий раз, когда я пытаюсь создать что-то нетривиальное, метод отката выдает эту ошибку, Oops! Возникла ошибка при попытке создать заказ на вставку. - person Ropez; 23.11.2019
comment
@Ropez, если бы вы могли предоставить небольшой проект модульного тестирования с этой проблемой либо в нашем трекере проблем: github.com /zzzprojects/EntityFramework-Effort или напрямую по почте: [email protected], мы будем рады его рассмотреть. - person Jonathan Magnan; 24.11.2019

Разделите модульный тест и интеграционный тест. Для интеграционного теста вы можете использовать базу данных и запускать ее в более высоких средах (чтобы сэкономить время), но в локальных средах вы можете использовать Faker\Bogus и NBuilder для создания массивных данных для модульного теста.

https://dzone.com/articles/using-faker-and-nbuilder-to-generate-massive-data

Другой вариант: вы можете создать файл ресурсов, соответствующий вашим примерам модульного тестирования https://www.danylkoweb.com/Blog/the-fastest-way-to-mock-a-database-for-unit-testing.-B6

Я также хотел бы показать вам производительность InMemoryDB и SqlLite, http://www.mukeshkumar.net/articles/efcore/unit-testing-with-inmemory-provider-and-sqlite-in-memory.-database-in-ef-core

Хотя приведенный выше пример относится к EFCore, в EF6 мы также можем использовать SqlLite https://www.codeproject.com/Tips/1056400/Setting-up-SQLite-and-Entity-Framework-Code-First

Поэтому я рекомендую вам использовать sqllite для сценариев тестирования интеграции. Для модульного теста вы можете использовать либо sqllite, либо Faker\Bogus и NBuilder.

Надеюсь, поможет!

person PavanT    schedule 22.11.2019