-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support step definitions written in Kotlin #1829
Comments
Hi @mpkorstanje , I'm sorry but I think you can forget about the arbitrary strings as method names feature. Kotlin doesn't really allow arbitrary strings, it just allows spaces, but it still has to adhere to the JVM spec (letters, numbers, and currency symbols. No [, ] ^ { } etc...) |
Ah that is too bad. Then we're back to using lambda step definitions. Changed the ticket to match that. |
Wouldn't using the annotations be a suitable solution?
… On 27 Nov 2019, at 04:43, M.P. Korstanje ***@***.***> wrote:
Ah that is too bad. Then we're back to using functional step definitions.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or unsubscribe.
|
Both are valid options. We can either use lambdas: class StepDefinitions(belly: Belly) : En {
init {
Given("there are {int} cukes in my belly"){ cukes : Int ->
belly.setCukes(cukes)
}
}
} Or we can use annotations: class StepDefinitions(belly: Belly) {
@Given("there are {int} cukes in my belly")
fun there_are_cukes_in_my_belly(cukes : Int){
belly.setCukes(cukes)
}
} Neither is perfect. The lambdas must be declared in the Additionally the main hurdle for init {
val Step = fun(expression: String, f: Function<Unit>) {
}
Step("{int} cukes in my {string}") { number: Int, thing: String, value : List<String> ->
}
} However in this example |
Any progress on this? It's a major showstopper as it's hard to sell to dev team Kotlin-Cucumber combo without proper kotlin support. |
@pavelpp is the show stopper major enough that you or someone from your team would be willing to help us implement this? What questions do you have to get started? |
@aslakhellesoy sure, I'd even contribute, if it is not way over my head. Where do I start? |
@pavelpp Kotlins interop with Java i quite good and people are using |
@mpkorstanje Main showstopper is inability to auto-generate step definition snippets using shortcut keys. The fact that I need to actually type a whole bunch of boilerplate code. In the past devs were also scared off by all the regexes in step definitions and when I told them it's auto-generated they were ready to accept. Now, after migration to Kotlin (POC for now) I can not give them this luxury and have to explain that they need to type everything manually. That is not going to fly. |
Do you mean auto-generated snippets by an IDE? |
@mpkorstanje yes. I guess this is actually done by IDE plugin? |
It depends on what you are referring too. When Cucumber executes and encounters undefined steps it will print a snippet. Currently these snippets are in Java not Kotlin (#1520). This happens at runtime. IDE's provide a similar functionality and will do a static analysis to generate code when steps are undefined. It is also worth noting that InteliJ IDEA will conver the Java snippets to Kotlin when pasted. |
@mpkorstanje I meant IDE. When I hit ALT+Enter in IDEA it suggests to create step definition for given step. Works for Java, but not for Kotlin. |
You'll have to ask IDEA to support that unfortunately. |
We've been using cucumber-java in Kotlin for a year or so, and have quite a few scenarios in multiple projects. In IDEA we just copy auto-generated Java snippets from test output into Kotlin code and IDEA converts them into Kotlin automatically. It would be nice to use Alt+Enter, but this workflow is pretty close. BTW it's probably missing feature of Cucumber IDEA plugin not cucumber-java? |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in two months if no further activity occurs. |
In Kotlin I think I would prefer to write something like this: class StepDefinitions(belly: Belly) {
@Given
fun `there are {int} cukes in my belly`(cukes : Int){
belly.setCukes(cukes)
}
} Is it as simple as saying that if the description is not provided, the name of the method is used as the description? |
Unfortunately not. See: #1829 (comment) |
That's unfortunate. I'd still prefer to write that for all the cases where it is possible, but it sucks that Java doesn't just let us use any character in a method name. (At the bytecode level, why should they care?) |
This looks promising: package io.cucumber.kotlin
import kotlin.reflect.KClass
class World {
fun hello() {
println("Hello world!")
}
fun hello(number: Int) {
println("Hello world ${number}!")
}
}
val stepDefinitions = define {
using<World> {
step("hello world") {
hello();
}
step("hello again world {int}") { number: Int ->
hello(number)
}
}
}
The plumbing: fun define(define: StepDefinitions.() -> Unit): StepDefinitions {
val stepDefinitions = StepDefinitions()
stepDefinitions.define()
return stepDefinitions;
}
class StepDefinitions {
val stepDefinitions: MutableList<Any> = mutableListOf()
inline fun <reified T : Any> using(define: Using<T>.() -> Unit) {
val context = Using(T::class)
context.define()
stepDefinitions.addAll(context.stepDefinitions);
}
}
class Using<T : Any>(val kls: KClass<T>) {
val stepDefinitions: MutableList<Any> = mutableListOf()
inline fun step(expression: String, noinline implementation: T.() -> Unit) {
stepDefinitions.add(StepDefinition00(expression, implementation, kls))
}
inline fun <reified V1 : Any> step(expression: String, noinline implementation: T.(a: V1) -> Unit) {
stepDefinitions.add(StepDefinition01(expression, implementation, kls, V1::class))
}
}
data class StepDefinition00<T : Any>(
val expression: String,
val implementation: T.() -> Unit,
val executionContextType: KClass<T>
)
data class StepDefinition01<T : Any, V1 : Any>(
val expression: String,
val implementation: T.(V1) -> Unit,
val executionContextType: KClass<T>,
val expressionArgType01: KClass<V1>
)
fun main() {
stepDefinitions.stepDefinitions.forEach { println(it) }
}
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in two months if no further activity occurs. |
Summary
Cucumber currently supports Kotlin by using Java Annotations or Lambda based step definitions. This works because Java and Kotlin can interoperate. This does however fails to leverage some of the advantages of Kotlin (#1554, #1520). So a Kotlin backend would be most useful.
Update:
The approach describe below will not work in the future because we are aiming for a design where step definitions must be discoverable without instantiating the glue class. For this reason we are deprecating
cucumber-java8
(#2174) and this design was patterned on that. As a replacement forcucumber-java8
we are consideringcucumber-lambda
(#2279). This may or may not make this ticket obsolete.Goals
Implement a Kotlin backend that supports step definitions, hooks, data table and parameter type declaration.
Avoid the duplication of method name and step definition common when using annotation based step definitions. Example:
Use Kotlin primitives and types everywhere. This should include Cucumber expressions, parameter types, data table types,
DataTable
andDocString
.Generate Kotlin step definitions
Step definitions
En
would be implement as a class with members for various keywords and other glue (see java8 equivalent).Note that
Function
has subtypes that support up to up to 22 parameters. After that it becomes a varg and we can no longer userf.javaClass.methods[1].genericParameterTypes
to work out what the the types were. As a result Cucumber won't be able to convert arguments automatically when using regular rather then Cucumber expressions. This is a reasonable drawback for wanting to use 22+ parameters.Steps to take
KotlinBackendProviderService
andKotlinBackend
andKotlinStepDefinition
for a single annotation. For examples seeJava8**
equivalents in thejava8
module.The text was updated successfully, but these errors were encountered: