运行 针对 Visual Studio 中的多个配置的单一测试

Run single test against multiple configurations in Visual Studio

我们使用 xUnitMicrosoft.AspNetCore.TestHost.TestServer 设置了集成测试,以 运行 测试 Web API 运行ning on ASP.NET核心 2.2.

我们的 Web API 是一个单一的代码库,可以根据某些配置或应用程序设置差异单独部署多次,例如 country货币,等等

下图试图解释我们的部署设置:

我们希望确保我们的集成测试 运行 针对所有部署。

对于两个部署,XX` 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()
    {
        //...
    }
}

现在,我希望 运行 ItShouldExecuteTwice_AgainstTwoSeparateConfigurations 在 class MyTest 中针对两种不同的配置/应用程序设置或换句话说针对两种不同的配置/应用程序设置进行多次测试Visual Studio.

中的不同测试部署

有没有更好更优雅的方法来实现这个?

重构测试启动以允许根据测试需要对其进行修改

例如

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

    }
}

@Nkosi 的 post 非常适合我们的场景和我提出的问题。这是一种简单、干净且易于理解的方法,具有最大的可重用性。满分答案。

但是,有几个原因导致我无法继续采用该方法:

  • 在建议的方法中,我们不能 运行 只测试一个特定的 setting。这对我们来说很重要,因为在未来,可以 两个不同的团队维护他们的特定实施和部署。使用 Theory,要 运行 对所有测试只有一个 setting 变得有点困难。

  • 我们很可能需要为每个设置/部署使用两个单独的构建和部署管道。

  • 虽然 API 端点、RequestResponse 今天完全相同,但我们不知道它是否会继续如此我们的开发继续进行。

基于以上原因我们还考虑了以下两种方案:

方法一

有一个通用的 class 库,它具有通用的 FixtureTests 作为 abstract class

  • 项目Common.IntegrationTests

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()
    {
        //...
    }
}
  • 项目Setting1.IntegrationTests(参考文献Common.IntegrationTests

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)
    {
    }
}
  • 项目Setting2.IntegrationTests(参考文献Common.IntegrationTests

Setting1.IntegrationTests

类似的结构

这种方法为 运行/ 独立修改测试提供了可重用性和灵活性的良好平衡。然而,我仍然不是 100% 相信这种方法,因为它意味着对于每个常见的 Test class 我们需要有一个实现,除了调用 base 我们什么都不做constructor.

方法二

在第二种方法中,我们进一步采用了方法 1,并尝试使用 Shared Project 解决方法 1 中的问题。来自文档:

Shared Projects let you write common code that is referenced by a number of different application projects. The code is compiled as part of each referencing project and can include compiler directives to help incorporate platform-specific functionality into the shared code base.

Shared Project 为我们提供了两全其美的体验,没有丑陋的 link 文件和不必要的 class inheritanceabstraction。我们的新设置如下:

编辑: 我为此写了一篇博客 post,其中详细讨论了我们的用例和解决方案。这是 link:

https://ankitvijay.net/2020/01/04/running-an-asp-net-core-application-against-multiple-db-providers-part-2/