Showing posts with label bdd. Show all posts
Showing posts with label bdd. Show all posts

Saturday, March 11, 2017

Two BDD styles in Kotlin

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.

(Code here.)

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.

Thursday, April 07, 2016

BDD-style fluent testing in BASH

I wanted to impress on my colleagues that BASH was still hip, still relevant. And that I wasn't an old hacker. So I wrote a small BDD test framework for BASH.

Techniques

Fluent coding relies on several BASH features:

  • Variable expansion happens before executing commands
  • A shell function is indistinguishable from a program: they are called the same way
  • Local function variables are dynamically scoped but only within a function, so are visible to other functions called within that scope, directly or indirectly through further function calls

Together with Builder pattern, it's easy to write given/when/then tests. (Builder pattern here solves the problem not of telescoping constructors, but massive, arbitrary argument lists.)

So when you run:

function c {
    echo "$message"
}

function b {
    "$@"
}

function a {
    local message="$1"
    shift
    "$@"
}

a "Bob's your uncle" b c

You see the output:

Bob's your uncle

How does this work?

First BASH expands variables. In function a this means that after the first argument is remembered and removed from the argument list, "$@" expands to b c. Then b c is executed.

Then BASH calls the function b with argument "c". Similarly b expands "$@" to c and calls it.

Finally as $message is visible in functions called by a, c prints the first argument passed to a (as it was remebered in the variable $message), or "Bob's your uncle" in this example.

Running the snippet with xtrace makes this clear (assuming the snippet is saved in a file named example):

bash -x example
+ a 'Bob'\''s your uncle' b c
+ local 'message=Bob'\''s your uncle'
+ shift
+ b c
+ c
+ echo 'Bob'\''s your uncle'
Bob's your uncle

So the test functions for given_jar, when_run and then_expect (along with other, similar functions) work the same way. Keep this in mind.

Application

So how does this buy me fluent BDD?

Given these functions:

function then_expect {
    local expectation="$1"
    shift

    if [[ some_test "$pre_condition" "$condition" "$expectation" ]]
    then
        echo "PASS: $scenario"
        return 0
    else
        echo "FAIL: $scenario"
        return 1
    fi
}

function when {
    local condition="$1"
    shift
    "$@"
}

function given {
    local pre_condition="$1"
    shift
    "$@"
}

function scenario {
    local scenario="$1"
    shift
    "$@"
}

When you write:

scenario "Some test case" \
    given "Something always true" \
    when "Something you want to test" \
    then_expect "Some outcome"

Then it executes:

some_test "Something always true" "Something you want to test" "Some outcome"

And prints one of:

PASS Some test case
FAIL Some test case

A real example at https://github.com/binkley/shell/tree/master/testing-bash.