You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Multiplatform跨多模块共享@BeforeTest与@AfterTest测试逻辑的方案咨询

Kotlin Multiplatform跨多模块共享@BeforeTest与@AfterTest测试逻辑的方案咨询

我之前在做KMM多模块项目时也碰到过完全一样的问题,一开始也是想把测试逻辑塞到commonMain里,结果测试运行器根本不买账。其实问题出在两个地方:一是你把测试代码放错了源集,二是kotlin.test的注解只有在测试源集里才会被测试运行器识别。下面给你梳理一套靠谱的实现方案,包括具体步骤和最佳实践:

一、先纠正一个核心误区:别把测试逻辑放在commonMain里!

commonMain是用来放生产环境代码的,测试相关的工具类、钩子逻辑属于测试范围,放在这里不仅会污染生产代码的依赖(比如引入测试库到生产包),更关键的是测试运行器默认只会扫描测试源集(commonTest、androidTest等)里的注解,完全不会处理commonMain中的测试注解——这就是你之前测试跑不起来的根本原因。

正确的做法是:新建一个专门的共享测试工具模块,把所有跨模块复用的测试逻辑都放在这个模块的commonTest源集里。


二、具体实现步骤

1. 新建共享测试工具模块

创建一个KMM模块(比如命名为shared-test-utils),这个模块不需要保留生产源集(commonMain、androidMain等),只需要测试相关的源集即可。

在它的build.gradle.kts里配置测试依赖:

plugins {
    id("org.jetbrains.kotlin.multiplatform")
}

kotlin {
    jvm()
    ios()
    // 其他你需要的平台...

    sourceSets {
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
                // 如果用第三方测试库(比如MockK、Kotest),也在这里加依赖
                // implementation("io.mockk:mockk-common:1.13.8")
            }
        }
        // 各个平台的test源集如果需要特定逻辑,可以单独配置
        val jvmTest by getting {
            dependencies {
                implementation(kotlin("test-junit"))
            }
        }
    }
}

2. 定义共享测试钩子

在shared-test-utils的commonTest源集下,创建一个基类或者单例类来封装共享的@BeforeTest和@AfterTest逻辑:

方式一:用开放基类(推荐,方便子模块扩展)

import kotlin.test.AfterTest
import kotlin.test.BeforeTest

open class BaseSharedTest {
    @BeforeTest
    open fun globalSetup() {
        // 这里写通用初始化逻辑:比如初始化日志、配置测试用DI、清理测试数据等
        println("[Shared] Running setup before test...")
    }

    @AfterTest
    open fun globalTeardown() {
        // 通用清理逻辑:比如关闭数据库连接、清空缓存等
        println("[Shared] Running teardown after test...")
    }
}

方式二:用单例类(适合完全不需要扩展的固定逻辑)

import kotlin.test.AfterTest
import kotlin.test.BeforeTest

object SharedTestHooks {
    @BeforeTest
    fun setup() {
        // 通用前置逻辑
    }

    @AfterTest
    fun teardown() {
        // 通用后置逻辑
    }
}

3. 在业务模块中引入共享测试模块

在需要复用测试逻辑的业务模块的build.gradle.kts里,给commonTest添加对shared-test-utils的依赖:

kotlin {
    sourceSets {
        val commonTest by getting {
            dependencies {
                implementation(project(":shared-test-utils"))
            }
        }
    }
}

4. 在业务模块的测试中使用共享逻辑

如果用的是基类方式,直接让测试类继承BaseSharedTest即可,测试运行器会自动执行父类的@BeforeTest和@AfterTest方法:

// 业务模块中的测试类
import kotlin.test.Test

class UserRepositoryTest : BaseSharedTest() {
    @Test
    fun `should fetch user data successfully`() {
        // 你的测试逻辑...
        // 执行前会先跑BaseSharedTest的globalSetup,执行后跑globalTeardown
    }
}

如果用的是单例类方式,需要在测试类中实例化它(不过这种方式不如基类直观,更推荐基类):

class OrderServiceTest {
    private val hooks = SharedTestHooks

    @Test
    fun `should create order`() {
        // 测试逻辑
    }
}

三、进阶:用第三方测试框架实现更灵活的共享逻辑

如果你的项目用的是Kotest、Spek这类功能更丰富的测试框架,可以用它们的监听器/扩展机制来实现共享逻辑,比原生kotlin.test的注解更灵活:

比如用Kotest的TestListener:

import io.kotest.core.listeners.TestListener
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult

class GlobalTestListener : TestListener {
    override suspend fun beforeTest(testCase: TestCase) {
        // 通用前置逻辑
    }

    override suspend fun afterTest(testCase: TestCase, result: TestResult) {
        // 通用后置逻辑
    }
}

然后在业务模块的测试类中注册这个监听器:

import io.kotest.core.spec.style.FunSpec

class PaymentTest : FunSpec({
    listener(GlobalTestListener)

    test("should process payment") {
        // 测试逻辑
    }
})

四、最佳实践

  1. 保持共享测试逻辑的通用性:只放跨模块都需要的通用操作(比如日志初始化、DI配置、测试数据清理),别把业务相关的测试逻辑塞进来,否则会导致共享模块变得臃肿难维护。
  2. 平台特定逻辑单独处理:如果某个平台需要特殊的测试初始化(比如Android需要初始化Context),可以在共享模块的对应平台test源集里扩展基类,比如在androidTest里重写setup方法添加Android特定逻辑。
  3. 避免过度封装:如果某个模块只需要少量特殊测试逻辑,直接在该模块的测试类里写即可,不要强行塞到共享模块里。
  4. 统一测试依赖版本:可以在项目根目录的build.gradle.kts里用dependencyResolutionManagement统一管理测试库的版本,避免各个模块版本不一致。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 14:23:03