我们使用xUnit
和Microsoft.AspNetCore.TestHost.TestServer
设置了集成测试,以针对在ASP.NET Core 2.2上运行的Web API运行测试。
我们的Web API是一个单一的代码库,根据某些配置或应用程序设置的差异(例如 country , currency 等),可以分别部署多次。
下图试图解释我们的部署设置:
我们要确保我们的集成测试能够针对所有部署运行。
对于这两个部署, X 和 X` 的API端点,请求和响应都完全相同。因此,在进行每个部署的集成测试时,我们希望避免重复。
以下是说明我们当前测试设置的示例代码:
TestStartup.cs
public class TestStartup : IStartup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false)
.AddEnvironmentVariables()
.Build();
services.AddMvc()
.SetCompatibilityVersion(version: CompatibilityVersion.Version_2_2);
// Code to add required services based on configuration
return services.BuildServiceProvider();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
// Code to configure test Startup
}
}
TestServerFixture.cs
public class TestServerFixture
{
public TestServerFixture()
{
var builder = new WebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartup>(new TestStartup());
});
var server = new TestServer(builder);
Client = server.CreateClient();
}
public HttpClient Client { get; private set; }
}
MyTest.cs
public class MyTest : IClassFixture<TestServerFixture>
{
private readonly TestServerFixture _fixture;
public MyTest(TestServerFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void ItShouldExecuteTwice_AgainstTwoSeparateConfigurations()
{
//...
}
}
现在,我希望针对两个不同的配置/应用设置,或者换句话说,针对Visual Studio中的两个不同的测试部署,在类ItShouldExecuteTwice_AgainstTwoSeparateConfigurations
中进行一次MyTest
测试。
我知道,我应该能够使用构建配置(例如DEBUG_SETTING1
,DEBUG_SETTING2
)和预处理程序指令(#if DEBUG_SETTING1
)的组合来实现这一目标。
另一种选择是拥有一个具有通用方法的基础测试帮助程序项目,并为每个部署提供一个单独的集成项目。
有没有更好,更优雅的方法来实现这一目标?
答案 0 :(得分:4)
重构测试启动以允许根据测试需要对其进行修改
例如
public class TestStartup : IStartup {
private readonly string settings;
public TestStartup(string settings) {
this.settings = settings;
}
public void ConfigureServices(IServiceCollection services) {
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(settings, false) //<--just an example
.AddEnvironmentVariables()
.Build();
services.AddMvc()
.SetCompatibilityVersion(version: CompatibilityVersion.Version_2_2);
//...Code to add required services based on configuration
}
public void Configure(IApplicationBuilder app) {
app.UseMvc();
//...Code to configure test Startup
}
}
并通过夹具过滤掉该图案
public class TestServerFixture {
static readonly Dictionary<string, TestServer> cache =
new Dictionary<string, TestServer>();
public TestServerFixture() {
//...
}
public HttpClient GetClient(string settings) {
TestServer server = null;
if(!cache.TryGetValue(settings, out server)) {
var startup = new TestStartup(settings); //<---
var builder = new WebHostBuilder()
.ConfigureServices(services => {
services.AddSingleton<IStartup>(startup);
});
server = new TestServer(builder);
cache.Add(settings, server);
}
return server.CreateClient();
}
}
最后是测试本身
public class MyTest : IClassFixture<TestServerFixture> {
private readonly TestServerFixture fixture;
public MyTest(TestServerFixture fixture) {
this.fixture = fixture;
}
[Theory]
[InlineData("settings1.json")]
[InlineData("settings2.json")]
public async Task Should_Execute_Using_Configurations(string settings) {
var client = fixture.CreateClient(settings);
//...use client
}
}
答案 1 :(得分:2)
@Nkosi的帖子非常适合我们的情况和我的提问。这是一种简单,干净且易于理解的方法,具有最大的可重用性。答案的满分。
但是,有一些原因导致我无法继续使用该方法:
在建议的方法中,我们不能仅针对一个特定的setting
运行测试。之所以对我们如此重要,是因为将来,可以有两个不同的团队来维护其特定的实施和部署。使用Theory
,对于所有测试仅运行一个setting
会有点困难。
对于每个设置/部署,我们很可能需要两个单独的构建和部署管道。
尽管当今的API端点Request
和Response
完全相同,但我们不知道在开发过程中是否会继续如此。
由于上述原因,我们还考虑了以下两种方法:
方法1
具有一个公共class
库,该库具有Fixture
类的公共Tests
和abstract
TestStartup.cs
public abstract class TestStartup : IStartup
{
public abstract IServiceProvider ConfigureServices(IServiceCollection services);
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
// Code to configure test Startup
}
}
TestServerFixture.cs
public abstract class TestServerFixture
{
protected TestServerFixture(IStartup startup)
{
var builder = new WebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartup>(startup);
});
var server = new TestServer(builder);
Client = server.CreateClient();
}
public HttpClient Client { get; private set; }
}
MyTest.cs
public abstract class MyTest
{
private readonly TestServerFixture _fixture;
protected MyTest(TestServerFixture fixture)
{
_fixture = fixture;
}
[Fact]
public void ItShouldExecuteTwice_AgainstTwoSeparateConfigurations()
{
//...
}
}
TestStartup.cs
public class TestStartup : Common.IntegrationTests.TestStartup
{
public override IServiceProvider ConfigureServices(IServiceCollection services)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false) // appsettings for Setting1
.AddEnvironmentVariables()
.Build();
services.AddMvc()
.SetCompatibilityVersion(version: CompatibilityVersion.Version_2_2);
// Code to add required services based on configuration
return services.BuildServiceProvider();
}
}
TestServerFixture.cs
public class TestServerFixture : Fixtures.TestServerFixture
{
public TestServerFixture() : base(new TestStartup())
{
}
}
MyTests.cs
public class MyTests : Common.IntegrationTests.MyTests, IClassFixture<TestServerFixture>
{
public MyTests(TestServerFixture fixture) : base(fixture)
{
}
}
与 Setting1.IntegrationTests
相似的结构这种方法在可重用性和灵活性之间取得了良好的平衡,可独立运行/修改测试。但是,我仍然不是100%相信这种方法,因为对于每个常见的Test
类,我们都需要一个实现,除了调用base
{{1} }。
方法2
在第二种方法中,我们进一步采用了方法1,并尝试使用 Shared Project 解决方法1中遇到的问题。从文档中:
Shared Projects使您可以编写由引用的通用代码。 不同应用程序项目的数量。该代码被编译为一部分 每个引用项目,并且可以包括用于 帮助将特定于平台的功能整合到共享代码中 基地。
Shared Project在没有constructor
文件的丑陋和不必要的link
或inheritance
类的情况下为我们两全其美。我们的新设置如下: