串行(而不是并行)执行单元测试

我正在尝试单元测试我编写的一个 WCF 主机管理引擎。该引擎基本上是根据配置动态创建 ServiceHost 实例。这使我们可以动态地重新配置哪些服务可用,而无需关闭所有服务,并在添加新服务或删除旧服务时重新启动它们。

然而,由于 ServiceHost 的工作方式,我在对这个主机管理引擎进行单元测试时遇到了困难。如果已经为特定端点创建、打开和尚未关闭 ServiceHost,则无法为同一端点创建另一个 ServiceHost,从而导致异常。由于现代单元测试平台并行化了它们的测试执行,因此我没有有效的方法来对这段代码进行单元测试。

我使用了 xUnit.NET,希望由于它的可扩展性,我可以找到一种方法来强制它串行运行测试。然而,我没有任何运气。我希望在 SO 上有人遇到过类似的问题,并且知道如何让单元测试连续运行。

注意: 服务主机是一个 WCF 类,由 Microsoft 编写。我没有能力改变它的行为。只承载每个服务端点一次也是正确的行为... ... 然而,它并不特别有利于单元测试。

118870 次浏览

我不知道细节,但它听起来像你可能试图做 集成测试而不是 单元测试。如果能够隔离对 ServiceHost的依赖,那么可能会使您的测试更容易(和更快)。因此(例如)您可以独立测试以下内容:

  • 配置读取类
  • ServiceHost 工厂(可能作为集成测试)
  • 接受 IServiceHostFactoryIConfiguration的引擎类

有助于包括隔离(模拟)框架和(可选) IoC 容器框架的工具。参见:

也许您可以使用 高级单元测试.它允许您定义运行测试的顺序。所以您可能必须创建一个新的 cs 文件来承载这些测试。

下面介绍如何按照所需的顺序弯曲测试方法。

[Test]
[Sequence(16)]
[Requires("POConstructor")]
[Requires("WorkOrderConstructor")]
public void ClosePO()
{
po.Close();


// one charge slip should be added to both work orders


Assertion.Assert(wo1.ChargeSlipCount==1,
"First work order: ChargeSlipCount not 1.");
Assertion.Assert(wo2.ChargeSlipCount==1,
"Second work order: ChargeSlipCount not 1.");
...
}

一定要让我知道它是否有效。

你可以使用 播放列表

右键单击测试方法-> 添加到播放列表-> 新建播放列表

然后您可以指定执行顺序,默认是,当您将它们添加到播放列表中时,但是您可以根据需要更改播放列表文件

enter image description here

重要提示: 此答案适用于.NETFramework。有关 dotnet 核心,请参阅 Dimitry 关于 xunit.runner.json的答案。

所有好的单元测试都应该是100% 隔离的。使用共享状态(例如,根据每个测试修改的 static属性)被认为是不好的做法。

话虽如此,您关于按顺序运行 xUnit 测试的问题确实有了答案!我遇到了完全相同的问题,因为我的系统使用了静态服务定位器(这不太理想)。

默认情况下,xUnit 2.x 并行运行所有测试。通过在测试项目的 AssemblyInfo.cs 中定义 CollectionBehavior,可以对每个程序集进行修改。

对于每个组件的分离使用:

using Xunit;
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]

或者根本不使用并行:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

后者可能是你想要的。更多关于并行化和配置的信息可以在 XUnit 文档上找到。

对于.NET Core 项目,使用以下命令创建 xunit.runner.json:

{
"parallelizeAssembly": false,
"parallelizeTestCollections": false
}

此外,您的 csproj应该包含

<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

对于旧的.Net Core 项目,您的 project.json应该包含

"buildOptions": {
"copyToOutput": {
"include": [ "xunit.runner.json" ]
}
}

对于.NET Core 项目,您可以使用 xunit.runner.json文件配置 xUnit,如 https://xunit.net/docs/configuration-files所示。

要停止并行测试执行,您需要更改的设置是 parallelizeTestCollections,它默认为 true:

如果程序集愿意在此程序集内并行地相互运行测试,则将此值设置为 true。... 将此设置为 false以禁用此测试程序集内的所有并行化。

JSON 模式类型: 布尔型
默认值: true

因此,用于此目的的最小 xunit.runner.json看起来像

{
"parallelizeTestCollections": false
}

正如文档中指出的,请记住在构建中包含这个文件,可以通过以下方式:

  • 在 VisualStudio 中将文件的 物业中的 复制到输出目录设置为 如果更新,请复制,或者

  • 添加

      <Content Include=".\xunit.runner.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    

    你的 .csproj档案,或

  • 添加

      "buildOptions": {
    "copyToOutput": {
    "include": [ "xunit.runner.json" ]
    }
    }
    

    你的 project.json文件

取决于您的项目类型。

最后,从 此外到上面,如果你使用 Visual Studio,那么确保你没有不小心点击了 并行运行测试按钮,这将导致测试并行运行,即使你在 xunit.runner.json中关闭了并行化。微软的用户界面设计师巧妙地让这个按钮没有标签,很难注意到,并且离 Test Explorer 中的 “全速前进”按钮只有一厘米远,只是为了最大化你错误地按下它的几率,并且不知道为什么你的测试会突然失败:

Screenshot with the button circled

每个测试类都是一个唯一的测试集合,它下面的测试将按顺序运行,因此如果您将所有测试放在同一个集合中,那么它将按顺序运行。

在 xUnit 中,可以进行以下更改来实现这一点:

以下内容将同时进行:

namespace IntegrationTests
{
public class Class1
{
[Fact]
public void Test1()
{
Console.WriteLine("Test1 called");
}


[Fact]
public void Test2()
{
Console.WriteLine("Test2 called");
}
}


public class Class2
{
[Fact]
public void Test3()
{
Console.WriteLine("Test3 called");
}


[Fact]
public void Test4()
{
Console.WriteLine("Test4 called");
}
}
}

要使其顺序化,只需将两个测试类放在同一个集合下:

namespace IntegrationTests
{
[Collection("Sequential")]
public class Class1
{
[Fact]
public void Test1()
{
Console.WriteLine("Test1 called");
}


[Fact]
public void Test2()
{
Console.WriteLine("Test2 called");
}
}


[Collection("Sequential")]
public class Class2
{
[Fact]
public void Test3()
{
Console.WriteLine("Test3 called");
}


[Fact]
public void Test4()
{
Console.WriteLine("Test4 called");
}
}
}

更多信息,你可以参考 这个链接

我在一个基类中添加了属性 [集合(「序列」)]:

    namespace IntegrationTests
{
[Collection("Sequential")]
public class SequentialTest : IDisposable
...




public class TestClass1 : SequentialTest
{
...
}


public class TestClass2 : SequentialTest
{
...
}
}

这是一个老问题,但我想写一个解决方案的人搜索新喜欢我:)

注意: 我在 xunit 2.4.1版的 Dot Net Core WebUI 集成测试中使用了这个方法。

创建一个名为 NonParallelCollectionDefinition 类的空类,然后像下面这样将 CollectionDefinition 属性赋给该类。(重要的部分是 DisableParallelization = true 设置。)

using Xunit;


namespace WebUI.IntegrationTests.Common
{
[CollectionDefinition("Non-Parallel Collection", DisableParallelization = true)]
public class NonParallelCollectionDefinitionClass
{
}
}

然后将 Collection 属性添加到类中,您不希望它像下面那样并行运行。(重点是收藏品名称。它必须与 CollectionDefinition 中使用的名称相同)

namespace WebUI.IntegrationTests.Controllers.Users
{
[Collection("Non-Parallel Collection")]
public class ChangePassword : IClassFixture<CustomWebApplicationFactory<Startup>>
...

当我们这样做时,首先运行其他并行测试。然后运行其他具有 Collection (“ Non-ParallCollection”)属性的测试。

到目前为止,所有建议的答案都不适合我。 我通过在每个单元测试中放置一个锁来代替变通方法,从而实现了所需的行为。在我的例子中,我不关心运行顺序,只关心测试是连续的。

public class TestClass
{
[Fact]
void Test1()
{
lock (this)
{
//Test Code
}
}


[Fact]
void Test2()
{
lock (this)
{
//Test Code
}
}
}

对我来说,进去。Net Core 的控制台应用,当我想要同步运行测试方法(而不是类)时,唯一有效的解决方案就是这个博客中描述的: 控制测试执行顺序