如何解决如何为.NET Core中的功能测试创建虚拟数据库?
我的应用程序基于带有实体框架6的.NET Core构建。我正在针对数据库上下文进行一些复杂的C#LINQ查询。虽然我有模拟数据库的XUnit单元测试并检查查询结果,但我发现其行为并不总是相同的。我该如何编写测试来创建某种关系虚拟数据库,该虚拟数据库的行为将与真实数据库完全相同,为该数据库添加种子,执行并测试查询结果,然后删除/删除该数据库?我还没找到东西。有一些文章建议单独创建本地数据库,但是我希望这项技术能够完成所有工作,包括将数据库创建和删除作为我的测试套件的一部分。
解决方法
使用xUnit,您可以使用Collection Fixtures完成类似的操作。
何时使用:当您要创建单个测试上下文并在多个测试类中的测试之间共享它,并在测试类中的所有测试完成后将其清除。
有时,您可能希望在多个测试类之间共享一个夹具对象。用于类固定装置的数据库示例是一个很好的示例:您可能希望使用一组测试数据来初始化数据库,然后将该测试数据保留在适当的位置以供多个测试类使用。您可以使用xUnit.net的collection fixture功能在多个测试类中的测试之间共享一个对象实例。
要使用收集装置,您需要执行以下步骤:
- 创建夹具类,然后将启动代码放入夹具类构造函数中。
- 如果夹具类需要执行清理,则在夹具类上实现IDisposable,并将清理代码放入Dispose()中 方法。
- 创建集合定义类,并使用[CollectionDefinition]属性对其进行修饰,为其赋予唯一的名称,该名称将 确定测试集合。
- 将ICollectionFixture 添加到集合定义类中。
- 使用您提供给测试的唯一名称,将[Collection]属性添加到将成为集合一部分的所有测试类中 集合定义类的[CollectionDefinition]属性。
- 如果测试类需要访问Fixture实例,请将其添加为构造函数参数,它将自动提供。
您可以创建一个类EfFixture
,该类实现IDisposable
并构成Entity Framework
DbContext
的实例。在此类的构造函数中,您将运行脚本或迁移以使用诸如(localdb)\mssqllocaldb
之类的服务器作为数据库架构。同样在构造函数中,您将为数据库设置种子。
然后,在Dispose
方法中,您将从服务器中删除数据库。
public class EfFixture
{
public EfFixture()
{
// Initialize the YourDbContext property,run migrations or scripts to create
// the schema,seed the database.
}
public Dispose()
{
// Tear down your test database.
}
public DbContext YourDbContext { get; }
}
您需要一个特殊的空类,该类具有CollectionDefinition
属性并实现ICollectionFixture<EfFixture>
。
[CollectionDefinition("EfCollection")]
public class EfCollection : ICollectionFixture<EfFixture>
{
}
然后,使用Collection
属性将测试类标记为集合的一部分。您可以将EfFixture
的实例传递给测试类的构造函数。
[Collection("EfCollection")]
public class TestClass1
{
public TestClass1(EfFixture fixture)
{
Fixture = fixture;
}
EfFixture Fixture { get; }
}
,
我认为Joshua提供的与xUnit集成的组合以及以下内容将使您能够测试数据库实例。我包括了为测试EF6 Migrations而创建的POC的部分来源。设置数据库所需的主要类型是DbMigratorHarness<T>
,它可用于创建新数据库。它需要迁移配置类型,该类型可以设置代码优先迁移:
var harness = new DbMigratorHarness<Migrations.Configuration>();
harness.LatestMigration();
var context = new MyDbContext(harness.ConnectionString);
// Do work with context
通过EfFixture
类型公开线束或上下文将使您可以访问隔离的数据库实例。
这是DbMigratorHarness<T>
的部分实现。完整的源代码可以在github.com/joncloud/ef6-migration-testing上找到。
using System;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations;
using System.Data.SqlClient;
public class DbMigratorHarness<T> : IDisposable
where T : DbMigrationsConfiguration,new()
{
public string ConnectionString { get; }
readonly DbMigrator _migrator;
public DbMigratorHarness()
{
ConnectionString = CreateNewSqlConnection();
CreateDatabase();
var dbConnectionInfo = new DbConnectionInfo(ConnectionString,"System.Data.SqlClient");
var configuration = new T
{
TargetDatabase = dbConnectionInfo
};
_migrator = new DbMigrator(configuration);
}
static string CreateNewSqlConnection()
{
// Note it may be important to change the connection string here depending upon how your database connection works.
var baseConnectionString = "Data Source=.;Initial Catalog=TestDbContext;Integrated Security=true;";
var builder = new SqlConnectionStringBuilder(baseConnectionString);
builder.InitialCatalog += Guid.NewGuid().ToString();
return builder.ConnectionString;
}
string GetDatabaseName()
{
var builder = new SqlConnectionStringBuilder(ConnectionString);
return builder.InitialCatalog;
}
string GetMasterConnectionString()
{
var builder = new SqlConnectionStringBuilder(ConnectionString);
builder.InitialCatalog = "master";
return builder.ConnectionString;
}
void CreateDatabase()
{
var masterConnectionString = GetMasterConnectionString();
using (var connection = new SqlConnection(masterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
var databaseName = GetDatabaseName();
command.CommandText = $@"
CREATE DATABASE [{databaseName}]
";
command.ExecuteNonQuery();
}
}
}
void DropDatabaseIfExists()
{
var masterConnectionString = GetMasterConnectionString();
using (var connection = new SqlConnection(masterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
var databaseName = GetDatabaseName();
command.CommandText = $@"
IF EXISTS(SELECT 1 FROM sys.databases WHERE name = '{databaseName}')
BEGIN
ALTER DATABASE [{databaseName}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [{databaseName}];
END
";
command.ExecuteNonQuery();
}
}
}
public void LatestMigration()
{
_migrator.Update();
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
DropDatabaseIfExists();
disposedValue = true;
}
}
~DbMigratorHarness()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。