Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Behaviour driven development in Kotlin with Spek

Behaviour driven development in Kotlin with Spek

Behaviour driven development in Kotlin with Spek. What the benefits are doing BBD, and how you can do so using the Spek framework.

Test should be clean, easy to read and fun to write. So lets introduce some tools to make writing specifications in Spek more fun and easier.

Simon Vergauwen

February 27, 2017
Tweet

More Decks by Simon Vergauwen

Other Decks in Programming

Transcript

  1. BBD testing with Spek
    Simon Vergauwen

    View full-size slide

  2. A Specification Framework

    View full-size slide

  3. What is the goal of BDD?
    • BDD is also referred to as Specification by Example
    • Describe the Specification
    • Ensure the specification by verification
    • A greater focus on the documentary role of such
    specifications
    • Clean and maintained tests
    • We love clean code so why not love clean test code

    View full-size slide

  4. Table of contents
    • Structure
    • Verification and iterative verification
    • Subject Spek
    • Technology Compatibility Kits
    • Mockito kotlin
    • Extending Spek

    View full-size slide

  5. Structure
    • 3 types of scopes
    • Test
    • it
    • Action
    • On
    • Group
    • Describe, context and given

    View full-size slide

  6. Structure
    object FixtureSpec : Spek({
    })

    View full-size slide

  7. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    }

    })

    View full-size slide

  8. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    beforeEachTest { println("hello from outer before each test \n") }


    afterEachTest { println("hello from outer after each test \n") }

    }

    })

    View full-size slide

  9. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    beforeEachTest { println("hello from outer before each test \n") }

    beforeGroup { println("hello from outer before each group \n") }


    afterEachTest { println("hello from outer after each test \n") }

    afterGroup { println("hello from outer after each group \n") }

    }

    })

    View full-size slide

  10. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    beforeEachTest { println("hello from outer before each test \n") }

    beforeGroup { println("hello from outer before each group \n") }


    context("first nested group") {


    }


    given("some other nested group") {


    }


    afterEachTest { println("hello from outer after each test \n") }

    afterGroup { println("hello from outer after each group \n") }

    }

    })

    View full-size slide

  11. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    beforeEachTest { println("hello from outer before each test \n") }

    beforeGroup { println("hello from outer before each group \n") }


    context("first nested group") {

    it("first test") { println("hello from first context test \n") }

    it("second test") { println("hello from second context test \n") }

    }


    given("some other nested group") {

    it("should be 2 as well") { }

    }


    afterEachTest { println("hello from outer after each test \n") }

    afterGroup { println("hello from outer after each group \n") }

    }

    })

    View full-size slide

  12. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    beforeEachTest { println("hello from outer before each test \n") }

    beforeGroup { println("hello from outer before each group \n") }


    context("first nested group") {

    beforeEachTest { println("hello from before each context test \n") }

    it("first test") { println("hello from first context test \n") }

    it("second test") { println("hello from second context test \n") }

    afterEachTest { println("hello from after each context test \n") }

    }


    given("some other nested group") {

    beforeEachTest { println("hello from before each given test \n") }

    it("should be 2 as well") { }

    afterEachTest { println("hello from after each given test \n") }

    }


    afterEachTest { println("hello from outer after each test \n") }

    afterGroup { println("hello from outer after each group \n") }

    }

    })

    View full-size slide

  13. Verification
    //every number after the first two is the sum of the two preceding ones
    fun fibonacci(n: Int): Int {

    tailrec fun loop(n: Int, previous: Int, current: Int): Int =

    if (n == 0) previous else loop(n - 1, current, previous + current)


    return loop(n, 0, 1)

    }

    View full-size slide

  14. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    }

    })

    View full-size slide

  15. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    //Simple test

    it("Seventh fibonacci number is equal to 13") {

    assertThat(fibonacci(7)).isEqualTo(13)

    }

    }

    })

    View full-size slide

  16. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    //Simple test

    it("Seventh fibonacci number is equal to 13") {

    assertThat(fibonacci(7)).isEqualTo(13)

    }


    //generation of data

    //Def fibonacci -> sum of previous 2 items = current

    (2..100).forEach { n ->


    }

    }

    })

    View full-size slide

  17. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    //Simple test

    it("Seventh fibonacci number is equal to 13") {

    assertThat(fibonacci(7)).isEqualTo(13)

    }


    //generation of data

    //Def fibonacci -> sum of previous 2 items = current

    (2..100).forEach { n ->

    val prev = fibonacci(n - 1)

    val beforePrev = fibonacci(n - 2)

    val cur = fibonacci(n)


    }

    }

    })

    View full-size slide

  18. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    //Simple test

    it("Seventh fibonacci number is equal to 13") {

    assertThat(fibonacci(7)).isEqualTo(13)

    }


    //generation of data

    //Def fibonacci -> sum of previous 2 items = current

    (2..100).forEach { n ->

    val prev = fibonacci(n - 1)

    val beforePrev = fibonacci(n - 2)

    val cur = fibonacci(n)


    it("$cur should be the sum of the two previous fibonacci numbers: $prev, $beforePrev") {

    assertThat(prev + beforePrev).isEqualTo(cur)

    }

    }

    }

    })

    View full-size slide

  19. Subject Spek
    open class Calculator {

    fun add(x: Int, y: Int) = x + y


    fun subtract(x: Int, y: Int) = x - y


    fun divide(x: Int, y: Int) = if (y == 0) throw IllegalArgumentException() else x / y

    }


    class AdvancedCalculator : Calculator() {

    fun pow(base: Int, exponent: Int) = Math.pow(base.toDouble(), exponent.toDouble()).toInt()

    }

    View full-size slide

  20. Subject Spek
    object CalculatorSpec : SubjectSpek({

    subject { Calculator() }


    })

    View full-size slide

  21. Subject Spek
    object CalculatorSpec : SubjectSpek({

    subject { Calculator() }


    describe("addition") {

    it("should return the result of adding the first number to the second number") {

    assertThat(subject.add(2, 4)).isEqualTo(6)

    }

    }

    })

    View full-size slide

  22. Subject Spek
    object CalculatorSpec : SubjectSpek({

    subject { Calculator() }


    describe("addition") {

    it("should return the result of adding the first number to the second number") {

    assertThat(subject.add(2, 4)).isEqualTo(6)

    }

    }


    describe("subtract") {

    it("should return the result of subtracting the second number from the first number") {

    assertThat(subject.subtract(2, 4)).isEqualTo(-2)

    }

    }

    })

    View full-size slide

  23. Subject Spek
    object CalculatorSpec : SubjectSpek({

    subject { Calculator() }


    describe("addition") {

    it("should return the result of adding the first number to the second number") {

    assertThat(subject.add(2, 4)).isEqualTo(6)

    }

    }


    describe("subtract") {

    it("should return the result of subtracting the second number from the first number") {

    assertThat(subject.subtract(2, 4)).isEqualTo(-2)

    }

    }


    describe("division") {

    it("should return the result of dividing the first number by the second number") {

    assertThat(subject.divide(4, 2)).isEqualTo(2)

    }


    context("division by zero") {

    it("should throw an exception") {

    assertThatThrownBy({ subject.divide(2, 0) })

    .isInstanceOf(IllegalArgumentException::class.java)

    }

    }

    }

    })

    View full-size slide

  24. Subject Spek
    object AdvancedCalculatorSpec : SubjectSpek({

    subject { AdvancedCalculator() }

    itBehavesLike(CalculatorSpec)


    })

    View full-size slide

  25. Subject Spek
    object AdvancedCalculatorSpec : SubjectSpek({

    subject { AdvancedCalculator() }

    itBehavesLike(CalculatorSpec)


    describe("pow") {

    it("should return the power of base raise to exponent") {

    assertThat(subject.pow(2, 2)).isEqualTo(4)

    }

    }

    })

    View full-size slide

  26. Technology Compatibility Kits
    Collection
    List MutableList Set MutableSet

    View full-size slide

  27. Technology Compatibility Kits
    public interface Collection : Iterable {

    val size: Int


    public fun isEmpty(): Boolean


    public operator fun contains(element: @UnsafeVariance E): Boolean


    override fun iterator(): Iterator


    public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean

    }

    View full-size slide

  28. Technology Compatibility Kits
    abstract class CollectionSpek(private val collection: Collection) : Spek({


    })

    View full-size slide

  29. Technology Compatibility Kits
    private val items = arrayOf(1, 2, 3, 4)


    abstract class CollectionSpek(private val collection: Collection) : Spek({


    describe("requesting the size") {

    it("$collection should have size ${items.size}"){

    assertThat(collection.size).isEqualTo(items.size)

    }

    }


    })

    View full-size slide

  30. Technology Compatibility Kits
    private val items = arrayOf(1, 2, 3, 4)
    abstract class CollectionSpek(private val collection: Collection) : Spek({


    describe("requesting the size") {

    it("$collection should have size ${items.size}"){

    assertThat(collection.size).isEqualTo(items.size)

    }

    }


    })


    class SetCollectionSpek : CollectionSpek(setOf(*items))


    class ListCollectionSpek : CollectionSpek(items.asList())

    View full-size slide

  31. Technology Compatibility Kits
    interface ComplexClass {

    fun doSomethingComplex(): Boolean

    }

    View full-size slide

  32. Technology Compatibility Kits
    interface ComplexClass {

    fun doSomethingComplex(): Boolean

    }


    class ComplexImpl : ComplexClass {

    override fun doSomethingComplex() = true

    }

    View full-size slide

  33. Technology Compatibility Kits
    interface ComplexClass {

    fun doSomethingComplex(): Boolean

    }


    class ComplexImpl : ComplexClass {

    override fun doSomethingComplex() = true

    }


    class SuccesDetermination : ComplexClass {

    override fun doSomethingComplex(): Boolean {

    TODO()

    }

    }


    class ComplexImplWithDependency(succesDetermination: SuccesDetermination) :

    ComplexClass by succesDetermination

    View full-size slide

  34. Technology Compatibility Kits
    abstract class ComplexSpek(private val factory: () -> ComplexClass) : Spek({


    val subject = factory.invoke()


    describe("A complex class should succeed") {

    assertThat(subject.doSomethingComplex()).isTrue()


    }


    })

    View full-size slide

  35. Technology Compatibility Kits
    abstract class ComplexSpek(private val factory: () -> ComplexClass) : Spek({


    val subject = factory.invoke()


    describe("A complex class should succeed") {

    assertThat(subject.doSomethingComplex()).isTrue()


    }


    })


    object ComplexImplComplexSpek : ComplexSpek({ ComplexImpl() })

    View full-size slide

  36. Technology Compatibility Kits
    abstract class ComplexSpek(private val factory: () -> ComplexClass) : Spek({


    val subject = factory.invoke()


    describe("A complex class should succeed") {

    assertThat(subject.doSomethingComplex()).isTrue()


    }


    })


    object ComplexImplComplexSpek : ComplexSpek({ ComplexImpl() })


    object ComplexImplWithDependencyComplexSpek : ComplexSpek({

    val succesDetermination = mock {

    on { doSomethingComplex() } doReturn true

    }


    ComplexImplWithDependency(succesDetermination)

    })

    View full-size slide

  37. Mockito kotlin
    testCompile "com.nhaarman:mockito-kotlin:1.3.0"

    View full-size slide

  38. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    }

    })

    View full-size slide

  39. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("calling doSomething") {

    mock.doSomething()

    verify(mock).doSomething()

    }


    }

    })

    View full-size slide

  40. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("assigning a value") {

    mock2.someOtherStringValue = "stringOtherValue2"


    verify(mock2).someOtherStringValue = "stringOtherValue2"

    }


    }

    })

    View full-size slide

  41. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("capturing a single value") {

    mock.setSomething(listOf("1", "2"))


    verify(mock).setSomething(argThat { size == 2 })

    }


    }

    })

    View full-size slide

  42. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("capturing a single value with multiple assertions") {

    mock.setSomething(listOf("1", "2"))


    verify(mock).setSomething(check {

    assertThat(it.size).isEqualTo(2)

    assertThat(it[0]).isEqualTo("1")

    })

    }

    }

    })

    View full-size slide

  43. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("capturing multiple value with multiple assertions") {

    mock.setSomething(listOf("1", "2"))

    mock.setSomething(listOf("3", "4"))


    argumentCaptor>().apply {

    verify(mock, times(2)).setSomething(capture())


    assertThat(allValues.size).isEqualTo(2)

    assertThat(firstValue).isEqualTo(listOf("1", "2"))

    }

    }


    }

    })

    View full-size slide

  44. Mockito kotlin
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    val mock = mock()


    val mock2 = mock {

    on { getStringValue() } doReturn "stringValue"

    on { someOtherStringValue } doReturn "stringOtherValue"

    }


    beforeEachTest { clearInvocations(mock, mock2) }


    it("testing order of execution") {

    mock.getStringValue()

    mock2.getStringValue()


    inOrder(mock, mock2) {

    verify(mock).getStringValue()

    verify(mock2).getStringValue()

    }

    }

    }

    })

    View full-size slide

  45. Mockito kotlin
    class MyOtherClass(val number: Int) {


    init {

    if(number <= 1) error("$number should be greater than 1")

    }

    }

    View full-size slide

  46. Mockito kotlin
    class MyOtherClass(val number: Int) {


    init {

    if(number <= 1) error("$number should be greater than 1")

    }

    }
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    MockitoKotlin.registerInstanceCreator { MyOtherClass(2) }

    }

    })

    View full-size slide

  47. Mockito kotlin
    class MyOtherClass(val number: Int) {


    init {

    if(number <= 1) error("$number should be greater than 1")

    }

    }
    class MockitoExample : Spek({


    describe("mockito-kotlin") {


    MockitoKotlin.registerInstanceCreator { MyOtherClass(2) }


    val mock3 = mock()


    it("verify that mocked correctly") {

    assertThat(mock3.number).isEqualTo(2) //FAILS!!!

    }

    }

    })

    View full-size slide

  48. Extending Spek
    infix fun Pair.to(that: C) =

    Triple(this.first, this.second, that)


    fun TestContainer.tableOf(vararg elements: T, f: (TestContainer.(elements: T) -> Unit)) =

    elements.toList().forEach { f.invoke(this, it) }


    class TableSpekTest : Spek({


    })

    View full-size slide

  49. Extending Spek
    infix fun Pair.to(that: C) =

    Triple(this.first, this.second, that)


    fun TestContainer.tableOf(vararg elements: T, f: (TestContainer.(elements: T) -> Unit)) =

    elements.toList().forEach { f.invoke(this, it) }


    class TableSpekTest : Spek({

    describe("zipping lists") {

    tableOf(

    listOf(1, 2, 3) to listOf(4, 5, 6) to listOf(5, 7, 9),

    listOf(1, 2, 3) to listOf(1, 2, 3) to listOf(2, 4, 6),

    listOf(4, 5, 6) to listOf(1, 2, 3) to listOf(5, 7, 9)

    ) { (l1, l2, result) ->

    }

    }

    })

    View full-size slide

  50. Extending Spek
    infix fun Pair.to(that: C) =

    Triple(this.first, this.second, that)


    fun TestContainer.tableOf(vararg elements: T, f: (TestContainer.(elements: T) -> Unit)) =

    elements.toList().forEach { f.invoke(this, it) }


    class TableSpekTest : Spek({

    describe("zipping lists") {

    tableOf(

    listOf(1, 2, 3) to listOf(4, 5, 6) to listOf(5, 7, 9),

    listOf(1, 2, 3) to listOf(1, 2, 3) to listOf(2, 4, 6),

    listOf(4, 5, 6) to listOf(1, 2, 3) to listOf(5, 7, 9)

    ) { (l1, l2, result) ->


    it("zipping $l1 and $l2 should result in $result") {

    assertThat(l1.zip(l2, { i1, i2 -> i1 + i2 })).isEqualTo(result)

    }

    }

    }

    })

    View full-size slide

  51. BBD testing with Spek
    Questions?
    Simon Vergauwen

    View full-size slide