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 Slide

  2. A Specification Framework

    View 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 Slide

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

    View Slide

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

    View Slide

  6. Structure
    object FixtureSpec : Spek({
    })

    View Slide

  7. Structure
    object FixtureSpec : Spek({

    describe("spek fixtures"){

    }

    })

    View 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 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 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 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 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 Slide

  13. Structure

    View Slide

  14. 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 Slide

  15. Verification
    object FibonacciTest : Spek({

    describe("a fibonacci calculator") {


    }

    })

    View 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)

    }

    }

    })

    View 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 ->


    }

    }

    })

    View 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)


    }

    }

    })

    View Slide

  19. 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 Slide

  20. 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 Slide

  21. Subject Spek
    object CalculatorSpec : SubjectSpek({

    subject { Calculator() }


    })

    View 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)

    }

    }

    })

    View 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)

    }

    }

    })

    View Slide

  24. 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 Slide

  25. Subject Spek
    object AdvancedCalculatorSpec : SubjectSpek({

    subject { AdvancedCalculator() }

    itBehavesLike(CalculatorSpec)


    })

    View Slide

  26. 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 Slide

  27. Technology Compatibility Kits
    Collection
    List MutableList Set MutableSet

    View Slide

  28. 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 Slide

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


    })

    View 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)

    }

    }


    })

    View Slide

  31. 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 Slide

  32. Technology Compatibility Kits
    interface ComplexClass {

    fun doSomethingComplex(): Boolean

    }

    View Slide

  33. Technology Compatibility Kits
    interface ComplexClass {

    fun doSomethingComplex(): Boolean

    }


    class ComplexImpl : ComplexClass {

    override fun doSomethingComplex() = true

    }

    View Slide

  34. 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 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()


    }


    })

    View 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() })

    View Slide

  37. 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 Slide

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

    View 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) }


    }

    })

    View 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("calling doSomething") {

    mock.doSomething()

    verify(mock).doSomething()

    }


    }

    })

    View 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("assigning a value") {

    mock2.someOtherStringValue = "stringOtherValue2"


    verify(mock2).someOtherStringValue = "stringOtherValue2"

    }


    }

    })

    View 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") {

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


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

    }


    }

    })

    View 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 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 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("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 Slide

  45. 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 Slide

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


    init {

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

    }

    }

    View 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) }

    }

    })

    View Slide

  48. 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 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({


    })

    View 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) ->

    }

    }

    })

    View Slide

  51. 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 Slide

  52. BBD testing with Spek
    Questions?
    Simon Vergauwen

    View Slide