理论上按顺序执行 Xunit 测试(非并行)

Execute Xunit tests in theory sequentially (not parallel)

我们在 git 存储库中托管不同环境的配置文件。作为 CI 过程的一部分,我想确保这些配置文件始终有效。为此,我创建了这个测试,它复制配置,尝试启动服务器并立即关闭它。

public class DeployConfigurationValidationTests
{
    #region Private Fields

    private readonly ITestOutputHelper _testOutputHelper;
    private const string ServerBaseUrl = "http://localhost:44315";

    #endregion

    #region Constructors

    public DeployConfigurationValidationTests(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }

    #endregion

    #region Public Tests

    /// <summary>
    /// Copies all files contained in the directory specified by parameter <see cref="deployConfigDirectoryPath"/> to the executing directory and launches the application with this configuration.
    /// </summary>
    /// <param name="deployConfigDirectoryPath">The path of the directory containing the deploy configurations</param>
    [Theory]
    [InlineData("../../../../../Configurations/Dev/")]
    [InlineData("../../../../../Configurations/Int/")]
    [InlineData("../../../../../Configurations/Prod/")]
    public async Task ValidateDeployConfigurationsTest(string deployConfigDirectoryPath)
    {
        // Arrange (copy deploy configurations into directory where the test is running)
        var currentDirectory = Directory.GetCurrentDirectory();
        var configurationFilePaths = Directory.GetFiles(deployConfigDirectoryPath);
        foreach (var configurationFilePath in configurationFilePaths)
        {
            var configurationFileName = Path.GetFileName(configurationFilePath);
            var destinationFilePath = Path.Combine(currentDirectory, configurationFileName);
            File.Copy(configurationFilePath, Path.Combine(currentDirectory, destinationFilePath), true);
            _testOutputHelper.WriteLine($"Copied file '{Path.GetFullPath(configurationFilePath)}' to '{destinationFilePath}'");
        }

        // Act (launch the application with the deploy config)
        var hostBuilder = Program.CreateHostBuilder(null)
                                 .ConfigureWebHostDefaults(webHostBuilder =>
                                                           {
                                                               webHostBuilder.UseUrls(ServerBaseUrl);
                                                               webHostBuilder.UseTestServer();
                                                           });

        using var host = await hostBuilder.StartAsync();

        // Assert
        // Nothing to assert, if no error occurs, the config is fine
    }

    #endregion
}

测试在 运行 单独 InlineData 时工作正常,但在 运行 理论时失败,因为默认情况下测试是 运行 并行的。使用相同的 DLL 在同一个端口上启动多个(测试)服务器显然是行不通的。

问题:如何告诉 xUnit 按顺序 运行 这些测试?

我们使用 .net core 3.1 和 XUnit 2.4.1

解决此问题的一种方法是使用 CollectionAttribute.

很遗憾,您只能将此属性应用于 classes

因此,您需要像这样进行小型重构:

internal class ValidateDeploymentConfigBase
{
    public async Task ValidateDeployConfigurationsTest(string deployConfigDirectoryPath)
    {
        // Arrange
        var currentDirectory = Directory.GetCurrentDirectory();
        var configurationFilePaths = Directory.GetFiles(deployConfigDirectoryPath);
        foreach (var configurationFilePath in configurationFilePaths)
        {
            var configurationFileName = Path.GetFileName(configurationFilePath);
            var destinationFilePath = Path.Combine(currentDirectory, configurationFileName);
            File.Copy(configurationFilePath, Path.Combine(currentDirectory, destinationFilePath), true);
            _testOutputHelper.WriteLine($"Copied file '{Path.GetFullPath(configurationFilePath)}' to '{destinationFilePath}'");
        }


        var hostBuilder = Program.CreateHostBuilder(null)
                                    .ConfigureWebHostDefaults(webHostBuilder =>
                                    {
                                        webHostBuilder.UseUrls(ServerBaseUrl);
                                        webHostBuilder.UseTestServer();
                                    });

        // Act
        using var host = await hostBuilder.StartAsync();
    }
}

然后您的测试用例将如下所示:

[Collection("Sequential")]
internal class ValidateDevDeploymentConfig: ValidateDeploymentConfigBase
{
    [Fact]
    public async Task ValidateDeployConfigurationsTest(string deployConfigDirectoryPath)
    {
        base.ValidateDeployConfigurationsTest("../../../../../Configurations/Dev/");
    }
}

...

[Collection("Sequential")]
internal class ValidateProdDeploymentConfig : ValidateDeploymentConfigBase
{
    [Fact]
    public async Task ValidateDeployConfigurationsTest(string deployConfigDirectoryPath)
    {
        base.ValidateDeployConfigurationsTest("../../../../../Configurations/Prod/");
    }
}