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

时间:2019-12-06 06:39:00

标签: c# asp.net-core .net-core integration-testing xunit

我们使用xUnitMicrosoft.AspNetCore.TestHost.TestServer设置了集成测试,以针对在ASP.NET Core 2.2上运行的Web API运行测试。

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

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

enter image description here

我们要确保我们的集成测试能够针对所有部署运行。

对于这两个部署, 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_SETTING1DEBUG_SETTING2)和预处理程序指令(#if DEBUG_SETTING1)的组合来实现这一目标。

  • 另一种选择是拥有一个具有通用方法的基础测试帮助程序项目,并为每个部署提供一个单独的集成项目。

有没有更好,更优雅的方法来实现这一目标?

2 个答案:

答案 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端点RequestResponse完全相同,但我们不知道在开发过程中是否会继续如此。

由于上述原因,我们还考虑了以下两种方法:

方法1

具有一个公共class库,该库具有Fixture类的公共Testsabstract

Approach 1: Project Structure

  • 项目 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类,我们都需要一个实现,除了调用base {{1} }。

方法2

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

  

Shared Projects使您可以编写由引用的通用代码。   不同应用程序项目的数量。该代码被编译为一部分   每个引用项目,并且可以包括用于   帮助将特定于平台的功能整合到共享代码中   基地。

Shared Project在没有constructor文件的丑陋和不必要的linkinheritance类的情况下为我们两全其美。我们的新设置如下:

Approach 2: Project Structure