Visual Studio 在线数据库集成测试
Database integration tests in Visual Studio Online
我很喜欢 Visual Studio Online 中的新构建工具。允许我做几乎所有我在本地构建服务器上做的事情。但是我缺少的一件事是集成数据库测试:对于每个构建 运行 我从脚本和 运行 数据库测试中重新创建测试数据库。
在 Visual Studio 网上,我似乎找不到任何可满足我需要的数据库实例。
我尝试为每个构建 运行 创建 Azure SQL 数据库(通过 PowerShell),然后在构建完成后将其删除。但是创建数据库需要很长时间(与构建过程的其余部分相比)。甚至当 PowerShell 脚本完成后,数据库还没有准备好接受请求——我需要不断地检查它是否真的准备好了。所以这个场景变得太复杂了,不靠谱。
是否有其他选项可以在 Visual Studio 在线进行数据库(SQL 服务器)集成测试?
更新: 我想我不是很清楚我需要什么 - 我需要一个免费的(非常便宜的)SQL 服务器实例来连接到 运行s 在 VSO 中构建代理。类似于 SQL Express 或 SQL CE 或 LocalDB,我可以在其中连接并重新创建数据库以针对 运行 C# 测试。重新创建数据库或 运行ning 测试不是问题,拥有有效的连接字符串才是问题。
2016 年 10 月更新:I've blogged关于我如何在 VSTS 中进行集成测试
市场上有一个 "Redgate SQL CI" VSTS 扩展,您可能想试试。有关详细信息,请参阅此 link:
Within the extension, there are four actions available:
•Build – builds your database into a NuGet package from the database
scripts folder in source control
•Test – runs your tSQLt tests against the database
•Sync – synchronizes the package to an integration database
•Publish – publishes the package to a NuGet stream
您应该将集成测试(任何需要应用程序实例的东西)作为发布管道的一部分推送到环境中 运行。
在您的构建中,只需进行编译和单元测试。如果竞争,您应该触发发布,作为发布管道的一部分,您的第一步应该是将数据库部署到 Azure 服务器。
您可以在 azure 中创建一个已安装 SQL 服务器的 VM,而不是尝试使用 SQL Azure。使用远程脚本部署数据库并执行测试。
即使您不使用发布工具来发布,这也适用于您。
TFS 构建服务器预装了 MSSQL Server 2012 和 MSSQL Server 2014 LocalDB。
来源: TFS Service - Software on the hosted build server
因此,只需将以下 one-liner 放入解决方案的 post-build 事件中,即可根据需要创建 MYTESTDB LocalDB 实例。这将允许您连接到 (LocalDB)\MYTESTDB
和 运行 数据库集成测试就好了。
"C:\Program Files\Microsoft SQL Server0\Tools\Binn\SqlLocalDB.exe" create "MYTESTDB" 12.0 -s
在 Azure DevOps 中,使用 .net Core 和 EF Core,我使用了不同的技术。
我在内存数据库中使用 SQLite 来执行集成和端到端测试。
目前在 .net Core 中,您可以同时使用 InMemory 数据库和 SQLite with in memory 选项,运行 默认 Azure DevOps CI Agent 中的任何集成测试。
内存中:https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory
请注意,InMemory 数据库不是关系数据库,它是一种多用途数据库,仅提及一个限制:
InMemory will allow you to save data that would violate referential
integrity constraints in a relational database
SQL内存模式 https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite
这种方法提供了一个更现实的测试平台。
现在,我走得更远了,我不想仅仅能够 运行 集成测试与 Azure DevOps 中的数据库依赖关系,我还希望能够托管我的 WebAPIs 在 CI 代理中,并在 API DBcontext 和我的 Persister 对象之间共享数据库(Persister 对象是一个助手 class,它允许我自动生成任何类型的实体并将它们保存到数据库中)。
关于集成测试和 Ent to End 测试的快速说明:
集成测试
涉及数据库的集成测试示例,可以是数据访问层的测试。在这种情况下,通常,您会在开始测试时创建一个 DBContext,用一些数据填充目标数据库,使用被测组件来操作数据,然后再次使用 DBContext 来确保断言得到满足。
这种情况非常简单,在相同的代码中,您可以共享相同的 DBContext 来生成数据并将其注入组件。
端到端测试
想象一下,在我的例子中,您想要测试一个 RESTful .net Core WebAPI,确保所有 CRUD 操作都按预期工作,并且您想要测试该过滤, 分页等等也是正确的。
在这种情况下,在测试(数据设置 and/or 验证)和 WebAPI 堆栈之间共享相同的 DBContext 要复杂得多。
.net EF Core 和 WebHostBuilder 之前
到目前为止,我知道唯一可行的方法是拥有专用服务器、VM 或 docker 映像,负责为 API 提供服务,而且还必须可以从 Web 访问或 Azure DevOps。
设置我的集成测试以重新创建数据库,或者 clever/limited 足以完全忽略现有数据,并确保每个测试对数据损坏具有弹性并且完全可靠(没有假阴性或阳性结果)。
然后我必须将构建定义配置为 运行 测试。
利用内存中的 SQLite with cache=shared 和 WebHostBuilder
下面我首先描述我使用的两种主要技术,然后我添加一些代码来展示如何做。
SQLite file::memory:?cache=shared
SQLite 允许您在内存中工作,而不是使用传统的文件,这已经给我们带来了巨大的性能提升,消除了 I/O 瓶颈,但除此之外,使用选项缓存=共享,我们可以在同一个进程中使用多个连接来访问相同的数据。如果您需要多个数据库,您可以指定一个名称。
更多信息: https://www.sqlite.org/inmemorydb.html
WebHostBuilder
.net Core 提供主机构建器,WebHostBuilder 允许我们创建一个服务器来启动和托管我们的 WebAPI,这样就可以像他们一样访问托管在真实服务器上。
当您在测试 class 中使用 WebHostBuilder 时,这两个存在于同一进程中。
更多信息: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilder?view=aspnetcore-2.2
解决方案
初始化 E2E 测试时,创建一个新的客户端来连接 api,创建一个 dbcontext,您将使用它来为数据库做种并可能断言。
测试初始化:
[TestClass]
public class CategoryControllerTests
{
private TestServerApiClient _client;
private Persister<Category> _categoryPersister;
private Builder<Category> _categoryBuilder;
private IHouseKeeperContext _context;
protected IDbContextTransaction Transaction;
[TestInitialize]
public void TestInitialize()
{
_context = ContextProvider.GetContext();
_client = new TestServerApiClient();
ContextProvider.ResetDatabase();
_categoryPersister = new Persister<Category>(_context);
_categoryBuilder = new Builder<Category>();
}
[TestCleanup]
public void Cleanup()
{
_client?.Dispose();
_context?.Dispose();
_categoryPersister?.Dispose();
ContextProvider.Dispose();
}
[...]
}
TestServerApiClient class:
public class TestServerApiClient : System.IDisposable
{
private readonly HttpClient _client;
private readonly TestServer _server;
public TestServerApiClient()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseEnvironment("Test");
webHostBuilder.UseStartup<Startup>();
_server = new TestServer(webHostBuilder);
_client = _server.CreateClient();
}
public void Dispose()
{
_server?.Dispose();
_client?.Dispose();
}
}
ContextProvider class 用于生成可用于播种数据或执行断言的数据库查询的 DBContext。
public static class ContextProvider
{
private static bool _requiresDbDeletion;
private static IConfiguration _applicationConfiguration;
public static IConfiguration ApplicationConfiguration
{
get
{
if (_applicationConfiguration != null) return _applicationConfiguration;
_applicationConfiguration = new ConfigurationBuilder()
.AddJsonFile("Config/appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
return _applicationConfiguration;
}
}
private static ServiceProvider _serviceProvider;
public static ServiceProvider ServiceProvider
{
get
{
if (_serviceProvider != null) return _serviceProvider;
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IConfiguration>(ApplicationConfiguration);
var databaseType = ApplicationConfiguration?.GetValue<DatabaseType>("DatabaseType") ?? DatabaseType.SQLServer;
_requiresDbDeletion = databaseType == DatabaseType.SQLServer;
IocConfig.RegisterContext(serviceCollection, null);
_serviceProvider = serviceCollection.BuildServiceProvider();
return _serviceProvider;
}
set
{
_serviceProvider = value;
}
}
/// <summary>
/// Generate the db context
/// </summary>
/// <returns>DB Context</returns>
public static IHouseKeeperContext GetContext()
{
return ServiceProvider.GetService<IHouseKeeperContext>();
}
public static void Dispose()
{
ServiceProvider?.Dispose();
ServiceProvider = null;
}
public static void ResetDatabase()
{
if (_requiresDbDeletion)
{
GetContext()?.Database?.EnsureDeleted();
GetContext()?.Database?.EnsureCreated();
}
}
}
IocConfig class 是一个帮手 class 我在我的框架中使用它来设置依赖注入。上面使用的方法 RegisterContext 负责注册 DBContext 并根据需要进行设置,因为这与 WebAPI 使用的 class 相同,使用配置 DatabaseType 来确定要做什么.
在这个 class 中,您可能可以找到大部分 "complexity"。
在内存中使用SQLite时,你要记住:
- 连接不会像使用 SQL 服务器那样自动打开和关闭(这就是我使用的原因:
context.Database.OpenConnection();
)
- 如果没有连接处于活动状态,数据库将被删除(这就是我使用
services.AddSingleton<IHouseKeeperContext>(s ...
的原因,重要的是保持一个连接打开,这样数据库就不会被破坏,但另一方面你必须注意在一次测试结束时关闭所有连接,这样数据库最终会被销毁,下一次测试会正确地创建一个新的空连接。
class 的其余部分处理生产和测试设置的 SQL 服务器配置。我可以随时设置测试以使用 SQL 服务器的真实实例,所有测试将保持完全独立于其他测试,但它肯定会很慢,并且可能仅适用于夜间构建(如果需要,这取决于您的系统大小)。
public class IocConfig
{
public static void RegisterContext(IServiceCollection services, IHostingEnvironment hostingEnvironment)
{
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString(Constants.ConfigConnectionStringName);
var databaseType = DatabaseType.SQLServer;
try
{
databaseType = configuration?.GetValue<DatabaseType>("DatabaseType") ?? DatabaseType.SQLServer;
}catch
{
MyLoggerFactory.CreateLogger<IocConfig>()?.LogWarning("Missing or invalid configuration: DatabaseType");
databaseType = DatabaseType.SQLServer;
}
if(hostingEnvironment != null && hostingEnvironment.IsProduction())
{
if(databaseType == DatabaseType.SQLiteInMemory)
{
throw new ConfigurationErrorsException($"Cannot use database type {databaseType} for production environment");
}
}
switch (databaseType)
{
case DatabaseType.SQLiteInMemory:
// Use SQLite in memory database for testing
services.AddDbContext<HouseKeeperContext>(options =>
{
options.UseSqlite($"DataSource='file::memory:?cache=shared'");
});
// Use singleton context when using SQLite in memory if the connection is closed the database is going to be destroyed
// so must use a singleton context, open the connection and manually close it when disposing the context
services.AddSingleton<IHouseKeeperContext>(s => {
var context = s.GetService<HouseKeeperContext>();
context.Database.OpenConnection();
context.Database.EnsureCreated();
return context;
});
break;
case DatabaseType.SQLServer:
default:
// Use SQL Server testing configuration
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
services.AddDbContext<HouseKeeperContext>(options =>
{
options.UseSqlServer(connectionString);
});
services.AddSingleton<IHouseKeeperContext>(s => {
var context = s.GetService<HouseKeeperContext>();
context.Database.EnsureCreated();
return context;
});
break;
}
// Use SQL Server production configuration
services.AddDbContextPool<HouseKeeperContext>(options =>
{
// Production setup using SQL Server
options.UseSqlServer(connectionString);
options.UseLoggerFactory(MyLoggerFactory);
}, poolSize: 5);
services.AddTransient<IHouseKeeperContext>(service =>
services.BuildServiceProvider()
.GetService<HouseKeeperContext>());
break;
}
}
[...]
}
示例测试,首先我使用持久化生成数据,然后在数据库中播种,然后我使用API获取数据,测试可以也可以相反,使用 POST 请求设置数据,然后使用 DBContext 读取数据库并确保创建成功。
[TestMethod]
public async Task GET_support_orderBy_Id()
{
_categoryPersister.Persist(3, (c, i) =>
{
c.Active = 1 % 2 == 0;
c.Name = $"Name_{i}";
c.Description = $"Desc_i";
});
var response = await _client.GetAsync("/api/category?&orderby=Id");
var categories = response.To<List<Category>>();
Assert.That.All(categories).HaveCount(3);
Assert.IsTrue(categories[0].Id < categories[1].Id &&
categories[1].Id < categories[2].Id);
response = await _client.GetAsync("/api/category?$orderby=Id desc");
categories = response.To<List<Category>>();
Assert.That.All(categories).HaveCount(3);
Assert.IsTrue(categories[0].Id > categories[1].Id &&
categories[1].Id > categories[2].Id);
}
结论
我喜欢这样的事实,我可以 运行 在 Azure DevOps 中免费进行 E2E 测试,性能非常好,这给了我很大的信心,非常适合您想要设置持续交付环境。
以下是此代码在 Azure DevOps(免费版)中构建执行的部分截图。
抱歉,这最终比预期的要长。
我很喜欢 Visual Studio Online 中的新构建工具。允许我做几乎所有我在本地构建服务器上做的事情。但是我缺少的一件事是集成数据库测试:对于每个构建 运行 我从脚本和 运行 数据库测试中重新创建测试数据库。
在 Visual Studio 网上,我似乎找不到任何可满足我需要的数据库实例。
我尝试为每个构建 运行 创建 Azure SQL 数据库(通过 PowerShell),然后在构建完成后将其删除。但是创建数据库需要很长时间(与构建过程的其余部分相比)。甚至当 PowerShell 脚本完成后,数据库还没有准备好接受请求——我需要不断地检查它是否真的准备好了。所以这个场景变得太复杂了,不靠谱。
是否有其他选项可以在 Visual Studio 在线进行数据库(SQL 服务器)集成测试?
更新: 我想我不是很清楚我需要什么 - 我需要一个免费的(非常便宜的)SQL 服务器实例来连接到 运行s 在 VSO 中构建代理。类似于 SQL Express 或 SQL CE 或 LocalDB,我可以在其中连接并重新创建数据库以针对 运行 C# 测试。重新创建数据库或 运行ning 测试不是问题,拥有有效的连接字符串才是问题。
2016 年 10 月更新:I've blogged关于我如何在 VSTS 中进行集成测试
市场上有一个 "Redgate SQL CI" VSTS 扩展,您可能想试试。有关详细信息,请参阅此 link:
Within the extension, there are four actions available:
•Build – builds your database into a NuGet package from the database scripts folder in source control
•Test – runs your tSQLt tests against the database
•Sync – synchronizes the package to an integration database
•Publish – publishes the package to a NuGet stream
您应该将集成测试(任何需要应用程序实例的东西)作为发布管道的一部分推送到环境中 运行。
在您的构建中,只需进行编译和单元测试。如果竞争,您应该触发发布,作为发布管道的一部分,您的第一步应该是将数据库部署到 Azure 服务器。
您可以在 azure 中创建一个已安装 SQL 服务器的 VM,而不是尝试使用 SQL Azure。使用远程脚本部署数据库并执行测试。
即使您不使用发布工具来发布,这也适用于您。
TFS 构建服务器预装了 MSSQL Server 2012 和 MSSQL Server 2014 LocalDB。
来源: TFS Service - Software on the hosted build server
因此,只需将以下 one-liner 放入解决方案的 post-build 事件中,即可根据需要创建 MYTESTDB LocalDB 实例。这将允许您连接到 (LocalDB)\MYTESTDB
和 运行 数据库集成测试就好了。
"C:\Program Files\Microsoft SQL Server0\Tools\Binn\SqlLocalDB.exe" create "MYTESTDB" 12.0 -s
在 Azure DevOps 中,使用 .net Core 和 EF Core,我使用了不同的技术。 我在内存数据库中使用 SQLite 来执行集成和端到端测试。 目前在 .net Core 中,您可以同时使用 InMemory 数据库和 SQLite with in memory 选项,运行 默认 Azure DevOps CI Agent 中的任何集成测试。
内存中:https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/in-memory 请注意,InMemory 数据库不是关系数据库,它是一种多用途数据库,仅提及一个限制:
InMemory will allow you to save data that would violate referential integrity constraints in a relational database
SQL内存模式 https://docs.microsoft.com/en-us/ef/core/miscellaneous/testing/sqlite 这种方法提供了一个更现实的测试平台。
现在,我走得更远了,我不想仅仅能够 运行 集成测试与 Azure DevOps 中的数据库依赖关系,我还希望能够托管我的 WebAPIs 在 CI 代理中,并在 API DBcontext 和我的 Persister 对象之间共享数据库(Persister 对象是一个助手 class,它允许我自动生成任何类型的实体并将它们保存到数据库中)。
关于集成测试和 Ent to End 测试的快速说明:
集成测试
涉及数据库的集成测试示例,可以是数据访问层的测试。在这种情况下,通常,您会在开始测试时创建一个 DBContext,用一些数据填充目标数据库,使用被测组件来操作数据,然后再次使用 DBContext 来确保断言得到满足。 这种情况非常简单,在相同的代码中,您可以共享相同的 DBContext 来生成数据并将其注入组件。
端到端测试
想象一下,在我的例子中,您想要测试一个 RESTful .net Core WebAPI,确保所有 CRUD 操作都按预期工作,并且您想要测试该过滤, 分页等等也是正确的。 在这种情况下,在测试(数据设置 and/or 验证)和 WebAPI 堆栈之间共享相同的 DBContext 要复杂得多。
.net EF Core 和 WebHostBuilder 之前
到目前为止,我知道唯一可行的方法是拥有专用服务器、VM 或 docker 映像,负责为 API 提供服务,而且还必须可以从 Web 访问或 Azure DevOps。 设置我的集成测试以重新创建数据库,或者 clever/limited 足以完全忽略现有数据,并确保每个测试对数据损坏具有弹性并且完全可靠(没有假阴性或阳性结果)。 然后我必须将构建定义配置为 运行 测试。
利用内存中的 SQLite with cache=shared 和 WebHostBuilder
下面我首先描述我使用的两种主要技术,然后我添加一些代码来展示如何做。
SQLite file::memory:?cache=shared
SQLite 允许您在内存中工作,而不是使用传统的文件,这已经给我们带来了巨大的性能提升,消除了 I/O 瓶颈,但除此之外,使用选项缓存=共享,我们可以在同一个进程中使用多个连接来访问相同的数据。如果您需要多个数据库,您可以指定一个名称。 更多信息: https://www.sqlite.org/inmemorydb.html
WebHostBuilder
.net Core 提供主机构建器,WebHostBuilder 允许我们创建一个服务器来启动和托管我们的 WebAPI,这样就可以像他们一样访问托管在真实服务器上。 当您在测试 class 中使用 WebHostBuilder 时,这两个存在于同一进程中。 更多信息: https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilder?view=aspnetcore-2.2
解决方案
初始化 E2E 测试时,创建一个新的客户端来连接 api,创建一个 dbcontext,您将使用它来为数据库做种并可能断言。
测试初始化:
[TestClass]
public class CategoryControllerTests
{
private TestServerApiClient _client;
private Persister<Category> _categoryPersister;
private Builder<Category> _categoryBuilder;
private IHouseKeeperContext _context;
protected IDbContextTransaction Transaction;
[TestInitialize]
public void TestInitialize()
{
_context = ContextProvider.GetContext();
_client = new TestServerApiClient();
ContextProvider.ResetDatabase();
_categoryPersister = new Persister<Category>(_context);
_categoryBuilder = new Builder<Category>();
}
[TestCleanup]
public void Cleanup()
{
_client?.Dispose();
_context?.Dispose();
_categoryPersister?.Dispose();
ContextProvider.Dispose();
}
[...]
}
TestServerApiClient class:
public class TestServerApiClient : System.IDisposable
{
private readonly HttpClient _client;
private readonly TestServer _server;
public TestServerApiClient()
{
var webHostBuilder = new WebHostBuilder();
webHostBuilder.UseEnvironment("Test");
webHostBuilder.UseStartup<Startup>();
_server = new TestServer(webHostBuilder);
_client = _server.CreateClient();
}
public void Dispose()
{
_server?.Dispose();
_client?.Dispose();
}
}
ContextProvider class 用于生成可用于播种数据或执行断言的数据库查询的 DBContext。
public static class ContextProvider
{
private static bool _requiresDbDeletion;
private static IConfiguration _applicationConfiguration;
public static IConfiguration ApplicationConfiguration
{
get
{
if (_applicationConfiguration != null) return _applicationConfiguration;
_applicationConfiguration = new ConfigurationBuilder()
.AddJsonFile("Config/appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
return _applicationConfiguration;
}
}
private static ServiceProvider _serviceProvider;
public static ServiceProvider ServiceProvider
{
get
{
if (_serviceProvider != null) return _serviceProvider;
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IConfiguration>(ApplicationConfiguration);
var databaseType = ApplicationConfiguration?.GetValue<DatabaseType>("DatabaseType") ?? DatabaseType.SQLServer;
_requiresDbDeletion = databaseType == DatabaseType.SQLServer;
IocConfig.RegisterContext(serviceCollection, null);
_serviceProvider = serviceCollection.BuildServiceProvider();
return _serviceProvider;
}
set
{
_serviceProvider = value;
}
}
/// <summary>
/// Generate the db context
/// </summary>
/// <returns>DB Context</returns>
public static IHouseKeeperContext GetContext()
{
return ServiceProvider.GetService<IHouseKeeperContext>();
}
public static void Dispose()
{
ServiceProvider?.Dispose();
ServiceProvider = null;
}
public static void ResetDatabase()
{
if (_requiresDbDeletion)
{
GetContext()?.Database?.EnsureDeleted();
GetContext()?.Database?.EnsureCreated();
}
}
}
IocConfig class 是一个帮手 class 我在我的框架中使用它来设置依赖注入。上面使用的方法 RegisterContext 负责注册 DBContext 并根据需要进行设置,因为这与 WebAPI 使用的 class 相同,使用配置 DatabaseType 来确定要做什么. 在这个 class 中,您可能可以找到大部分 "complexity"。 在内存中使用SQLite时,你要记住:
- 连接不会像使用 SQL 服务器那样自动打开和关闭(这就是我使用的原因:
context.Database.OpenConnection();
) - 如果没有连接处于活动状态,数据库将被删除(这就是我使用
services.AddSingleton<IHouseKeeperContext>(s ...
的原因,重要的是保持一个连接打开,这样数据库就不会被破坏,但另一方面你必须注意在一次测试结束时关闭所有连接,这样数据库最终会被销毁,下一次测试会正确地创建一个新的空连接。
class 的其余部分处理生产和测试设置的 SQL 服务器配置。我可以随时设置测试以使用 SQL 服务器的真实实例,所有测试将保持完全独立于其他测试,但它肯定会很慢,并且可能仅适用于夜间构建(如果需要,这取决于您的系统大小)。
public class IocConfig
{
public static void RegisterContext(IServiceCollection services, IHostingEnvironment hostingEnvironment)
{
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetService<IConfiguration>();
var connectionString = configuration.GetConnectionString(Constants.ConfigConnectionStringName);
var databaseType = DatabaseType.SQLServer;
try
{
databaseType = configuration?.GetValue<DatabaseType>("DatabaseType") ?? DatabaseType.SQLServer;
}catch
{
MyLoggerFactory.CreateLogger<IocConfig>()?.LogWarning("Missing or invalid configuration: DatabaseType");
databaseType = DatabaseType.SQLServer;
}
if(hostingEnvironment != null && hostingEnvironment.IsProduction())
{
if(databaseType == DatabaseType.SQLiteInMemory)
{
throw new ConfigurationErrorsException($"Cannot use database type {databaseType} for production environment");
}
}
switch (databaseType)
{
case DatabaseType.SQLiteInMemory:
// Use SQLite in memory database for testing
services.AddDbContext<HouseKeeperContext>(options =>
{
options.UseSqlite($"DataSource='file::memory:?cache=shared'");
});
// Use singleton context when using SQLite in memory if the connection is closed the database is going to be destroyed
// so must use a singleton context, open the connection and manually close it when disposing the context
services.AddSingleton<IHouseKeeperContext>(s => {
var context = s.GetService<HouseKeeperContext>();
context.Database.OpenConnection();
context.Database.EnsureCreated();
return context;
});
break;
case DatabaseType.SQLServer:
default:
// Use SQL Server testing configuration
if (hostingEnvironment == null || hostingEnvironment.IsTesting())
{
services.AddDbContext<HouseKeeperContext>(options =>
{
options.UseSqlServer(connectionString);
});
services.AddSingleton<IHouseKeeperContext>(s => {
var context = s.GetService<HouseKeeperContext>();
context.Database.EnsureCreated();
return context;
});
break;
}
// Use SQL Server production configuration
services.AddDbContextPool<HouseKeeperContext>(options =>
{
// Production setup using SQL Server
options.UseSqlServer(connectionString);
options.UseLoggerFactory(MyLoggerFactory);
}, poolSize: 5);
services.AddTransient<IHouseKeeperContext>(service =>
services.BuildServiceProvider()
.GetService<HouseKeeperContext>());
break;
}
}
[...]
}
示例测试,首先我使用持久化生成数据,然后在数据库中播种,然后我使用API获取数据,测试可以也可以相反,使用 POST 请求设置数据,然后使用 DBContext 读取数据库并确保创建成功。
[TestMethod]
public async Task GET_support_orderBy_Id()
{
_categoryPersister.Persist(3, (c, i) =>
{
c.Active = 1 % 2 == 0;
c.Name = $"Name_{i}";
c.Description = $"Desc_i";
});
var response = await _client.GetAsync("/api/category?&orderby=Id");
var categories = response.To<List<Category>>();
Assert.That.All(categories).HaveCount(3);
Assert.IsTrue(categories[0].Id < categories[1].Id &&
categories[1].Id < categories[2].Id);
response = await _client.GetAsync("/api/category?$orderby=Id desc");
categories = response.To<List<Category>>();
Assert.That.All(categories).HaveCount(3);
Assert.IsTrue(categories[0].Id > categories[1].Id &&
categories[1].Id > categories[2].Id);
}
结论
我喜欢这样的事实,我可以 运行 在 Azure DevOps 中免费进行 E2E 测试,性能非常好,这给了我很大的信心,非常适合您想要设置持续交付环境。
以下是此代码在 Azure DevOps(免费版)中构建执行的部分截图。
抱歉,这最终比预期的要长。