Experimenting with BDD syntax in Kotlin, I tried these two styles:
fun main(args: Array<String>) { println(So GIVEN "an apple" WHEN "it falls" THEN "Newton thinks") } data class BDD constructor( val GIVEN: String, val WHEN: String, val THEN: String) { companion object { val So = So() } class So { infix fun GIVEN(GIVEN: String) = Given(GIVEN) data class Given(private val GIVEN: String) { infix fun WHEN(WHEN: String) = When(GIVEN, WHEN) data class When(private val GIVEN: String, private val WHEN: String) { infix fun THEN(THEN: String) = BDD(GIVEN, WHEN, THEN) } } } }
And:
fun main(args: Array<String>) { println(GIVEN `an apple` WHEN `it falls` THEN `Newton thinks` QED) } infix fun Given.`an apple`(WHEN: When) = When() infix fun When.`it falls`(THEN: Then) = Then(GIVEN) infix fun Then.`Newton thinks`(QED: Qed) = BDD(GIVEN, WHEN) inline fun whoami() = Throwable().stackTrace[1].methodName data class BDD(val GIVEN: String, val WHEN: String, val THEN: String = whoami()) { companion object { val GIVEN = Given() val WHEN = When() val THEN = Then("") val QED = Qed() } class Given class When(val GIVEN: String = whoami()) class Then(val GIVEN: String, val WHEN: String = whoami()) class Qed }
Comparing main()
methods, which is easier to read or use? I
haven't tried implementing, just have looked at testing code style. Note
that I'm using the
infix
feature of Kotlin to have my BDD
"GIVEN/WHEN/THEN" as punctuation free as I'm able.
In the one case—using strings to describe cases—, an
implementation would be more similar to Spec or Cucumber, which usually
uses pattern matching to associate text with implementation. In the other
case—using functions to describe cases—, an implementation
goes directly into the function definition. In either case, Kotlin only
supports binary infix functions, not unary (of course, you say, that's
what "infix" means!), so I need either an initial starting token
(So
in the strings case) or an ending one (QED
in the functions case).
I'm curious how implementation sorts out.
UPDATE:
I have working code now that runs these BDD sentences but remain unclear which of the two styles (strings vs functions) would be easier to work with:
fun main(args: Array<String>) { var apple: Apple? = null upon("an apple") { apple = Apple(Newton(thinking = false)) } upon("it falls") { apple?.falls() } upon("Newton thinks") { assert(apple?.physicist?.thinking ?: false) { "Newton is sleeping" } } println(So GIVEN "an apple" WHEN "it falls" THEN "Newton thinks") }
Vs:
fun main(args: Array<String>) { println(GIVEN `an apple` WHEN `it falls` THEN `Newton thinks` QED) } var apple: Apple? = null infix fun Given.`an apple`(WHEN: When) = upon(this) { apple = Apple(Newton(thinking = false)) } infix fun When.`it falls`(THEN: Then) = upon(this) { apple?.falls() } infix fun Then.`Newton thinks`(QED: Qed) = upon(this) { assert(apple?.physicist?.thinking ?: false) { "Newton is sleeping" } }
The strings style is certainly more familiar. However, mistakes in registering matches of "GIVEN/WHEN/THEN" clauses appear at runtime and do not provide much help.
The functions style is more obtuse. However, mistakes cause compile-time errors that are easier to understand, and your code editor can navigate between declaration and usage.
No comments:
Post a Comment