前回の続き。
アサインされたJavaプロジェクトではSpring Bootを使用しているが、ユニットテストではモックが使われていなかった。
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
でWebサーバーを起動し、 TestRestTemplate
でControllerクラスのメソッドを実行しているため、テスト実行まで数分かかる。
また、Repositoryに対する個別のテストは書かれておらず、ControllerやServiceから間接的にテストされていた。アクセスするDBは processTestResources
にてローカルにH2 Databaseのファイルを作成し、ControllerやServiceのテストクラスのsetupメソッドでデータ削除を実行したりと、実行にも時間がかかる。
こうした場合、速度(Slow/Fast)やテストサイズ(Small/Medium/Large)でテストをグループ化するが、引数を渡して test
タスク実行では面倒なので、別途タスク化して実行したい。さらに、追加したタスクが指定された場合、H2 Databaseは不要なので、DBファイル作成処理は実行させないようにしたい。調べてみたらとりあえず実現できたのでメモ。
環境
Groovy v3.0.3, Gradle v6.3。
テストのグルーピング
検索すればたくさん出てくるが、SpockはJUnit4を利用しているため、 @Category
によるカテゴリ化テストを利用できる。
Gradleからは test.useJUnit(Closure)
で includeCategories
や excludeCategories
として対象のクラス名を指定すればいい。
Test - Gradle DSL Version 6.3
@Category
には任意のクラスを渡せるため、StringなりObjectなりでもいいが、一応クラスを作っておく。インスタンス生成できないよう、finalかつprivateコンストラクタを持ったクラスを作成。
package com.example
public final class FastTests {
private FastTests() {
}
}
@SpringBootTest
を付与していない、シンプルなテストクラスに @Category(FastTests.class)
として付与しておく。
Gradleタスクとしてカテゴリ化テストを切り出し
以下の記事に方法が全部書いてあった。
mike-neck.hatenadiary.com
gradle fastTest
で実行できるよう、 build.gradle
にタスク定義を記述。オプションで出力レベルを変更させたくないため、 lifecycle
ログの出力設定を追加し、以下のようになった。
task fastTest(type: Test, dependsOn: testClasses) {
useJUnit {
includeCategories 'com.example.FastTests'
}
testLogging.lifecycle {
events 'standard_out'
exceptionFormat 'full'
showStandardStreams true
}
afterSuite { TestDescriptor desc, TestResult ret ->
if (!ret.testCount) {
return
}
def hasClassName = !!desc.className
if (!hasClassName && desc.parent) {
return
}
def descName = hasClassName ? "${ret.resultType} ${desc.className}" : 'Results :'
def retMessage = [
'Tests run': ret.testCount,
Failures: ret.failedTestCount,
Errors: ret.exceptions.size(),
Skipped: ret.skippedTestCount
].collect {
"${it.key}: ${it.value}"
}.join(', ') + (hasClassName ? ", Time elapsed: ${(ret.endTime - ret.startTime) / 1000} sec" : '')
logger.lifecycle "${descName}\n${retMessage}"
}
}
これで、 gradle fastTest
でログ出力しつつ @Category(FastTests.class)
を付与したテストクラスを実行できるようになった。
指定されたタスクによる処理の切り分け
H2 Databaseのファイル作成処理は createTestDB
タスクとして記述され、 processTestResources
に dependsOn
されている。
fastTest
タスクが実行されるのはローカルでのユニットテスト実行に限定できるため、これが指定されたときは依存関係設定をしなければいい。
指定されたタスク名は project.gradle.startParameter.taskNames
で取得できる。Javadocによると taskNames
は List<String>
のため、以下の記述で実現できた。
if ('fastTest' !in project.gradle.startParameter.taskNames) {
processResources.dependsOn createTestDB
}
振り返り
Spring Bootのテストだからとナイーブに @SpringBootTest
を使ってしまうと、テストが遅くなりがち。
ローカルで何度も実行することを考えると、Smallテストの実行は30秒以内に終わらせたい。
ただ、テストが遅くなったから修正しようにも、なかなかテストコードのリファクタリングをする時間は取れない。
事前にテスト記述のルールを決めておくなど、ある程度の準備は必要だと思った。
Serviceの結合テストではDIできればいいので @SpringBootTest(webEnvironment = WebEnvironment.NONE)
を使う、単体テストとしてモックを使ったテストを記述してSmallテストとして実行できるようにするなど、テスト自体の改善もしないとなぁ。
以下、テスト記述の参考。
備考
Spockのテストのグルーピングは、 SpockConfig.groovy
を使えばアノテーションでもできる模様。 SpockConfig.groovy
についての知識がなく、起動時のパラメータで切り替える必要がありそうだったので、今回は見送り。