Тестовая фабрика Kotest и beforeSpec

Обновление В поисках оптимального решения я временно переключился на подход с использованием специальной фабрики тестов, которая выполняет инициализацию в сочетании с forAll. Таким образом, мне не нужно повторять код инициализации в каждой тестовой фабрике.

fun somePreStuffFactory() = funSpec {
    beforeTest {
        println("Do some init stuff here")
    }

    test("dummy test just to trigger beforeTest") {}
}

class MyTest : FunSpec({
    runBlocking {
        forAll(
            row(MyTestFactory::someTest1),
            row(MyTestFactory::someTest2)
        ) { testFactoryFunc ->
            include(somePreStuffFactory())
            include(testFactoryFunc())
        }
    }
})

Конец обновления

У меня много тестов на тестовых фабриках. Прежде чем запускать тесты внутри тестовой фабрики, мне нужно выполнить некоторую первоначальную настройку в начале каждой тестовой фабрики, и эта настройка одинакова для всех тестовых фабрик.

Проблема в том, что beforeSpec не вызывается внутри тестовой фабрики, поэтому в настоящее время я использую грязный обходной путь, выполняя инициализацию в первом тесте в каждой тестовой фабрике. Я был бы очень признателен за любые советы по этому поводу.

Оптимальным решением было бы иметь хук жизненного цикла внутри тестового класса, который запускается перед каждой тестовой фабрикой.

Код для воспроизведения

class MyTest : FunSpec({
    include(someTest1())
    include(someTest2())
})
import io.kotest.core.spec.style.funSpec

object MyTestFactory {
    fun someTest1() = funSpec {
        beforeSpec {
            /** Not invoked */
            println("Hello from someTest1#beforeSpec")
        }

        test("Init stuff is done inside a test") {
            /** some init here */
        }

        test("first test") {
            println("Hello from first test")
        }

        test("second test") {
            println("Hello from second test")
        }
    }

    fun someTest2() = funSpec {
        beforeSpec {
            /** Not invoked */
            println("Hello from someTest2#beforeSpec")
        }

        test("Init stuff is done inside a test") {
            /** some init here */
        }

        test("third test") {
            println("Hello from third test")
        }
    }
}

Что я пробовал до сих пор

После повышения Kotest с 4.6.4 до 5.4.2 я смог запустить код в ответ от @ocos. Проблема в том, что BeforeSpecSample#beforeSpec вызывается только один раз, а не для каждой тестовой фабрики, что является моим требованием.

object BeforeSpecSample : BeforeSpecListener {
    override suspend fun beforeSpec(spec: Spec) {
        println("Hello from beforeSpec")
    }
}

class MyTest : FunSpec({
    extensions(BeforeSpecSample)

    include(someTest1())
    include(someTest2())
})

Обновлять Прочитав этот выпуск GitHub, я успешно протестировал следующий подход, используя логическую переменную initialized и beforeTest. Было бы неплохо, если бы этот подход можно было использовать внутри тестового класса, а не внутри каждой тестовой фабрики, но без каких-либо ловушек жизненного цикла для тестовых фабрик я не понимаю, как это можно сделать.

fun someTest1() = funSpec {
    var initialized = false

    beforeTest {
        if (!initialized) {
            println("Hello from someTest1#beforeTest")
            initialized = true
        }
    }

    /** tests goes here */
}

Среда Котест 4.6.4, Котлин 1.7.10, Микронавт 3.6.3

Документ фабрики тестов Kotest

Может быть, вы можете использовать прослушиватели контейнера до/после контейнера? Они могут помочь kotest.io/docs/framework/lifecycle-hooks.html

LeoColman 16.09.2022 23:29

@LeoColman: Спасибо за предложение. Я только что завершил тестирование beforeContainer как в тестовом классе, так и в тестовой фабрике. Ни один из них не был вызван. Причина этого, как я полагаю, в том, что в тестовом классе нет тестов, только включения. В тестовой фабрике beforeContainer страдает от той же проблемы, что и beforeSpec. BR

Roar S. 17.09.2022 12:10
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
184
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

include использует только тесты и расширения, определенные в TestFactory вашего someTest(). Вот почему beforeSpec не вызывается.

Вы можете зарегистрировать extension в своем MyTest. Вот пример.

// you may use object MyTestFactory : BeforeSpecListener 

object BeforeSpecSample : io.kotest.core.listeners.BeforeSpecListener {

  override suspend fun beforeSpec(spec: Spec) {
    println("-> before spec <-")
  }
}

class MyTest : FunSpec({
  extensions(BeforeSpecSample)

  include(someTest())

  test("my test 1") { println("my test 1") }
})

Спасибо за предложение. После повышения Kotest с 4.6.4 до 5.1.0 (более новые версии дают NoClassDefFound kotlinx/coroutines/CopyableThreadContextElement), я смог запустить ваш код. Проблема в том, что BeforeSpecSample#beforeSpec вызывается только один раз, а не для каждой тестовой фабрики, что является моим требованием. BR

Roar S. 16.09.2022 18:53
Ответ принят как подходящий

Если вы не против сгруппировать все внутри тестовой фабрики в блок context, вы можете использовать BeforeContainerListener для инициализации:

object InitExtension : BeforeContainerListener {
    override suspend fun beforeContainer(testCase: TestCase) {
        if (testCase.parent == null) {
            println("init stuff")
        }
    }
}

if (testCase.parent == null) просто позволяет вашей фабрике иметь другие вложенные context, которые не будут вызывать дополнительный вызов инициализации.

Затем вы можете написать свою фабрику следующим образом:

fun someTest1() = funSpec {
    extension(InitExtension)
    context("someTest1") {
        test("first test") {
            println("Hello from first test")
        }

        test("second test") {
            println("Hello from second test")
        }
    }
}

Инициализация в InitExtension будет вызываться в начале contextsomeTest1 и может быть аналогичным образом включена в другие фабрики тестов, каждая из которых может быть инициализирована одним и тем же расширением.

Это работает, как и ожидалось, большое спасибо. Для других стоит упомянуть, что InitExtension также может быть определен как класс с параметрами конструктора в случае, например. услуги не требуются (как в моем случае). BR

Roar S. 22.09.2022 16:57

Другие вопросы по теме