Wednesday, May 4, 2016

You Will Test the Way We Want You to Test

That sounds like a line from Hogan's Heroes. But really it's JUinit. I can't believe the lack of alternatives in the java-sphere. Actually, this extends to Kotlin, too. I've implemented the Entitas api in Kotlin, and it needs to be tested. And all I get is JUint? Actually, there is Spek, but it requires JUnit...

I suppose that would be OK if I were only calling my Kotlin code from Java. But I'm not, I'm calling from Kotlin, and JUnit can't test idomatic usage. Plus, btw - have I mentioned lately that I hate java? That's why I'm using Kotlin.

So far, Kotlin's been doing a good job of insulating me from java, and I'd like to keep it that way. So I've written a unit test framework before - I can write one in Kotlin. In fact, I just did, it doesn't take long.
~ here is an example of usage:

fun main(args: Array<String>) {
    var k1 = 0
    var k2 = 0
    var k3 = 0

    TestIt("this is the test suite")
    .beforeEach {
        k1 += 1
    }.afterEach {
        k2 += 1
    }.onDone {
        k3 = k1 + k2
    }.run {

        it("this is the test") {
            it.equals(1, 1)
        }

        it("this is another test") {
            it.equals("fred", 1)
        }

    }

}

And, here is the TestIt code, the thing about kotlin is that it's so easy and succinct. The entire framework fits in on file, and is about 80 lines of code.


class TestIt(name:String) {

    private var name = name
    private var beforeProc = {}
    private var afterProc = {}
    private var doneProc = {}
    private val tests:MutableList = mutableListOf()

    data class Test(val name:String, val proc:(assert) -> Unit) {}

    object assert {
        var result = false
        var actual:Any? = null
        var expected:Any? = null
        fun equals(actual:Any, expected:Any):Boolean {
            if (actual.equals(expected)) {
                this.result = true
            } else {
                this.result = false
                this.actual = actual
                this.expected = expected
            }
            return this.result
        }
    }

    fun beforeEach(proc:() -> Unit):TestIt {
        beforeProc = proc
        return this
    }

    fun afterEach(proc:() -> Unit):TestIt {
        afterProc = proc
        return this
    }

    fun onDone(proc:() -> Unit):TestIt {
        doneProc = proc
        return this
    }

    fun run(main:(func:(name:String, proc:(assert) -> Unit) -> Unit) -> Unit) {

        val fail = "\u001b[31m" /* VT100 RED */
        val pass = "\u001b[32m" /* VT100 GREEN */
        val reset = "\u001b[0m" /* VT100 RESET */
        var passed = 0
        var failed = 0

        main {name:String, proc:(assert) -> Unit ->
            val ignore  = tests.add(Test(name, proc))
        }

        println("\t$name\n---------------------------------")
        for (test in tests) {
            assert.result = true
            beforeProc()
            test.proc(assert)
            afterProc()
            if (assert.result) {
                passed++
                println("${pass}PASS${reset} <=> ${test.name}")
            } else {
                failed++
                println("${fail}FAIL${reset} <=> ${test.name}")
                println("     expected ${fail}[${reset}${assert.expected}${fail}]${reset}")
                println("       actual ${fail}[${reset}${assert.actual}${fail}]${reset}")
            }
        }
        doneProc()
        println("---------------------------------")
        println("    <====> Pass: $passed")
        println("    <====> Fail: $failed\n\n")
    }
}


No comments:

Post a Comment