В чем разница между runBlockingTest, runTest и runBlocking?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между runBlockingTest, runTest и runBlocking
В контексте Kotlin Coroutines для тестирования и разработки существует три основных функции, которые часто вызывают путаницу: runBlocking, runBlockingTest (устаревший) и runTest (современная замена). Все они предназначены для запуска корутин в синхронном стиле, но имеют разное предназначение и поведение.
runBlocking: для блокирующего запуска в продакшене
runBlocking — это базовая функция-строитель корутин, которая блокирует текущий поток до завершения всех запущенных внутри неё корутин. Она предназначена в основном для использования в продакшен-коде, например, в функциях main, тестах или при интеграции с блокирующим кодом.
import kotlinx.coroutines.*
fun main() {
// Блокирует основной поток до выполнения корутины
runBlocking {
delay(1000L)
println("Hello from runBlocking!")
}
}
- Ключевые особенности:
* Реальное ожидание (`delay` занимает реальное время).
* Не предназначена для тестирования (замедляет тесты).
* Использует настоящий диспетчер (если не указан иной).
runBlockingTest (устаревший): ускоренное тестирование (коревая библиотека)
runBlockingTest была экспериментальным API в kotlinx-coroutines-test и устарела в версии 1.6.0. Она создавала специальный TestCoroutineDispatcher и "виртуальное" время, позволяя пропускать задержки (delay) и мгновенно выполнять корутины.
// УСТАРЕВШИЙ ПРИМЕР (библиотека версии <1.6)
import kotlinx.coroutines.test.runBlockingTest
@Test
fun oldTest() = runBlockingTest { // Deprecated
launch {
delay(1000L) // Виртуальное время, выполняется мгновенно в тесте
println("Immediate execution")
}
advanceTimeBy(1000L) // Управление виртуальным временем
}
runTest: современная замена для unit-тестов
Начиная с kotlinx-coroutines 1.6.0, runTest является полноценной заменой runBlockingTest. Она предоставляет те же возможности для управления временем, но построена на новой абстракции TestScope и использует StandardTestDispatcher.
import kotlinx.coroutines.test.runTest
@Test
fun modernTest() = runTest { // Рекомендуемый способ
launch {
delay(1000L) // Виртуальное время! Не ждем реальную секунду.
println("Executed after virtual delay")
}
// Задержка не требуется, корутина запустится автоматически
// или можно использовать advanceTimeBy(1000L)
}
- Ключевые особенности
runTest:
* **Виртуальное время**: `delay`, `withTimeout` выполняются мгновенно.
* **Контроль выполнения**: функции `advanceTimeBy`, `runCurrent`.
* **Чистота тестов**: автоматическая отмена незавершенных корутин после теста.
* Использует **TestScope** и **StandardTestDispatcher** "под капотом".
Сравнительная таблица
| Критерий | runBlocking | runBlockingTest (устаревший) | runTest (современный) |
|---|---|---|---|
| Основное назначение | Продакшен-код, main-функции | Устаревшие unit-тесты | Современные unit-тесты |
Ожидание delay | Реальное время (тест будет медленным) | Виртуальное время (мгновенно) | Виртуальное время (мгновенно) |
| Управление временем | Нет | advanceTimeBy, runCurrent | advanceTimeBy, runCurrent |
| Базовая архитектура | Event loop | TestCoroutineDispatcher (устар.) | TestScope + StandardTestDispatcher |
| Обработка пауз | Реальные паузы | Пропускались автоматически | Требует advanceUntilIdle или явного продвижения |
| Рекомендация | Избегать в unit-тестах | Не использовать (Deprecated) | Использовать для тестирования кода с корутинами |
Практические рекомендации
- Для unit-тестов всегда используйте runTest. Он гарантирует скорость выполнения (исключая реальные задержки) и предоставляет полный контроль над виртуальным временем.
- runBlocking может использоваться в тестах интеграции или UI, где требуется реальное время, или при тестировании вместе с блокирующими библиотеками. Но для изолированных unit-тестов — это антипаттерн.
- При миграции с runBlockingTest на runTest обратите внимание на изменение поведения:
runTestне продвигает время автоматически при запуске корутин сdelay. Вам может потребоваться явно вызватьadvanceUntilIdle()илиadvanceTimeBy(...). - Всегда явно задавайте TestDispatcher в коде, который тестируете (через параметры или DI), чтобы избежать неявного использования
Dispatchers.Mainили других реальных диспетчеров.
Итог: runBlocking — инструмент для синхронного запуска в продакшене, runTest — специализированный и эффективный инструмент для написания быстрых и предсказуемых unit-тестов для асинхронного кода на корутинах, а runBlockingTest — его устаревший предшественник, от которого следует отказаться.