Интеграционные тесты NUnit на веб-API - как создать / уничтожить интеграционный тестовый БД - PullRequest
0 голосов
/ 22 октября 2018

Я реализую NUnit Integration Tests конечных точек REST нашего контроллера в .NET Web API 2 проекте.Мы используем подход Entity Framework code-first from database для создания наших контроллеров и моделей.

У меня настроен проект myProjectIntegrationTests с установленным NUnit и ссылкой на myProject.

Из моего исследования следующим шагом является создание сценария TestSetup, который в каждом тесте создает Integration Tests Database в LocalDb.Это позволяет нам проводить интеграционное тестирование наших вызовов API, не затрагивая мастер dev database.

Этот скрипт TestSetup должен выполнять несколько вещей при каждом запуске теста:
- проверить, еслисоединение в настоящее время открыто в Integration Test Db - если это так, закройте его.
- проверьте, существует ли существующее Integration Test db - если да, разорвите его.
- запустите миграцию с моего мастера dev database наmy Integration Test Db чтобы загрузить его с реальными данными.
- создать новый экземпляр Integration Test Db
- выполнить интеграционные тесты ...
- закрыть Integration Test Db соединения
- разорвать Integration Test Db

Создание этого TestSetup класса - вот что доставляет мне неприятности.Я нашел учебные пособия о том, как сделать это для .NET MVC, .NET Core, а также Entity Framework - но, похоже, ни один из них не использует просто .Net Web API, поэтому некоторые библиотеки и код, на которые делается ссылка, не работают длямне. Может ли кто-нибудь предоставить пример скрипта или ссылку на учебник, который может работать в .NET Web API 2?

Вот пример того, как кто-то делает это для EntityFramework, используя, я полагаю, .Net Core.Это часть замечательного учебника PluralSight по интеграционному тестированию в Entity Framework Майкла Перри, найденного здесь :

using Globalmantics.DAL;
using Globalmantics.DAL.Migrations;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Data.E  ntity;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Globalmantics.IntegrationTests
{
    [SetUpFixture]
    public class TestSetup
    {
        [OneTimeSetUp]
        public void SetUpDatabase()
        {
            DestroyDatabase();
            CreateDatabase();
        }

        [OneTimeTearDown]
        public void TearDownDatabase()
        {
            DestroyDatabase();
        }

        private static void CreateDatabase()
        {
            ExecuteSqlCommand(Master, $@"
                CREATE DATABASE [Globalmantics]
                ON (NAME = 'Globalmantics',
                FILENAME = '{Filename}')");

            var migration = new MigrateDatabaseToLatestVersion<
                GlobalmanticsContext, GlobalmanticsConfiguration>();
            migration.InitializeDatabase(new GlobalmanticsContext());
        }

        private static void DestroyDatabase()
        {
            var fileNames = ExecuteSqlQuery(Master, @"
                SELECT [physical_name] FROM [sys].[master_files]
                WHERE [database_id] = DB_ID('Globalmantics')",
                row => (string)row["physical_name"]);

            if (fileNames.Any())
            {
                ExecuteSqlCommand(Master, @"
                    ALTER DATABASE [Globalmantics] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
                    EXEC sp_detach_db 'Globalmantics'");

                fileNames.ForEach(File.Delete);
            }
        }

        private static void ExecuteSqlCommand(
            SqlConnectionStringBuilder connectionStringBuilder,
            string commandText)
        {
            using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = commandText;
                    command.ExecuteNonQuery();
                }
            }
        }

        private static List<T> ExecuteSqlQuery<T>(
            SqlConnectionStringBuilder connectionStringBuilder,
            string queryText,
            Func<SqlDataReader, T> read)
        {
            var result = new List<T>();
            using (var connection = new SqlConnection(connectionStringBuilder.ConnectionString))
            {
                connection.Open();
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = queryText;
                    using (var reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            result.Add(read(reader));
                        }
                    }
                }
            }
            return result;
        }

        private static SqlConnectionStringBuilder Master =>
            new SqlConnectionStringBuilder
            {
                DataSource = @"(LocalDB)\MSSQLLocalDB",
                InitialCatalog = "master",
                IntegratedSecurity = true
            };

        private static string Filename => Path.Combine(
            Path.GetDirectoryName(
                Assembly.GetExecutingAssembly().Location),
            "Globalmantics.mdf");
    }
}

А вот более старый пример того, как кто-то делает это для .Net MVC:

using System;
using System.Data.Entity;
using NUnit.Framework;

namespace BankingSite.IntegrationTests
{
    [SetUpFixture]
    public class TestFixtureLifecycle
    {
        public TestFixtureLifecycle()
        {
            EnsureDataDirectoryConnectionStringPlaceholderIsSet();

            EnsureNoExistingDatabaseFiles();
        }

        private static void EnsureDataDirectoryConnectionStringPlaceholderIsSet()
        {
            // When not running inside MVC application the |DataDirectory| placeholder 
            // is null in a connection string, e.g AttachDBFilename=|DataDirectory|\TestBankingSiteDb.mdf

            AppDomain.CurrentDomain.SetData("DataDirectory", NUnit.Framework.TestContext.CurrentContext.TestDirectory);
        }

        private void EnsureNoExistingDatabaseFiles()
        {
            const string connectionString = "name=DefaultConnection";

            if (Database.Exists(connectionString))
            {                
                Database.Delete(connectionString);    
            }           
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

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

0 голосов
/ 22 октября 2018

Возможно, не тот ответ, который вы ищете, но недавно я успешно использовал образ докера сервера sql с docker compose.

Вы можете запустить экземпляр базы данных и удалить данные.громкости, когда изображение выключается.Использование ключа -rm в команде docker run сделает это автоматически за вас.

Если вы используете ядро ​​dot net, вы можете настроить другой контейнер для запуска миграций и тестов вашей инфраструктуры сущностей.

Если вы используете dotnet framework, вы, возможно, сможете запускать образы Windows Docker, однако они, как правило, медленнее запускаются.

Этот подход лучше всего подойдет, если вы запустите все из скрипта powershell.Запуск инфраструктуры из кода, как вы ожидаете, может быть сложным и, возможно, более сложным, чем необходимо.

...