tech.guitarrapc.cóm

Technical updates

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

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

目次

TL;DR

  • 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つ一つを気にするのではなく、テスト自体が異常かどうかを判別したくなります。

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

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

順にみてみましょう。

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

実行環境は、 CircleCI です。

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

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

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

docs.microsoft.com

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

gist.github.com

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)] を使ってテストケースごとにタイムアウトを仕掛けることができます。

gist.github.com

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

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

まとめ

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

REF


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