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的には、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
-
VsTestでの接続タイムアウトは
VSTEST_CONNECTION_TIMEOUT環境変数で調整できます。(デフォ90sec)↩