Kotest 태그를 사용하여 테스트 그룹화(Grouping Tests with Tags)
Tag 생성 및 정의
모든 테스트를 실행하고 싶지 않은 경우가 있는데, 이 경우 Kotest는 런타임에 어떤 테스트를 실행할지 결정할 수 있는 태그를 제공한다.
Tag 상속 받아 생성
태그는 io.kotest.core.Tag
에서 상속하는 객체이다.
예를 들어, 운영 체제별로 테스트를 그룹화하려면 다음 태그를 정의할 수 있다:
import io.kotest.core.Tag
class Windows : Tag()
class Linux : Tag()
NamedTag
클래스를 사용하여 정의
또는, NamedTag
클래스를 사용하여 태그를 정의할 수도 있다.
예를 들어, 다음과 같다.
val linux = NamedTag("Linux")
이 NamedTag
클래스를 사용할 시에는 다음 규칙을 준수해야 한다:
- 태그는 null이거나 공백이어서는 안 된다.
- 태그는 공백을 포함하지 않아야 한다.
- 태그에는 ISO 제어 문자가 포함되어서는 안 된다.
- 태그에는 다음 문자가 포함되지 않아야 한다:
!
: 느낌표(
: 왼쪽 소괄호)
: 오른쪽 소괄호&
: 앰퍼샌드|
: 파이프
테스트 마킹
생성된 Tag로 config
함수를 사용하여 테스트 케이스에 태그를 표시할 수 있다:
import io.kotest.core.spec.style.StringSpec
class TagConfigTest : StringSpec() {
init {
"should run on Windows".config(tags = setOf(Windows)) {
// ...
}
"should run on Linux".config(tags = setOf(Linux)) {
// ...
}
"should run on Windows and Linux".config(tags = setOf(Windows, Linux)) {
// ...
}
}
}
태그를 사용하여 실행
시스템 속성인 kotest.tags
로 테스트 실행기를 호출하여 어떤 테스트가 실행되는지 제어할 수도 있다. 전달할 표현식은 부울 연산자(&
, |
, !
)를 사용하는 간단한 부울 표현식이다. 이를 괄호로 묶어 연결할 수도 있다.
예를 들어, 다음과 같다
Tag1 & (Tag2 | Tag3)
테스트를 실행할 때 태그 객체의 간단한 이름(패키지 제외)을 입력한다. 대문자와 소문자 사용에 주의해야 한다! 두 태그 객체의 간단한 이름이 동일한 경우(다른 이름 공간에서) 동일한 태그로 취급된다.
예를 들면, Linux로 태그가 지정된 테스트만 실행하고 데이터베이스 태그가 지정된 테스트는 실행하지 않으려면 다음과 같이 Gradle을 호출한다:
gradle test -Dkotest.tags="Linux & !Database"
이렇게 시스템 속성(kotest.tags
)을 넣으면 Linux 이고 Database 아닌 경우만 실행이 된다.
주의
운영 체제에 따라 !
가 적용이 안되는 경우도 있을 수 있다. 그럴 경우에는 \
으로 이스케이프를 해야 할 수도 있다.
./gradlew test -Dkotest.tags="Linux & \!Database"
그리고, Gradle 시스템 속성 전달받기 위한 설정을 따로 하지 않으면 동작하지 않는다.
태그는 런타임에 포함/제외할 수도 있다(예를 들면, 속성 대신 프로젝트 구성을 실행하는 경우) RuntimeTagExtension
을 통한다:
RuntimeTagExpressionExtension.expression = "Linux & !Database"
태그 표현식 연산자
연산자(우선 순위 내림차순)
연산자 | 의미 | 사용 예 |
---|---|---|
! |
not | !macos |
& |
and | linux & intergration |
| |
or | windows |
모든 테스트에 태그 지정
스펙 자체의 tags
함수를 사용하면, 스펙의 모든 테스트에 태그를 추가할 수도 있다.
예를 들어, 아래와 같이 tags
함수로 태그를 지정할 수 있다.
import io.kotest.core.spec.style.FunSpec
class TagFuncTest : FunSpec({
tags(Linux, MySql)
test("my test") { } // automatically marked with the above tags
})
CAUTION
이러한 방식으로 테스트에 태그를 지정하는 경우 테스트 자체에서 추가 태그를 정의할 수 있으므로 각 테스트의 태그를 검사하려면 여전히 스펙 클래스를 인스턴스화해야 한다.NOTE
런타임 시에 활성화된 루트 테스트가 없으면,beforeSpec
및 afterSpec
의 콜백은 호출되지 않는다.
스펙에 태그로 지정
스펙 클래스에 추가할 수 있는 어노테이션은 @Tags
와 @RequiresTag
이렇게 두 가지로, 하나 이상의 태그 이름을 인수로 사용할 수 있다.
@Tags
첫 번째 태그인 @Tags
어노테이션는 클래스 내의 모든 테스트에 적용될 것이다. 이는 특정 태그가 명시적으로 제외되어 테스트가 실행되지 않을 경우에 해당 스펙의 인스턴스화되지 않는다.
Tags를 사용하는 기본적인 방법은 다음과 같다:
예를 들어, 일부로 느린 테스트를 실행하지 않으려 할 수 있다.
import io.kotest.core.spec.style.FreeSpec
import io.kotest.core.annotation.Tag
@Tags("fast")
class MyFastTests : FreeSpec({
"테스트 1" {
// 테스트 내용
}
})
@Tags("slow")
class MySlowTests : FreeSpec({
"테스트 2" {
// 테스트 내용
}
})
테스트를 kotest.tags=fast
를 지정하면 fast
로 지정된 테스트만 실행된다.
여러 태그를 함께 사용하여 특정 조건에 맞는 테스트를 실행할 수 있다.
@Tags("fast", "smoke")
class MyFastSmokeTests : FreeSpec({
"테스트 3" {
// 테스트 내용
}
})
위 코드는 fast
와 smoke
두 가지 태그가 모두 지정된 테스트를 나타낸다. 이 경우 kotest.tags
시스템을 사용하여 두 태그 중 하나 또는 두 태그 모두를 고려하여 실행할 수 있다.
다음은 @Tags
와 config
및 tags
함수에 태그를 지정한 경우, 시스템 속성(kotest.tags
)에 명령이 어떻게 동작을 하는 다음 예제를 통해 알아보겠다:
@Tags("Linux")
class MyTestClass : FunSpec({
tags(UnitTest)
beforeSpec { println("Before") }
test("A").config(tags = setOf(Mysql)) {}
test("B").config(tags = setOf(Postgres)) {}
test("C") {}
})
런타임 태그 | 스펙 생성 | 콜백 호출 | 결과 |
---|---|---|---|
kotest.tags=Linux |
Y | Y | 모든 테스트가 어노테이션에서 Linux 태그를 상속하기 때문에 A, B, C가 실행된다. |
kotest.tags=Linux & Mysql |
Y | Y | 모든 테스트에 Linux 태그가 있지만 A에만 Mysql 태그가 있기 때문에 A만 실행된다. |
kotest.tags=!Linux |
N | N | 테스트가 실행되지 않으며, 태그 어노테이션에 따라 제외할 수 있으므로 MyTestClass 가 인스턴스화되지 않는다. |
kotest.tags=!UnitTest |
Y | N | 모든 테스트가 태그 함수에서 UnitTest 를 상속하므로 테스트가 실행되지 않는다.MyTestClass 는 클래스에 정의된 태그를 검색하기 위해 인스턴스화된다. 활성 테스트가 없기 때문에 beforeSpec 콜백이 실행되지 않는다. |
kotest.tags=Mysql |
Y | Y | A만 실행되는데, 이는 Mysql로 표시된 유일한 테스트이기 때문이다. |
kotest.tags=!Mysql |
Y | Y | B, C만 실행된다. A는 Mysql로 표시되어 제외되었기 때문이다. |
kotest.tags=Linux & !Mysql |
Y | Y | 모든 테스트가 어노테이션에서 Linux를 상속하지만 A는 Mysql 태그에 의해 제외되기 때문에 B, C만 실행된다. |
@RequiresTag
두 번째 태그인 @RequiresTag
는 참조된 태그가 모두 있는지 여부만 확인하고 없는 경우 스펙을 건너띈다.
예를 들어, 다음 스펙은 런타임에 Linux
및 Mysql
태그가 지정되지 않은 경우 건너뛰고 인스턴스화되지 않는다.
@RequiresTag("Linux", "Mysql")
class MyTestClass : FunSpec()
NOTE
이러한 어노테이션을 사용할 때는 태그 자체가 아닌 태그 문자열 이름을 전달한다는 점에 유의해야 한다. 이는 Kotlin 어노테이션이 “primitive” 인수만 허용하기 때문이다.태그 상속
기본적으로 @Tags
어노테이션은 해당 어노테이션이 적용된 바로 앞의 Spec에서만 고려된다. 그러나 스펙은 슈퍼 클래스 및 슈퍼 인터페이스에서 태그를 상속할 수도 있다. 이를 활성화하려면 프로젝트 구성으로 tagInheritance = true
으로 전환한다.
Gradle 시스템 속성 전달받기 위한 구성
Gradle 구성에 특별한 주의가 필요하다.
시스템 속성(-Dx=y
)을 사용하려면, 테스트 실행에 전파되도록 Gradle을 구성해야 하며, 테스트에 추가 구성을 추가해야 한다:
Groovy:
test {
//... Other configurations ...
systemProperties = System.properties
}
Kotlin Gradle DSL:
val test by tasks.getting(Test::class) {
// ... Other configurations ...
systemProperties = System.getProperties().asIterable().associate { it.key.toString() to it.value }
}
혹은 아래와 같이 -Dkotest.tags=<tags>
만 받아와서 넣을 수 있게 설정할 수도 있다.
val test by tasks.getting(Test::class) {
// ... Other configurations ...
systemProperties.putAll(System.getProperties().filter { it.key == "kotest.tags" }.map { it.key.toString() to it.value.toString() }.toMap())
}
이렇게 하면 JVM에서 시스템 프로퍼티를 올바르게 읽을 수 있다.
Tag는 특정 환경이나 조건에 따라 특정 테스트를 선택적으로 실행하거나 제외할 때 유용하며, 테스트를 더 효율적으로 관리하고 구성할 수 있도록 도와준다.