Kotest 구성(Configuration)

Kotest 구성(Configuration)은 테스트 실행 동작을 커스터마이징하고 제어하는 데 사용된다. 다양한 설정 옵션을 제공하여 사용자가 테스트 환경을 원하는 대로 조정할 수 있다.

테스트 케이스 구성

각 테스트는 다양한 파라미터로 구성할 수 있다. 테스트 이름 뒤에 설정하려는 파라미터를 전달하는 구성 함수를 호출한다. 사용 가능한 파라미터는 다음과 같다:

  • invocations: 이 테스트를 실행할 횟수이다. 비결정적 테스트가 있고 특정 테스트를 정해진 횟수만큼 실행하여 결국 실패하는지 확인하려는 경우에 유용하다. 모든 호출이 성공하는 경우에만 테스트가 성공한다. 기본값은 1이다.
  • threads: 스레드 수를 설정하여 이 테스트의 호출을 병렬화할 수 있다. 값은 호출 값보다 작거나 같아야 한다. 마찬가지로 호출을 스레드 수와 같은 값으로 설정하면 각 호출에 고유한 스레드가 생긴다.
  • enabled: false로 설정하면 이 테스트가 비활성화된다. 테스트를 일시적으로 무시해야 하는 경우에 유용할 수 있다. 이 매개변수를 부울 표현식과 함께 사용하여 특정 조건에서만 테스트를 실행할 수도 있다.
  • enabledIf: enabled와 동일한 기능을 제공하지만 테스트 케이스의 실행 기한이 되면 느리게 평가되는 함수이다.
  • timeout: 이 테스트의 시간 제한을 설정한다. 해당 시간 내에 테스트가 완료되지 않으면 테스트가 실패한다. 비결정적이며 완료되지 않을 수 있는 코드에 유용하다. 시간 제한은 2.seconds, 3.minutes 등과 같이 인스턴스화할 수 있는 kotlin.Duration 유형이다.
  • tags: 테스트를 그룹화하는 데 사용할 수 있는 태그 집합이다(아래 자세한 설명 참조).
  • listeners: 이 테스트에서만 실행할 테스트 리스너를 등록한다.
  • extensions: 이 테스트에서만 실행할 확장을 등록한다.

테스트에서 구성 설정의 예제이다:

import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe

class MyTests : ShouldSpec() {
    init {
        should("return the length of the string").config(invocations = 10, threads = 2) {
            "sammy".length shouldBe 5
            "".length shouldBe 0
        }
    }
}
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import kotlin.time.Duration.Companion.seconds

class MyTests : WordSpec() {
    init {
        "String.length" should {
            "return the length of the string".config(timeout = 2.seconds) {
                "sammy".length shouldBe 5
                "".length shouldBe 0
            }
        }
    }
}
import io.kotest.core.spec.style.FunSpec
import io.kotest.core.Tag

class FunSpecTest : FunSpec() {
    init {
        test("FunSpec should support config syntax").config(tags = setOf(Database, Linux)) {
            // ...
        }
    }
}

object Database : Tag()
object Linux : Tag()

스펙의 모든 테스트 케이스에 대해 기본 TestCaseConfig를 지정할 수도 있다:

defaultTestCaseConfig 함수 재정의:

import io.kotest.core.spec.style.StringSpec
import io.kotest.core.test.config.TestCaseConfig

class MySpec : StringSpec() {

    override fun defaultTestCaseConfig() = TestCaseConfig(invocations = 3)

    init {
        // your test cases ...
    }
}

또는 defaultTestConfig 값에 할당을 통해 가능하다:

import io.kotest.core.spec.style.FunSpec
import io.kotest.core.test.config.TestCaseConfig

class FunSpecTest : FunSpec() {
    init {

        defaultTestConfig = TestCaseConfig(enabled = true, invocations = 3)

        test("FunSpec should support Spec config syntax in init{} block") {
            // ...
        }
    }
}

프로젝트 구성

Kotest는 유연하며 스펙 내에서 테스트 순서를 구성하거나 테스트 클래스를 생성하는 방법 등 다양한 방법으로 테스트를 구성할 수 있다. 때로는 전역 수준에서 설정하고 싶을 수 있으며, 이를 위해서는 project-level-config를 사용해야 한다.

프로젝트 레벨 구성은 AbstractProjectConfig에서 확장되는 객체 또는 클래스를 생성하여 사용할 수 있다.

Spec 레벨 또는 테스트에서 직접 설정한 모든 구성은 프로젝트 레벨에서 지정된 구성을 재정의한다.

KotestProjectConfig에서 사용할 수 있는 일부 구성 옵션에는 테스트 병렬 처리, 무시된 테스트를 사용한 스펙 실패, 전역 AssertSoftly, 재사용 가능한 리스너 또는 확장이 포함된다.

런타임 탐지

런타임에 Kotest는 AbstractProjectConfig를 확장하는 클래스를 검색하고 해당 클래스에 정의된 모든 구성 값을 사용하여 인스턴스화한다.

서로 다른 모듈에 둘 이상의 설정 클래스를 생성할 수 있으며, 현재 클래스 경로에 있는 모든 클래스가 감지되어 설정이 병합된다. 이는 공통 설정이 루트 모듈에 배치될 수 있도록 하는 데 효과적이다. 충돌이 발생하는 경우 하나의 값이 임의로 선택되므로 서로 다른 설정에 경쟁하는 설정을 추가하지 않는 것이 좋다.

대규모 프로젝트의 경우 시작 비용이 많이 드는 경우 이러한 구성 클래스에 대한 자동 검색을 비활성화할 수 있다. 시스템 속성 또는 환경 변수 kotest.framework.classpath.scanning.config.disabletrue로 설정하여 이 작업을 수행할 수 있다.

자동 스캔이 비활성화된 후에도 프로젝트 구성을 계속 사용하려면 잘 알려진 클래스 이름을 지정하여 Kotest가 반영적으로 인스턴스화할 수 있다. 사용할 시스템 속성 또는 환경 변수는 kotest.framework.config.fqn이다.

예를 들어, 설정:

kotest.framework.classpath.scanning.config.disable=true
kotest.framework.config.fqn=com.wibble.KotestConfig

런타임 스캔을 비활성화하고 com.wibble.KotestConfig 클래스를 찾는다. 이 클래스는 여전히 ₩AbstractProjectConfig₩를 상속해야 한다.

병렬 처리

병렬 처리 레벨(기본값은 1)을 설정하여 여러 코어가 있는 최신 CPU를 활용하기 위해 스펙을 병렬로 실행하도록 Kotest에 요청할 수 있다. 스펙 내 테스트는 항상 순차적으로 실행된다.

이렇게 하려면 구성 내에서 parallelism를 재정의하고 1보다 큰 값으로 설정한다. 설정된 숫자는 동시에 실행되는 스펙 갯수이다. 예를 들어

object KotestProjectConfig : AbstractProjectConfig() {
    override val parallelism = 3
}

이를 활성화하는 다른 방법은 시스템 속성인 kotest.framework.parallelism이 항상(정의된 경우) 여기 값보다 우선적으로 적용하는 것이다.

일부 테스트는 병렬로 실행되지 않을 수 있으므로 개별 스펙을 선택 해제하고 스펙에 @DoNotParallelize 어노테이션을 사용하여 개별적으로 실행되도록 할 수 있다.

Assertion 모드

Kotest Assertion을 사용하지 않는 테스트가 실행되는 경우 빌드에 실패하도록 요청하거나 std 오류로 경고할 수 있다.

이렇게 하려면 구성 내에서 assertionModeAssertionMode.Error 또는 AssertionMode.Warn으로 설정한다. 예를 들어, 이를 활성화하는 다른 방법은 시스템 속성인 kotest.framework.assertion.mode가 항상(정의된 경우) 여기 값보다 우선순위를 갖도록 하는 것이다.

object KotestProjectConfig : AbstractProjectConfig() {
    override val assertionMode = AssertionMode.Error
}

전역 Assert Softly

Assert Softly는 오류를 하나의 실패로 일괄 처리하는 데 매우 유용하다. 모든 테스트에 대해 이 기능을 자동으로 활성화하려면 구성에서 이 작업을 수행할 수 있다. 이 기능을 활성화하는 다른 방법은 시스템 속성 kotest.framework.assertion.globalassertsoftlytrue로 설정하여 항상(정의된 경우) 여기 값보다 우선적으로 적용하는 것이다.

object KotestProjectConfig : AbstractProjectConfig() {
    override val globalAssertSoftly = true
}

중복 테스트 이름 처리

기본적으로 Kotest는 동일한 범위의 다른 테스트와 이름이 같은 경우 테스트의 이름을 바꾼다. 테스트 이름에 _1, _2 등을 추가한다. 이 기능은 자동으로 생성된 테스트에 유용하다.

이 동작은 duplicateTestNameModeDuplicateTestNameMode.Error 또는 DuplicateTestNameMode.Warn으로 설정하여 전역적으로 변경할 수 있다.

Error는 반복되는 이름에서 테스트 스위트를 실패시키고, 경고는 이름을 바꾸지만 경고를 출력한다.

무시된 테스트의 실패

무시된 테스트를 실패로 간주하고 싶을 수 있다. 이 기능을 활성화하려면 프로젝트 구성에서 failOnIgnoredTeststrue로 설정한다.

예를 들어:

object KotestProjectConfig : AbstractProjectConfig() {
    override val failOnIgnoredTests = true
}

주문테스트 순서

Kotest는 스펙과 테스트를 독립적으로 순서가 반영될 수 있도록 지원한다.

테스트 순서

하나의 스펙에서 여러 테스트를 실행할 때 실행 방법에 대한 특정 순서가 있다.

기본적으로 순차적 순서(스펙에 정의된 테스트 순서)가 사용되지만, 이 순서는 변경할 수 있다. 사용 가능한 옵션은 테스트 순서를 참조한다.

스펙 순서

기본적으로 스펙 클래스의 순서는 정의되어 있지 않는다. 기본 설정이 없는 경우 이 정도면 충분하지만, 스펙의 실행 순서를 제어해야 하는 경우 스펙 순서를 사용할 수 있다.

테스트 네이밍

테스트 이름은 여러 가지 방법으로 조정할 수 있다.

테스트 케이스

테스트 이름 대소문자는 testNameCase의 값을 변경하여 제어할 수 있다.

기본적으로 값은 변경되지 않는 TestNameCase.AsIs이다.

값을 TestNameCase.Lowercase로 설정하면 테스트 이름이 소문자로 출력된다.

테스트 이름에 접두사를 추가하는 스펙을 사용하는 경우(WordSpec 또는 BehaviorSpec이어야 함) TestNameCase.SentenceTestNameCase.InitialLowercase 값이 유용할 수 있다.

테스트 이름 태그

테스트 이름에 사용하는 또 다른 옵션은 true로 설정하면 테스트 이름에 해당 태그를 모두 포함하는 testNameAppendTags이다. 예를 들어, 스펙에 linuxspark 태그가 있는 테스트 foo가 정의된 경우 테스트 이름은 foo [linux, spark]로 조정된다.

이 설정은 시스템 속성 또는 환경 변수 kotest.framework.testname.append.tagstrue로 사용하여 설정할 수도 있다.

테스트 이름 공백

여러 줄에 걸쳐 테스트 이름을 정의하는 경우 removeTestNameWhitespace가 유용할 수 있다. 이 예제를 살펴보겠다:

"""this is
   my test case""" {
  // test here
}

이렇게 하면 출력의 테스트 이름은 this is my test case이다. removeTestNameWhitespacetrue로 설정하면 이 이름이 this is my test case로 공색이 제거된다.

이 기능을 활성화하는 다른 방법은 시스템 속성 kotest.framework.testname.multilinetrue로 설정하여 항상(정의된 경우) 여기 값보다 우선적으로 적용하는 것이다.

object KotestProjectConfig : AbstractProjectConfig() {
    override val testNameRemoveWhitespace = true
}

프레임워크 구성 속성

KotestEngineProperties.kt

package io.kotest.core.internal

object KotestEngineProperties {

   const val scriptsEnabled = "kotest.framework.scripts.enabled"

   const val dumpConfig = "kotest.framework.dump.config"

   /**
    * Sets the tag expression that determines included/excluded tags.
    */
   const val tagExpression = "kotest.tags"

   const val excludeTags = "kotest.tags.exclude"

   const val includeTags = "kotest.tags.include"

   /**
    * A regex expression that is used to match the test [io.kotest.core.descriptors.Descriptor]'s path
    * to determine if a test should be included in the test plan or not.
    */
   const val filterTests = "kotest.filter.tests"

   /**
    * A regex expression that is used to match the [io.kotest.mpp.bestName] of a class
    * to determine if a spec should be included in the test plan or not.
    */
   const val filterSpecs = "kotest.filter.specs"

   const val propertiesFilename = "kotest.properties.filename"

   /**
    * If set to true, then source ref's will not be created for test cases.
    * This may speed up builds (as the engine will not need to create stack traces to
    * generate line numbers) but will also reduce functionality in the intellij plugin
    * (by limiting the ability to drill directly into the test inside a file).
    */
   const val disableSourceRef = "kotest.framework.sourceref.disable"

   /**
    * If set to true, disables the use of '!' as a prefix to disable tests.
    */
   const val disableBangPrefix = "kotest.bang.disable"

   /**
    * The default [io.kotest.core.spec.IsolationMode] for specs.
    */
   const val isolationMode = "kotest.framework.isolation.mode"

   /**
    * The default [io.kotest.core.test.AssertionMode] for tests.
    */
   const val assertionMode = "kotest.framework.assertion.mode"

   /**
    * The default parallelism for specs.
    */
   const val parallelism = "kotest.framework.parallelism"

   /**
    * The default timeout for test cases.
    */
   const val timeout = "kotest.framework.timeout"

   /**
    * The default timeout for the entire test suite.
    */
   const val projectTimeout = "kotest.framework.projecttimeout"

   const val logLevel = "kotest.framework.loglevel"

   /**
    * The default timeout for each invocation of a test case.
    */
   const val invocationTimeout = "kotest.framework.invocation.timeout"

   const val disableTestNestedJarScanning = "kotest.framework.disable.test.nested.jar.scanning"

   const val concurrentSpecs = "kotest.framework.spec.concurrent"

   const val concurrentTests = "kotest.framework.test.concurrent"

   /**
    * Disable scanning the classpath for configuration classes by setting this property to true
    */
   const val disableConfigurationClassPathScanning = "kotest.framework.classpath.scanning.config.disable"

   /**
    * Specify a fully qualified name to use for project config.
    * This class will be instantiated via reflection.
    */
   const val configurationClassName = "kotest.framework.config.fqn"

   /**
    * Disable scanning the classpath for listeners with @AutoScan by setting this property to true
    */
   const val disableAutoScanClassPathScanning = "kotest.framework.classpath.scanning.autoscan.disable"

   const val allowMultilineTestName = "kotest.framework.testname.multiline"

   /**
    *  If set -> filter testCases by this severity level and higher, else running all
    */
   const val testSeverity = "kotest.framework.test.severity"

   /**
    * Enable assert softly globally.
    * */
   const val globalAssertSoftly = "kotest.framework.assertion.globalassertsoftly"

   /**
    * Appends all tags associated with a test case to its display name.
    * */
   const val testNameAppendTags = "kotest.framework.testname.append.tags"

   /**
    * Controls whether classes will inherit tags from their supertypes. Default false
    */
   const val tagInheritance = "kotest.framework.tag.inheritance"

   /**
    * Controls the [io.kotest.core.names.DuplicateTestNameMode] mode.
    */
   const val duplicateTestNameMode = "kotest.framework.testname.duplicate.mode"

   const val disableJarDiscovery = "kotest.framework.discovery.jar.scan.disable"
}

참조




최종 수정 : 2024-04-21