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

时间:2009-09-10 23:04:27

标签: c# .net unit-testing xunit.net

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

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

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

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

11 个答案:

答案 0 :(得分:68)

如上所述,所有良好的单元测试应该是100%隔离的。使用共享状态(例如,取决于每个测试修改的public function handle(BarPageWasViewed $event) { if(count($event->bar->views) === 0) { $event->bar->views()->create(['views' => 1]); } else { $event->bar->views()->increment('views'); } } 属性)被视为不良做法。

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

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

对于每个组件分离使用:

CollectionBehavior

或者根本不使用并行化:

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

后者可能就是你想要的那个。有关并行化和配置的更多信息,请参见xUnit documentation

答案 1 :(得分:45)

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

在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

答案 2 :(得分:38)

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

答案 3 :(得分:13)

对于.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中关闭并行化,也会导致测试并行运行。微软的UI设计人员狡猾地将这个按钮取消标记,很难注意到,并且距离测试资源管理器中的“全部运行”按钮大约一厘米,只是为了最大限度地提高误操作的机会并且不知道为什么你的测试突然失败了:

Screenshot with the button circled

答案 4 :(得分:5)

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

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

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

答案 5 :(得分:5)

您可以使用播放列表

右键点击测试方法 - &gt;添加到播放列表 - &gt;新播放列表

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

enter image description here

答案 6 :(得分:3)

也许你可以使用Advanced Unit TestingIt 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.");
  ...
}

请告诉我它是否有效。

答案 7 :(得分:2)

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

注意::我在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”)属性的其他测试。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

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

private static final long serialVersionUID = 1L;

private static Logger log = Logger.getLogger(CsvFileUploadInterceptor.class);

@Override
protected String getTextMessage(Object action, String messageKey, String[] args) {
    String actionName = action.getClass().getName();
    if (actionName.endsWith(WebAppConst.CSV_UPLOAD_ACTION)) {
        if (messageKey.equals(WebAppConst.STRUTS_MESSAGES_ERROR_FILE_TOO_LARGE)) {
            messageKey = WebAppConst.CSV_UPLOAD_ERROR_FILE_TOO_LARGE;
        } else if (messageKey.equals(WebAppConst.STRUTS_MESSAGES_ERROR_CONTENT_TYPE_NOT_ALLOWED)) {
            messageKey = WebAppConst.CSV_UPLOAD_ERROR_CONTENT_TYPE_NOT_ALLOWED;
        }
    }
    return super.getTextMessage(actionName, messageKey, args);

}

答案 10 :(得分:-1)

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

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


      public class TestClass1 : SequentialTest
      {
      ...
      }

      public class TestClass2 : SequentialTest
      {
      ...
      }
    }