はじめに
JUnitでテストを書いていると、「ローカルでは実行したいけど、CI環境などでは実行したくない/できない」テストが出てくることがあります。例えば、CI環境からは接続できないサーバーへのアクセスが必要な場合などです。
こういう場合の回避策について、やってみたことの備忘録です。
@DisabledIfEnvironmentVariableを付与し特定条件で無効化
JUnit 5 には、環境変数の値に応じてテストを無効化する@DisabledIfEnvironmentVariableというアノテーションがあります。
クラス単位でも、メソッド単位でも付与することができます。
CI環境では、実行環境を識別するためにCIという環境変数が使われることがあります。
例えばGitHub Actionsでは、デフォルトでCI=trueが設定されます。
両者を組み合わせると、CI=trueが設定されている環境でのみ、特定のテストを無効化できます。
たとえばこんな感じです。
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable
class ExternalServerTest {
@Test
@DisabledIfEnvironmentVariable(
named = "CI",
matches = "true",
disabledReason = "CI環境から対象サーバーへ接続できないため",
)
fun connectToExternalServer() {
// 外部サーバーへの接続が必要なテスト
}
}
この例だとconnectToExternalServerというテストのみ、CIがtrueの環境で無効化されます。
matchesには環境変数の値を正規表現で指定します。環境変数が存在しなかったり、値がmatchesと合致しなかったりすると、通常通り実行されます。
ちなみに無効化されたテストは、テスト結果ではスキップ扱いになります。
さきほどの例のようにdisabledReasonが設定されていたら、対応しているテスト実行環境では、理由もあわせて表示されます。disabledReasonはJUnit 5.7から利用できる項目です。
逆方向の@EnabledIfEnvironmentVariableもある
ちなみに、逆方向、つまり「環境変数が特定の値の場合のみ有効化する」ためのアノテーションもあります。
@EnabledIfEnvironmentVariableです。
もっとキレイにするなら@Tagかも
@DisabledIfEnvironmentVariableによるテストの無効化は、手軽ではありますが最善のアプローチではないかもしれません。
というのも、コードとしては「CI環境で実行しない」以上の意味は表現できていないからです。先述のdisabledReasonを使えば情報は増やせますが、構造で表現できていない事実は変わりません。
今回の件で本当に表現したいのは、「外部サーバーへの接続が必要なテスト」であるということです。
こうした分類をより正確に行うには、@Tagがより適しています。
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
class ExternalServerTest {
@Test
@Tag("external")
fun connectToExternalServer() {
// 外部サーバーへの接続が必要なテスト
}
}
あとはテスト設定などで、該当のタグを除外します。
tasks.test {
useJUnitPlatform {
if (System.getenv("CI") == "true") {
excludeTags("external")
}
}
}
この方がテストの性質をより明確に表現できますし、外部接続の必要なテストが増えたとしてもまとめて制御できます。
上記の例はシンプルな構成ですが、性質に応じて、より細かい条件での制御なども可能です。
ただ、分類を定めたりビルド設定を整えたりするのには少々手間がかかるので、「とりあえずCI環境で実行したくない」程度であれば@DisabledIfEnvironmentVariableから始めるのでも十分だと思います。
テストの数が増えたり、CI環境でスキップしたい理由が複数に分かれ始めたりしたら、そのタイミングで@Tagの導入を検討するくらいの使い分けが良いかもしれませんね。