ユニットテストを(並列ではなく)逐次実行


96

私が作成したWCFホスト管理エンジンを単体テストしようとしています。エンジンは基本的に、構成に基づいてオンザフライでServiceHostインスタンスを作成します。これにより、新しいサービスが追加されたり古いサービスが削除されたりするたびに、すべてのサービスを停止して再起動することなく、利用可能なサービスを動的に再構成できます。

ただし、ServiceHostの動作方法が原因で、このホスト管理エンジンの単体テストで問題が発生しました。特定のエンドポイントに対してServiceHostがすでに作成され、開かれ、まだ閉じられていない場合、同じエンドポイントの別のServiceHostを作成できず、例外が発生します。最近の単体テストプラットフォームはテストの実行を並列化しているため、このコードを単体テストする効果的な方法はありません。

私はxUnit.NETを使用しましたが、その拡張性のために、テストを逐次実行するように強制する方法を見つけることができると期待していました。しかし、私には運がありませんでした。SOの誰かが同様の問題に遭遇し、単体テストをシリアルに実行する方法を知っていることを願っています。

注:ServiceHostは、Microsoftによって作成されたWCFクラスです。私はその行動を変える能力がありません。各サービスエンドポイントを1回だけホストすることも適切な動作ですが、単体テストには特に役立ちません。


ServiceHostのこの特定の動作は、対処したいものではないでしょうか。
Robert Harvey

ServiceHostはMicrosoftによって作成されました。私はそれを制御できません。そして、技術的に言えば、これは有効な動作です...エンドポイントごとに複数のServiceHostを持つことはできません。
jrista 09/09/09

1
TestServerdockerで複数を実行しようとすると、同様の問題が発生しました。そのため、統合テストをシリアル化する必要がありました。
h-rai

回答:


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
過小評価の答えだと思います。機能しているようで、1つのアセンブリに並列化可能テストと並列化不可能テストがあるため、細分性が気に入っています。
Igand

1
これは、これを行う正しい方法です。Xunitのドキュメントを参照してください。
ホーコンK. Olafsen

2
通常、一部のテストは並行して実行できますが(私の場合はすべての単体テスト)、並行して実行するとランダムに失敗するテストがあります(私の場合は、メモリ内のWebクライアント/サーバーを使用しているテスト)。必要に応じて、テストの実行を最適化できます。
Alexei

2
これは、sqliteデータベースとの統合テストを実行する.netコアプロジェクトでは機能しませんでした。テストは引き続き並行して実行されました。受け入れられた答えはうまくいきました。
user1796440

この回答をどうもありがとう!同じTestBaseから継承した異なるクラスに受け入れテストがあり、同時実行性がEF Coreでうまく機能していなかったため、これを行う必要がありました。
Kyanite

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 2017

2
Abhinav Saxenaからの回答は、.NET Coreの方が詳細です。
Yennefer

64

.NET Coreプロジェクトの場合は、次のもので作成xunit.runner.jsonします。

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

また、あなたcsprojは含むべきです

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

古いネットコアプロジェクトでは、あなたがproject.json含まれている必要があります

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

2
最新のcsproj dotnetコアと同等のものは<ItemGroup><None Include="xunit.runner.json" CopyToOutputDirectory="Always" /></ItemGroup>似ていると思いますか?
Squiggle 2017

3
これはcsprojで私のために働きました:<ItemGroup> <None Update="xunit.runner.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
skynyrd '19年

並列化の無効化はxUnit Theoriesで機能しますか?
John Zabroski、

これは私にとってうまくいった唯一のものでした、私はのように走ってみましたdotnet test --no-build -c Release -- xunit.parallelizeTestCollections=falseが、それは私にとってはうまくいきませんでした。
Harvey、

18

.NET Coreプロジェクトの場合xunit.runner.jsonhttps://xunit.github.io/docs/configuring-with-json.htmlに記載されているように、ファイルを使用してxUnitを構成できます。

並列テストの実行を停止するために変更する必要が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を使用している場合は、[ Run Tests In Parallel ]ボタンを誤ってクリックしていないことを確認してください。これにより、で並列化をオフにした場合でも、テストが並列で実行されxunit.runner.jsonます。MicrosoftのUI設計者は、このボタンにラベルを付けず、見づらく、テストエクスプローラーの[ すべて実行 ]ボタンから約1センチ離れたところに配置しました突然失敗しています:

ボタンに丸を付けたスクリーンショット


@JohnZabroski 提案された編集を理解できません。ReSharperは何と関係がありますか?上記の回答を書いたときにおそらくインストールされていたと思いますが、それを使用しているかどうかに関係なく、すべてがここにあるわけではありませんか?編集でリンクするページはxunit.runner.jsonファイルの指定とどのように関係していますか?そしてxunit.runner.json、テストを連続的に実行することとの関係は何ですか?
Mark Amery、

テストをシリアルで実行しようとしていますが、最初は問題がReSharperに関連していると考えていました(ReSharperにはVisual Studioテストエクスプローラーのような[テストを並列で実行]ボタンがないため)。ただし、[理論]を使用すると、テストが分離されていないようです。私が読んだすべてのものがクラスが並列化可能な最小単位であることを示唆しているので、これは奇妙です。
John Zabroski、

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は、適切にモック可能ではないため、分離できる依存関係ではありません。
jrista 2009

1
@jrista-あなたのスキルに意図的なものはありませんでした。私はWCF開発者ではありませんが、ラッパーのインターフェイスを持つServiceHostのラッパーをエンジンが返すことは可能ですか?それともServiceHostsのカスタムファクトリですか?
TrueWill 2009

ホスティングエンジンはServiceHostsを返しません。実際には何も返しません。ServiceHostsの作成、オープン、クローズを内部で管理するだけです。すべての基本的なWCFタイプをラップできますが、これは実際には許可されていない多くの作業です。また、判明したように、この問題は並列実行が原因ではなく、通常の操作中にも発生します。私はここで問題について別の質問を始めました。うまくいけば、答えが得られます。
jrista 2009

@TrueWill:ところで、私はあなたが私のスキルを軽視することについて全く心配していませんでした...私はユニットテストに関するすべての一般的なものをカバーするありふれた答えをたくさん欲したくなかっただけです。非常に具体的な問題について迅速な回答が必要でした。私が少し不機嫌になった場合は申し訳ありませんが、私の意図ではありませんでした。私はこれを機能させるのにかなり限られた時間しかない。
jrista 2009

3

たぶん、あなたはAdvanced Unit Testingを使うことができます。テストを実行する順序を定義できます。そのため、これらのテストをホストするために新しい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 2009

2
待って、最初にテストを並行して実行したくないと言い、次に問題はテストランナーがテストを並行して実行しないことだと言います...それでどちらですか?
グラビトン

指定したリンクは機能しません。これはxunitでできることですか?
Allen Wang


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
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.