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

Cucumber-Kotlin实现软步骤:如何通过步骤名调用执行步骤?

解决Cucumber Kotlin中实现软步骤(失败不终止)的方案

Cucumber本身没有提供直接通过字符串调用步骤的API,但可以通过步骤注册表+手动/自动注册的方式实现需求,避免重复代码和额外维护负担。以下是两种可行方案:

方案一:手动注册步骤(简单可控)

1. 定义步骤注册表

创建单例对象存储步骤模板与执行逻辑的映射,支持不同参数类型的步骤:

sealed class StepExecutor {
    data class NoArg(val block: () -> Unit) : StepExecutor()
    data class SingleStringArg(val block: (String) -> Unit) : StepExecutor()
    // 可扩展其他参数类型(如Int、自定义对象)
}

data class StepDefinition(val template: String, val executor: StepExecutor)

object StepRegistry {
    val steps = mutableListOf<StepDefinition>()
}

2. 封装步骤注册函数

替代原生When/Given/Then,在注册Cucumber步骤的同时同步到注册表:

fun registerWhen(template: String, block: () -> Unit) {
    StepRegistry.steps.add(StepDefinition(template, StepExecutor.NoArg(block)))
    When(template, block)
}

fun registerWhen(template: String, block: (String) -> Unit) {
    StepRegistry.steps.add(StepDefinition(template, StepExecutor.SingleStringArg(block)))
    When(template, block)
}

// 同理封装registerGiven、registerThen

3. 定义软步骤执行函数

实现步骤匹配、参数解析和异常捕获:

When("soft {string}")
fun executeSoftStep(stepText: String) {
    // 匹配步骤模板并提取参数
    val match = StepRegistry.steps.firstNotNullOfOrNull { def ->
        val regex = def.template
            .replace("{string}", "\"?(.*?)\"?") // 处理带引号的字符串参数
            .replace("{int}", "(\\d+)")
            .toRegex()
        regex.find(stepText)?.let { def to it.groupValues.drop(1) }
    } ?: error("未找到匹配的步骤:$stepText")

    val (stepDef, args) = match
    runCatching {
        when (val executor = stepDef.executor) {
            is StepExecutor.NoArg -> executor.block()
            is StepExecutor.SingleStringArg -> {
                require(args.size == 1) { "步骤${stepDef.template}需要1个参数,实际传入${args.size}个" }
                executor.block(args[0])
            }
            // 扩展其他参数类型的执行逻辑
        }
    }.onFailure {
        println("软步骤执行失败 [$stepText]:${it.message}")
        // 可添加日志上报、测试标记等逻辑
    }
}

4. 使用方式

步骤定义和.feature文件写法:

// 普通步骤定义(自动注册到注册表)
registerWhen("click button") {
    // 点击按钮逻辑
}

registerWhen("input {string} into username field") { text: String ->
    // 输入文本逻辑
}
# .feature文件
When click button  // 正常执行,失败终止
When soft "click button"  // 软执行,失败不终止
When soft "input \"admin\" into username field"  // 带参数的软步骤

方案二:反射自动收集步骤(零额外维护)

如果不想手动注册步骤,可以通过反射扫描所有Cucumber步骤定义,自动填充注册表:

1. 自动收集步骤的注册表

object AutoStepRegistry {
    val stepMap = mutableMapOf<String, KFunction<*>>()

    init {
        // 扫描当前包下所有带Cucumber注解的函数
        val currentPackage = this::class.java.packageName
        val classLoader = Thread.currentThread().contextClassLoader

        // 遍历包下所有类
        classLoader.getResources(currentPackage.replace('.', '/')).toList().forEach { url ->
            File(url.toURI()).listFiles()?.filter { it.name.endsWith(".class") }?.forEach { file ->
                val className = "${currentPackage}.${file.name.substringBefore('.')}"
                val clazz = Class.forName(className)
                // 收集@When/@Given/@Then注解的函数
                listOf(When::class.java, Given::class.java, Then::class.java).forEach { annotationClass ->
                    clazz.declaredMethods.forEach { method ->
                        method.getAnnotation(annotationClass)?.let { annotation ->
                            val kFunc = clazz.kotlin.members.find { it.name == method.name } as? KFunction<*>
                            kFunc?.let { stepMap[annotation.value] = it }
                        }
                    }
                }
            }
        }
    }
}

2. 软步骤执行函数(反射调用)

When("soft {string}")
fun executeSoftStep(stepText: String) {
    // 匹配步骤模板(逻辑同方案一)
    val match = AutoStepRegistry.stepMap.entries.firstNotNullOfOrNull { (template, func) ->
        val regex = template
            .replace("{string}", "\"?(.*?)\"?")
            .replace("{int}", "(\\d+)")
            .toRegex()
        regex.find(stepText)?.let { template to it.groupValues.drop(1) }
    } ?: error("未找到匹配的步骤:$stepText")

    val (template, args) = match
    val func = AutoStepRegistry.stepMap[template] ?: error("步骤模板不存在:$template")

    runCatching {
        // 处理参数类型转换并调用函数
        val convertedArgs = args.mapIndexed { index, arg ->
            val paramType = func.parameters[index+1].type // index+1跳过顶层函数的Receiver参数
            when (paramType) {
                String::class -> arg
                Int::class -> arg.toInt()
                // 扩展其他类型转换
                else -> arg
            }
        }
        // 调用函数(顶层函数直接call,成员函数需传入实例,这里假设是顶层函数)
        func.call(*convertedArgs.toTypedArray())
    }.onFailure {
        println("软步骤执行失败 [$stepText]:${it.message}")
    }
}

注意事项

  • 反射方案需处理类加载范围,如果步骤分布在多个包,需调整扫描逻辑。
  • 成员函数(非顶层函数)的调用需要获取类实例,若使用Spring等框架,可从上下文获取Bean。

内容的提问来源于stack exchange,提问作者Bar_g5

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:54:52