Web API上的NUnit集成测试-如何创建/销毁集成测试数据库

时间:2018-10-22 19:47:42

标签: sql-server unit-testing asp.net-web-api nunit integration-testing

我正在NUnit Integration Tests项目中实现.NET Web API 2控制器REST端点。我们使用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到我的Integration Test Db的迁移,以向其加载真实数据。
-创建Integration Test Db的新实例
-运行集成测试...
-关闭Integration Test Db个连接
-拆解Integration Test Db

创建这个TestSetup类很麻烦。我找到了有关如何针对.NET MVC,.NET Core以及实体框架执行此操作的教程-但是这些教程似乎都没有利用.Net Web API,因此所引用的某些库和代码不是为我工作。 有人可以提供示例脚本或教程链接,这些脚本或教程链接可能在.NET Web API 2中有效吗?


下面是一个使用Entity .Net Core为EntityFramework进行此操作的示例。这是Michael Perry撰写的关于Entity Framework中集成测试的PluralSight很棒教程的一部分,发现here

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 :(得分:1)

可能不是您要找的答案,但最近我在docker compose上使用the sql server docker image取得了成功。

当映像关闭时,您可以启动数据库实例并删除数据卷。在docker run命令上使用—rm开关将自动为您完成操作。

如果您使用的是点网核心,则可以设置另一个容器来运行您的实体框架迁移和测试。

如果您使用的是dotnet框架,则也许可以运行Windows泊坞窗映像,但是它们的启动速度会稍慢一些。

如果您从Powershell脚本启动所有内容,则此方法最有效。如您所愿,从代码中启动基础架构可能很棘手,甚至可能比需要的复杂。

答案 1 :(得分:1)

逐行浏览这些操作所需的sql命令类型将很痛苦。仅开发执行拆解/构建步骤的存储过程会更好。您似乎已经有了一个开始,因为我看到您围绕这些语句编写代码。然后,您的集成测试代码将只需要调用此过程并等待设置完成即可。请记住,您不必执行代码中的所有操作。