tech.guitarrapc.cóm

Technical updates

dotnet testにタイムアウトを仕掛ける

dotnet testをCIで実行していて、永遠に終わらないのを仕込んでしまったのですがどう対処するかです。

概要

  • dotnet test自体に--timeout引数はない
  • dotnet testとしてはRunsettingsのRunConfiguration.TestSessionTimeoutプロパティでtimeout制約を提供している
  • テスト全体の所要時間で決め打ちしてもいいならtimeoutコマンドを組み合わせることもできる
  • テストメソッドごとに[Fact(Timeout = int)]で提供も可能

特に、CircleCIをPerformance Planで実行していると、クレジットが目も当てられないことになります。

異常なテストを捕捉したい

捉えたい異常は、あるテストケースに誤って無限に実行されるバグを仕込んでしまった状況です。 今回の状況は、CircleCIのno_output_timeoutではタイムアウトがなされず、テストが何時間も実行されていました。

どんなテストプロジェクトなのか見通してみます。

  • 1つのテストプロジェクトには多くのテストケースを書いている
  • CIで普段からテストを実行しているのでdotnet testごとの所要時間は判明している
  • VSでも実行していることから、ほとんどのテストケースは1msで終わっていることが個別に判明している
  • まれにPRにおいて永遠にテスト実行されるバグがあり、dotnet testが終了しない
  • CI上は実行中のためfailedにならず気づけない

この状況下では、やりたいことはテストケース1つ1つを気にするのではなく、テスト自体が異常かどうかを判別したくなります。

打てる手は大きく2つありそうです。

  • テスト実行dotnet testをタイムアウトさせる
  • テストケースごとにタイムアウトを設定する

順にみてみましょう。

テスト実行dotnet testをタイムアウトさせる

実行環境は、 CircleCIです。

dotnet testをタイムアウトさせるということは、個別のテストケースは気にせず、テストがこれぐらいの時間で終わってほしいことを明示することになります。 テストケースが数多くあり、どこで無限に終わらないバグを作るか予測はムズカシイので、個別のテストケースにTimeoutをつけるよりも妥当そうです。

dotnet testをRunsettings.TestSessionTimeout で実行タイムアウトさせる

dotnet test --timeoutのようなパラメーターがアレば簡単に設定できそうですが、残念ながら--timeoutパラメーターは用意されていません。

dotnet test

dotnet test的には、RunSettingsのRunConfiguration.TestSessionTimeoutでタイムアウトを設定できます。

https://gist.github.com/guitarrapc/3c6f98398bead4133473103c9f71acaa

RunSettingsは、2つの方法で設定できます。

RunSettings をdotnet testの引数で実行時上書きする

dotnet testの引数には、RunSettings argumentsを使ったRunSettingsの実行時上書きが提供されています。 RunSettingsの指定は、[name]=[value]ペアを--引数の後ろで指定すると利用できます。

TestSessionTimeoutはRunConfigurationの子要素で、ミリ秒で指定します。 例えば、テスト実行を1秒でタイムアウトするように指定するにはこう書きます。

dotnet test  -- RunConfiguration.TestSessionTimeout=1000

このパラメーターは、テスト実行時間自体を見ているのでテスト開始前の時間は無視されます、純粋にテスト実行時間で指定すればいいので最高です。1

RunSettings をファイルで指定する

dotnet test-s--settings引数を利用すると、RunSettingsを定義した.runsettingsファイルを指定できます。

例えば先ほどのSample.runsettingsを指定するなら次のように書きます。

dotnet test --settings Sample.runsettings

ただ、.runsettingsファイルを指定するにはテストプロジェクトごとにrunsettingsファイルのパスを仕込む必要があります。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <RunSettingsFilePath>$(SolutionDir)\example.runsettings</RunSettingsFilePath>
  </PropertyGroup>
  ...
</Project>

Configure unit tests with a .runsettings file - Visual Studio | Microsoft Docs

実行時の引数指定のほうがやりやすいので、私はこの方法を避けています。

timeout コマンドでdotnet testをタイムアウトさせる

.NETCoreを想定しているので、CI環境はLinuxであることが多いです。 ということは、安定のtimeoutコマンドをつかってdotnet test全体の時間に制約をかけることもできます。

timeout -sKill SECOND dotnet test

dotnet test自体の実行時間が定まっているのであればなかなか便利なのでこれもおすすめです。

テストケースごとにタイムアウトを設定する

xUnitであれば、[Fact(Timeout = int)]を使ってテストケースごとにタイムアウトを仕掛けることができます。

https://gist.github.com/guitarrapc/38a4e90ae7280ac58721015b7bd9052d

あらかじめ、このテストは時間がかかるなど個別にタイムアウトをさせたいケースでとても有用です。 特に、nightlyバッチなどで実行させる時間のかかるテストでは個別に設定するのはうれしいでしょう。

実行側はタイムアウトを知らなくていいので、並列実行でも制御しやすいですしね。

まとめ

dotnet testで無限テスト実行、やらないとは言い切れません。そんな時はtimeoutを設定してあげましょう。

REF


  1. VsTestでの接続タイムアウトはVSTEST_CONNECTION_TIMEOUT環境変数で調整できます。(デフォ90sec)