Тестирование общего кода
Главный дивиденд KMP: пишете бизнес-тесты один раз — они защищают обе платформы.
commonTest — исходный набор для тестов общего кода; они запускаются под все цели, проверяя, что логика ведёт себя одинаково на Android и iOS.
Почему это важнее, чем кажется
Главная боль двух кодовых баз — расходящиеся бизнес-правила. В KMP логика общая, и тесты к ней тоже общие. Один тест «скидка 110% запрещена» защищает обе платформы. Это, возможно, самый недооценённый выигрыш KMP: не только меньше кода, но и единая гарантия корректности.
Общий тест
// commonTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class PriceCalculatorTest {
private val calc = PriceCalculator()
@Test fun appliesDiscount() {
assertEquals(80.0, calc.finalPrice(100.0, 20))
}
@Test fun rejectsInvalidDiscount() {
assertFailsWith<IllegalArgumentException> {
calc.finalPrice(100.0, 110)
}
}
}Этот тест выполнится и под JVM, и под Kotlin/Native — если логика где-то разойдётся, упадёт соответствующая цель.
Мокаем зависимости интерфейсами
Здесь окупается архитектура на интерфейсах: чтобы протестировать репозиторий, подставляем фейковый API без всякой сети.
// commonTest
class FakeOrderApi(private val data: List<OrderDto>) : OrderApiContract {
override suspend fun fetchOrders() = data
}
class OrderRepositoryTest {
@Test fun mapsToDomain() = runTest {
val repo = OrderRepository(FakeOrderApi(listOf(OrderDto(1, 50.0))), FakeCache())
repo.refresh()
// проверяем содержимое кэша
}
}runTest из kotlinx-coroutines-test запускает корутины в тесте детерминированно.
Как работает под капотом
Тесты commonTest компилируются под каждую цель как обычный код и запускаются её средствами: JVM-тесты через JUnit-подобный раннер, iOS-тесты — через нативный тест-раннер на симуляторе. Поэтому общий тест реально исполняется дважды (или больше), на разных бэкендах. Если бы где-то поведение зависело от платформы (например, форматирование чисел), это всплыло бы как падение одной из целей. Так KMP заодно ловит скрытые платформенные различия.
Частые ошибки
Полагаться на expect/actual в тестируемом коде так, что тест требует реальной платформенной реализации — лучше абстрагировать через интерфейс и подменять фейком. Вторая ошибка — тестировать корутины без runTest, получая флаки из-за реального времени и потоков. Третья — гонять тесты только под JVM (быстрее) и пропускать iOS-цель, теряя смысл «одинаково на обеих платформах».
Итоги
- Общие тесты в
commonTestзащищают логику на всех платформах сразу. - Архитектура на интерфейсах позволяет мокать зависимости без сети и БД.
runTestделает тесты корутин детерминированными.- Тесты исполняются под каждую цель, попутно ловя платформенные различия.