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

Posted

技术标签:

【中文标题】串行执行单元测试(而不是并行)【英文标题】:Execute unit tests serially (rather than in parallel) 【发布时间】:2010-11-27 08:21:40 【问题描述】:

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

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

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

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

【问题讨论】:

ServiceHost 的这种特殊行为难道不是您可能想要解决的问题吗? ServiceHost 由 Microsoft 编写。我无法控制它。从技术上讲,这是一种有效的行为……每个端点的 ServiceHost 不应该超过一个。 我在尝试在 docker 中运行多个 TestServer 时遇到了类似的问题。所以我不得不序列化集成测试。 【参考方案1】:

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

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

如果程序集愿意在此程序集中彼此并行运行测试,请将其设置为 true。 ... 将此设置为 false 以禁用此测试程序集中的所有并行化。

JSON 模式类型:boolean默认值:true

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


    "parallelizeTestCollections": false

如文档中所述,请记住将此文件包含在您的构建中,方法是:

在 Visual Studio 中的文件的 Properties 中将 Copy to Output Directory 设置为 Copy if newer,或

添加

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

到您的.csproj 文件,或

添加

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

到您的project.json 文件

取决于您的项目类型。

最后,除了上述之外,如果您使用的是 Visual Studio,请确保您没有意外单击 Run Tests In Parallel 按钮,该按钮即使您在xunit.runner.json 中关闭了并行化,也会导致测试并行运行。微软的 UI 设计师巧妙地将这个按钮设置为无标签、难以注意到,并且距离测试资源管理器中的 “全部运行” 按钮大约一厘米,只是为了最大限度地提高你误击它的机会并且不知道为什么您的测试突然失败:

【讨论】:

@JohnZabroski 我不明白你的suggested edit。 ReSharper 与任何事情有什么关系?我想我在写上面的答案时可能已经安装了它,但是这里的所有内容都不是与您是否使用它无关吗? page you link to in the edit 与指定 xunit.runner.json 文件有什么关系?指定xunit.runner.json 与使测试串行运行有什么关系? 我正在尝试让我的测试连续运行,最初认为该问题与 ReSharper 相关(因为 ReSharper 没有像 Visual Studio 测试资源管理器那样的“并行运行测试”按钮)。但是,似乎当我使用 [Theory] ​​时,我的测试并不是孤立的。这很奇怪,因为我读到的所有内容都表明 Class 是最小的可并行化单元。【参考方案2】:

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

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

更多信息可以参考this link

【讨论】:

我认为答案被低估了。似乎有效,我喜欢这种粒度,因为我在一个程序集中有可并行和不可并行的测试。 这是正确的方法,参考 Xunit 文档。 这应该是公认的答案,因为通常一些测试可以并行运行(在我的情况下是所有单元测试),但有些测试在并行运行时会随机失败(在我的情况下那些使用内存网络客户端/服务器),因此可以根据需要优化测试运行。 这在我使用 sqlite 数据库执行集成测试的 .net 核心项目中对我不起作用。测试仍然并行执行。接受的答案确实有效。 非常感谢您的回答!需要这样做,因为我在不同的类中都有接受测试,它们都继承自同一个 TestBase,并且并发性与 EF Core 不兼容。【参考方案3】:

重要提示:此答案适用于 .NET Framework。对于 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 documentation。

【讨论】:

对我来说,每个类中的方法之间都有共享资源。从一个班级运行测试,然后从另一个班级运行测试,会破坏两者的测试。我能够通过使用[assembly: CollectionBehavior(CollectionBehavior.CollectionPerClass, DisableTestParallelization = true)] 来解决。感谢@Squiggle,我可以运行所有测试并去喝杯咖啡! :) Abhinav Saxena 的回答对于 .NET Core 来说更加精细。【参考方案4】:

对我来说,在 .Net Core Console 应用程序中,当我想同步运行测试方法(而不是类)时,唯一有效的解决方案是这个博客中描述的: xUnit: Control the Test Execution Order

【讨论】:

【参考方案5】:

到目前为止,没有一个建议的答案对我有用。我有一个带有 XUnit 2.4.1 的 dotnet 核心应用程序。 我通过在每个单元测试中放置一个锁来实现所需的行为。就我而言,我并不关心运行顺序,只是测试是顺序的。

public class TestClass

    [Fact]
    void Test1()
    
        lock (this)
        
            //Test Code
        
    

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

【讨论】:

【参考方案6】:

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

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

创建一个名为 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") 属性的测试运行。

【讨论】:

这是在 .net 核心中对我有用的唯一解决方案,可以在同一集合中按顺序运行测试,并与同一程序集中的其他测试并行【参考方案7】:

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

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


      public class TestClass1 : SequentialTest
      
      ...
      

      public class TestClass2 : SequentialTest
      
      ...
      
    

【讨论】:

【参考方案8】:

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

【讨论】:

我假设最新的 csproj dotnet 核心等效项是 &lt;ItemGroup&gt;&lt;None Include="xunit.runner.json" CopyToOutputDirectory="Always" /&gt;&lt;/ItemGroup&gt; 或类似的? 这在 csproj 中对我有用:&lt;ItemGroup&gt; &lt;None Update="xunit.runner.json"&gt; &lt;CopyToOutputDirectory&gt;PreserveNewest&lt;/CopyToOutputDirectory&gt; &lt;/None&gt; &lt;/ItemGroup&gt; 禁用并行化是否适用于 xUnit Theories? 这是唯一对我有用的东西,我尝试像dotnet test --no-build -c Release -- xunit.parallelizeTestCollections=false 一样运行,但它对我不起作用。【参考方案9】:

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

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

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

http://www.mockobjects.com/ http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

【讨论】:

我不想做集成测试。我确实需要进行单元测试。我完全精通 TDD/BDD 术语和实践(IoC、DI、Mocking 等),所以像创建工厂和使用接口这样的工厂运行并不是我需要的(它已经完成了,除了 ServiceHost 本身。)ServiceHost 不是可以隔离的依赖项,因为它不能正确模拟(与 .NET 系统命名空间一样)。我真的需要一种方法来连续运行单元测试。 @jrista - 无意贬低您的技能。我不是 WCF 开发人员,但引擎是否有可能在 ServiceHost 周围返回一个包装器,并在包装​​器上有一个接口?或者也许是 ServiceHosts 的自定义工厂? 托管引擎不返回任何 ServiceHosts。它实际上不返回任何东西,它只是在内部管理 ServiceHosts 的创建、打开和关闭。我可以包装所有基本的 WCF 类型,但这是我没有真正被授权做的大量工作。此外,事实证明,该问题不是由并行执行引起的,并且在正常运行期间仍然会发生。我在这里提出了另一个关于这个问题的问题,希望我能得到答案。 @TrueWill:顺便说一句,我根本不担心你会忽视我的技能......我只是不想得到很多涵盖所有内容的普通答案关于单元测试的常见内容。我需要一个非常具体的问题的快速答案。抱歉,如果我有点粗鲁,那不是我的本意。我只有非常有限的时间来让这件事发挥作用。【参考方案10】:

您可以使用播放列表

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

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

【讨论】:

【参考方案11】:

也许你可以使用Advanced Unit Testing。 It allows you to define the sequence in which you run the test。因此,您可能必须创建一个新的 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 上添加了书签。感谢您提供链接,但事实证明,问题似乎要深得多,因为测试运行器似乎没有并行运行测试。 等等,首先你说你不希望测试并行运行,然后你说问题是测试运行器不并行运行测试......所以这是哪个? 您提供的链接不再有效。这是你可以用 xunit 做的事情吗?

以上是关于串行执行单元测试(而不是并行)的主要内容,如果未能解决你的问题,请参考以下文章

流星:使用npm包进行单元测试(而不是大气包)

为啥使用集成测试而不是单元测试是一个坏主意?

如何并行运行单元测试(MSTest)?

在开发和测试阶段而不是生产阶段激活单元测试

使用 QThreadPool 并行运行单元测试任务

python单元测试来测试循环而不是中断