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
相关产品推荐
相关产品推荐

