Kotest 태그를 사용하여 테스트 그룹화(Grouping Tests with Tags)

효과적인 소프트웨어 테스트를 위해서는 테스트 케이스를 작성하고 관리하는 것이 중요하다. 이번 섹션에서는 Kotest를 사용하여 테스트 케이스를 작성하는 방법에 대해 알아보겠다.

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 아닌 경우만 실행이 된다.

태그는 런타임에 포함/제외할 수도 있다(예를 들면, 속성 대신 프로젝트 구성을 실행하는 경우) 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
})

스펙에 태그로 지정

스펙 클래스에 추가할 수 있는 어노테이션은 @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" {
        // 테스트 내용
    }
})

위 코드는 fastsmoke 두 가지 태그가 모두 지정된 테스트를 나타낸다. 이 경우 kotest.tags 시스템을 사용하여 두 태그 중 하나 또는 두 태그 모두를 고려하여 실행할 수 있다.

다음은 @Tagsconfigtags 함수에 태그를 지정한 경우, 시스템 속성(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는 참조된 태그가 모두 있는지 여부만 확인하고 없는 경우 스펙을 건너띈다.

예를 들어, 다음 스펙은 런타임에 LinuxMysql 태그가 지정되지 않은 경우 건너뛰고 인스턴스화되지 않는다.

@RequiresTag("Linux", "Mysql")
class MyTestClass : FunSpec()

태그 상속

기본적으로 @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는 특정 환경이나 조건에 따라 특정 테스트를 선택적으로 실행하거나 제외할 때 유용하며, 테스트를 더 효율적으로 관리하고 구성할 수 있도록 도와준다.

참조




최종 수정 : 2024-04-23