Интеграция тестирования нескольких объектов Entity framework dbcontexts, которые совместно используют базу данных

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

public class Context1 : DbContext {
    public Context1()
        : base("DemoDb") {
    }
}

public class Context2 : DbContext {
    public Context2()
        : base("DemoDb") {
    }
}

Все обновления базы данных выполняются с помощью сценариев и не зависят от миграции (и они не будут продвигаться вперед). Вопрос в том, как бы вы проводили тестирование интеграции с этими контекстами?

Я считаю, что здесь есть три варианта (может быть, больше я их не знаю)

Вариант 1 - Суперконтекст - контекст, который содержит все модели и конфигурации, необходимые для настройки базы данных:

public class SuperContext : DbContext
{
    public SuperContext()
        : base("DemoDb") {
    }
}

В этом параметре тестовая база данных будет настроена против суперконтекста, и все последующие тесты будут проводиться через меньшие контексты. Причина, по которой я не увлекаюсь этим вариантом, заключается в том, что я буду дублировать все конфигурации и модели сущностей, которые я уже создал.

Вариант 2 - создайте пользовательский инициализатор для тестов интеграции, в котором будут выполняться все соответствующие сценарии инициализации db:

public class IntegrationTestInitializer : IDatabaseInitializer<DbContext> {

    public void InitializeDatabase(DbContext context) {
        /* run scripts to set up database here */
    }
}

Эта опция позволяет тестировать истинную структуру базы данных, но также требует обновления при каждом добавлении новых сценариев db.

Вариант 3 - просто проверьте отдельные контексты:

В этой опции можно просто создать EF тестовую базу данных на основе контекста, и все тесты будут работать внутри собственной "песочницы". Причина, по которой мне это не нравится, заключается в том, что вам не кажется, что вы будете тестировать истинное представление базы данных.

В настоящее время я покачусь по вариантам 2. Что вы все думаете? Есть ли лучший способ там?

Ответ 1

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

В результате я оказался Вариантом 4: поддерживал содержимое базы данных unit test через обычный пользовательский интерфейс. Конечно, большинство интеграционных тестов временно изменяют содержимое базы данных как часть фазы "действия" теста (подробнее об этом "временном" позже), но содержимое не настроено при запуске тестового сеанса.

Вот почему.

На каком-то этапе мы также сгенерировали содержимое базы данных в начале тестового сеанса либо с помощью кода, либо путем десериализации файлов XML. (У нас еще не было EF, но в противном случае у нас, вероятно, был бы некоторый метод Seed в инициализаторе базы данных). Постепенно я начал испытывать опасения при таком подходе. Было очень сложно поддерживать код /​​XML при изменении модели данных или бизнес-логики, особенно. когда необходимо было разработать новые варианты использования. Иногда я позволял себе небольшое повреждение данных тестов, зная, что это не повлияет на тесты.

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

Итак, я обнаружил, что создаю тестовые данные через интерфейс приложения, а затем тщательно сериализую этот контент в XML или записывая код, который будет генерировать точно то же самое. До того, как однажды мне пришло в голову, что у меня есть данные, которые легко доступны в этой базе данных, почему бы не использовать их напрямую?

Теперь, может быть, вы спросите Как сделать независимыми тесты?

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

Но что, если есть только одна база данных и нет семантических скриптов? Вы можете восстановить резервную копию для каждого теста. Мы выбрали другой подход. Каждый тест интеграции выполняется в TransactionScope, который никогда не выполнялся. Этого очень легко достичь. Каждое тестовое приспособление наследуется от базового класса, который имеет эти методы (NUnit):

[SetUp]
public void InitTestEnvironment()
{
    SetupTeardown.PerTestSetup();
}

[TearDown]
public void CleanTestEnvironment()
{
    SetupTeardown.PerTestTearDown();
}

и в SetupTeardown:

public static void PerTestSetup()
{
    _tranactionScope = new TransactionScope();
}

public static void PerTestTearDown()
{
    if (_tranactionScope != null)
    {
        _tranactionScope.Dispose(); // Rollback any changes made in a test.
        _tranactionScope = null;
    }
}

где _tranactionScope - статическая членная переменная.

Ответ 2

Вариант 2 или любой его вариант, который запускает фактические сценарии обновления БД, был бы лучшим. В противном случае вы не обязательно должны тестировать интеграцию с той же базой данных, что и вы, по крайней мере, в отношении схемы.

Чтобы решить вашу проблему, требуя обновления каждый раз, когда добавляются новые сценарии БД, если вы должны хранить все сценарии в одной папке, возможно, в рамках проекта с действием сборки "copy if newer" вы могли бы программно читать каждый файл и выполнять script в нем. Пока место, где вы читаете файлы, является вашим каноническим репозиторием для сценариев обновления, вам больше не понадобится вносить изменения и делать какие-либо дальнейшие изменения.