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


96

我试图对我编写的WCF主机管理引擎进行单元测试。引擎基本上是根据配置动态创建ServiceHost实例。这使我们能够动态地重新配置哪些服务可用,而不必关闭所有服务并在添加新服务或删除旧服务时重新启动它们。

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

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

注意:ServiceHost是由Microsoft编写的WCF类。我没有能力更改其行为。仅将每个服务端点托管一次也是一种正确的行为……但是,这特别不利于单元测试。


您可能不想解决ServiceHost的这种特殊行为吗?
罗伯特·哈维

ServiceHost由Microsoft编写。我无法控制它。从技术上讲,这是一种有效的行为...每个终结点不应拥有多个ServiceHost。
jrista

1
我在尝试TestServer在docker中运行多个时遇到类似的问题。因此,我不得不序列化集成测试。
h-rai

Answers:


114

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

在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");
        }
    }
}

有关更多信息,请参考此链接


23
我认为答案不够充分。似乎可以正常工作,而且我喜欢粒度,因为我在一个程序集中具有可并行化和不可并行化的测试。
Igand

1
参考Xunit文档,这是执行此操作的正确方法。
哈康K. Olafsen

2
这应该是一个可以接受的答案,因为通常一些测试可以并行运行(在我的情况下是所有单元测试),但有些测试在并行运行时会随机失败(在我的情况下是使用内存中的Web客户端/服务器),因此一个是如果愿意,可以优化测试运行。
阿列克谢

2
在我使用sqlite数据库执行集成测试的.net核心项目中,这对我不起作用。测试仍然并行执行。接受的答案确实有效。
user1796440

非常感谢您的回答!因为我在不同的类中都有验收测试,所以它们都需要从同一个TestBase继承而来,并且并发在EF Core中表现不佳,因此需要这样做。
蓝晶石

104

如上所述,所有好的单元测试都应100%隔离。使用共享状态(例如,取决于static每个测试修改的属性)被视为不良做法。

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

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

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

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

或完全不使用并行化:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

后者可能是您想要的。有关并行化和配置的更多信息,请参见xUnit文档


5
对我来说,每个类中的方法之间都有共享资源。从一个类运行一个测试,然后从另一个类运行一个测试,将破坏两个类的测试。我能够通过使用解决[assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)]。感谢您,@ Squiggle,我可以运行所有测试并去喝杯咖啡!:)
Alielson Piffer

2
对于.NET Core,Abhinav Saxena的答案更为详尽。
Yennefer '19

65

对于.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" ]
  }
}

2
我假设最新的csproj dotnet核心等效<ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>或类似?

3
这对我在csproj中起作用:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd

禁用并行化是否适用于xUnit理论?
John Zabroski

这是唯一对我有用的东西,我尝试像这样跑步,dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=false但对我不起作用。
哈维

18

对于.NET Core项目,您可以使用xunit.runner.json文件配置xUnit ,如https://xunit.github.io/docs/configuring-with-json.html所述

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

设置为 true如果程序集愿意并行运行该程序集内的测试则将其。...设置false为禁用此测试程序集内的所有并行化。

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

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

{
    "parallelizeTestCollections": false
}

如文档中所述,请记住通过以下方式将该文件包含在您的版本中:

  • 设置“ 复制到输出目录”以在文件属性中更新时进行复制在Visual Studio中中,或者
  • 新增中

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

    到您的.csproj文件,或

  • 新增中

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

    到你的project.json文件

取决于您的项目类型。

最后,上述内容外,如果您使用的是Visual Studio,请确保您没有意外单击 “并行运行测试”按钮,这将导致测试并行运行,即使您已在中关闭了并行化xunit.runner.json。Microsoft的UI设计师巧妙地使此按钮不加标签,很难注意到,并且与Test Explorer中的“ Run All”按钮相距约一厘米,只是最大程度地增加了您误击它并且不知道为什么进行测试的机会突然失败:

屏幕截图(带圆圈的按钮)


@JohnZabroski我不明白您的建议编辑。ReSharper与什么有什么关系?我想我在上面写答案时可能已经安装了它,但是这里的内容是否与您是否使用它无关?您在编辑中链接到页面与指定xunit.runner.json文件有什么关系?与指定xunit.runner.json使测试连续运行有什么关系?
马克·阿默里

我试图让我的测试以串行方式运行,并且最初认为该问题与ReSharper有关(因为ReSharper不像Visual Studio Test Explorer那样具有“以并行方式运行测试”按钮)。但是,似乎当我使用[Theory]时,我的测试并不是孤立的。这很奇怪,因为我读到的所有内容都表明Class是最小的可并行化单元。
约翰·扎布罗斯基

8

这是个老问题,但我想为像我这样新搜寻的人写一个解决方案:)

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

创建一个名为NonParallelCollectionDefinitionClass的空类,然后将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-Parallel Collection”)属性的其他测试。


6

您可以使用播放列表

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

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

在此处输入图片说明


5

我不知道细节,但是听起来您可能正在尝试进行集成测试而不是单元测试。如果您可以隔离对的依赖关系ServiceHost,则可能会使您的测试更加轻松(而且更快)。因此,(例如)您可以独立测试以下内容:

  • 配置阅读课
  • ServiceHost工厂(可能是集成测试)
  • 带有IServiceHostFactory和的引擎类IConfiguration

可以帮助使用的工具包括隔离(模拟)框架和(可选)IoC容器框架。看到:


我不是要进行集成测试。我确实确实需要进行单元测试。我对TDD / BDD的术语和实践(IoC,DI,模拟等)非常精通,因此不需要像创建工厂和使用接口这样的工厂操作(已经完成,除了ServiceHost本身的情况之外。)ServiceHost不是一个可以隔离的依赖项,因为它不能正确地进行模拟(与许多.NET System命名空间一样。)我确实需要一种串行运行单元测试的方法。
jrista

1
@jrista-您的技能没什么微妙的意图。我不是WCF开发人员,但是引擎是否有可能通过Service包装器上的接口返回ServiceHost周围的包装器?还是为ServiceHosts定制的工厂?
TrueWill

托管引擎不返回任何ServiceHosts。它实际上不返回任何内容,它仅在内部管理ServiceHosts的创建,打开和关闭。我可以包装所有基本的WCF类型,但这是很多工作,实际上我没有被授权做。而且,事实证明,该问题不是由并行执行引起的,并且在正常操作期间仍然会发生。我在这里就这个问题开始了另一个问题,希望我能得到答案。
jrista

@TrueWill:顺便说一句,我根本不担心您会稍微削弱我的技能...我只是不想得到很多关于单元测试的常见问题的常规答案。我需要一个非常具体的问题的快速解答。对不起,如果我有点不高兴,那不是我的意图。我只有有限的时间来使这件事起作用。
jrista

3

也许您可以使用高级单元测试它允许您定义运行测试的顺序。因此,您可能必须创建一个新的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.");
  ...
}

让我知道它是否有效。


很棒的文章。实际上,我把它收藏在CP上。感谢您提供的链接,但事实证明,问题似乎更加严重,因为测试运行程序似乎无法并行运行测试。
jrista

2
等一下,首先您说您不希望测试并行运行,然后您说的问题是测试运行程序不并行运行测试...那么哪个是?
Graviton

您提供的链接不再有效。这是您可以使用xunit进行的操作吗?
艾伦·王


0

我在基类中添加了属性[Collection(“ Sequential”)]

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }

0

到目前为止,没有建议的答案对我有用。我有一个XUnit 2.4.1的dotnet核心应用程序。通过在每个单元测试中加一个锁,我通过一种解决方法实现了所需的行为。就我而言,我不在乎运行顺序,只是测试是顺序的。

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

    [Fact]
    void Test2()
    {
        lock (this)
        {
            //Test Code
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.