Границы общего кода и тестируемые зависимости
Source sets, интерфейсы и точечный expect/actual
Общий код — пересечение доступных API
commonMain должен компилироваться для всех выбранных таргетов. Наличие JVM-артефакта библиотеки не означает, что её можно вызвать из общего кода. Промежуточный iosMain разделяет код между соответствующими iOS-таргетами; доступ к API зависит от всей группы таргетов и зависимостей. Иерархия source sets.
Две разные точки расширения
expect/actual связывает объявление и платформенную реализацию при компиляции. Обычный интерфейс позволяет передать разные экземпляры на одной платформе, включая fake для теста. Для небольшого платформенного примитива удобна expect-функция; для репозитория или часов часто хватает конструктора. Expect/actual и интерфейсы.
// commonMain: единицы и семантика времени заданы договором
fun interface Clock { fun nowMillis(): Long }
class Session(private val clock: Clock) {
fun expired(deadlineMillis: Long): Boolean =
clock.nowMillis() >= deadlineMillis
}
Пример использует абсолютный deadline и часы в одной системе отсчёта. Для измерения длительности операции нужны монотонные часы: перевод системного времени не должен продлевать тайм-аут. Kotlin: измерение времени.
Иерархия — не просто похожие имена папок
dependsOn задаёт отношения source sets, а не библиотечную зависимость. Default hierarchy template создаёт общие наборы для подходящих объявленных таргетов. Ручные связи могут мешать автоматическому шаблону; сначала определите нужные таргеты и проверьте предупреждения Gradle. Иерархия source sets.
commonTest тоже запускается на таргетах
Общий тест проверяет контракт независимо от платформенной реализации, но не доказывает работу iOS bridge или доступность системного SDK. Запускайте тесты для нужных платформ отдельно. Тестирование KMP.
Основа отбора: KMP-блок draft/faq500.md; расширено по документации Kotlin. Исходная тема «Структура KMP-проекта» сохраняется.