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. 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
  2. Table of contents • Structure • Verification and iterative verification

    • Subject Spek • Technology Compatibility Kits • Mockito kotlin • Extending Spek
  3. Structure • 3 types of scopes • Test • it

    • Action • On • Group • Describe, context and given
  4. 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") }
 }
 })
  5. 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") }
 }
 })
  6. 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") }
 }
 })
  7. 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") }
 }
 })
  8. 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") }
 }
 })
  9. 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)
 }
  10. Verification object FibonacciTest : Spek({
 describe("a fibonacci calculator") {
 


    //Simple test
 it("Seventh fibonacci number is equal to 13") {
 assertThat(fibonacci(7)).isEqualTo(13)
 } 
 }
 })
  11. 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 ->
 
 }
 }
 })
  12. 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)
 
 }
 }
 })
  13. 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)
 }
 }
 }
 })
  14. 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()
 }
  15. Subject Spek object CalculatorSpec : SubjectSpek<Calculator>({
 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)
 }
 }
 })
  16. Subject Spek object CalculatorSpec : SubjectSpek<Calculator>({
 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)
 }
 } 
 })
  17. Subject Spek object CalculatorSpec : SubjectSpek<Calculator>({
 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)
 }
 }
 }
 })
  18. Subject Spek object AdvancedCalculatorSpec : SubjectSpek<AdvancedCalculator>({
 subject { AdvancedCalculator() }


    itBehavesLike(CalculatorSpec)
 
 describe("pow") {
 it("should return the power of base raise to exponent") {
 assertThat(subject.pow(2, 2)).isEqualTo(4)
 }
 }
 })
  19. Technology Compatibility Kits public interface Collection<out E> : Iterable<E> {


    val size: Int
 
 public fun isEmpty(): Boolean
 
 public operator fun contains(element: @UnsafeVariance E): Boolean
 
 override fun iterator(): Iterator<E>
 
 public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
 }
  20. Technology Compatibility Kits private val items = arrayOf(1, 2, 3,

    4)
 
 abstract class CollectionSpek<T : Any>(private val collection: Collection<T>) : Spek({
 
 describe("requesting the size") {
 it("$collection should have size ${items.size}"){
 assertThat(collection.size).isEqualTo(items.size)
 }
 }
 
 })
  21. Technology Compatibility Kits private val items = arrayOf(1, 2, 3,

    4) abstract class CollectionSpek<T : Any>(private val collection: Collection<T>) : Spek({
 
 describe("requesting the size") {
 it("$collection should have size ${items.size}"){
 assertThat(collection.size).isEqualTo(items.size)
 }
 }
 
 })
 
 class SetCollectionSpek : CollectionSpek<Int>(setOf(*items))
 
 class ListCollectionSpek : CollectionSpek<Int>(items.asList())
  22. Technology Compatibility Kits interface ComplexClass {
 fun doSomethingComplex(): Boolean
 }


    
 class ComplexImpl : ComplexClass {
 override fun doSomethingComplex() = true
 }
  23. 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
  24. 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()
 
 }
 
 })
  25. 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() })
  26. 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<SuccesDetermination> {
 on { doSomethingComplex() } doReturn true
 }
 
 ComplexImplWithDependency(succesDetermination)
 })
  27. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 on { getStringValue() } doReturn "stringValue"
 on { someOtherStringValue } doReturn "stringOtherValue"
 }
 
 beforeEachTest { clearInvocations(mock, mock2) }
 
 }
 })
  28. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 on { getStringValue() } doReturn "stringValue"
 on { someOtherStringValue } doReturn "stringOtherValue"
 }
 
 beforeEachTest { clearInvocations(mock, mock2) }
 
 it("calling doSomething") {
 mock.doSomething()
 verify(mock).doSomething()
 }
 
 }
 })
  29. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 on { getStringValue() } doReturn "stringValue"
 on { someOtherStringValue } doReturn "stringOtherValue"
 }
 
 beforeEachTest { clearInvocations(mock, mock2) }
 
 it("assigning a value") {
 mock2.someOtherStringValue = "stringOtherValue2"
 
 verify(mock2).someOtherStringValue = "stringOtherValue2"
 }
 
 }
 })
  30. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 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 })
 }
 
 }
 })
  31. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 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")
 })
 } 
 }
 })
  32. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 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<List<String>>().apply {
 verify(mock, times(2)).setSomething(capture())
 
 assertThat(allValues.size).isEqualTo(2)
 assertThat(firstValue).isEqualTo(listOf("1", "2"))
 }
 }
 
 }
 })
  33. Mockito kotlin class MockitoExample : Spek({
 
 describe("mockito-kotlin") {
 


    val mock = mock<MyClass>()
 
 val mock2 = mock<MyClass> {
 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()
 }
 }
 }
 })
  34. Mockito kotlin class MyOtherClass(val number: Int) {
 
 init {


    if(number <= 1) error("$number should be greater than 1")
 }
 }
  35. 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) } 
 }
 })
  36. 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<MyOtherClass>()
 
 it("verify that mocked correctly") {
 assertThat(mock3.number).isEqualTo(2) //FAILS!!!
 }
 }
 })
  37. Extending Spek infix fun <A, B, C> Pair<A, B>.to(that: C)

    =
 Triple(this.first, this.second, that)
 
 fun <T> TestContainer.tableOf(vararg elements: T, f: (TestContainer.(elements: T) -> Unit)) =
 elements.toList().forEach { f.invoke(this, it) }
 
 class TableSpekTest : Spek({
 
 })
  38. Extending Spek infix fun <A, B, C> Pair<A, B>.to(that: C)

    =
 Triple(this.first, this.second, that)
 
 fun <T> 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) ->
 }
 }
 })
  39. Extending Spek infix fun <A, B, C> Pair<A, B>.to(that: C)

    =
 Triple(this.first, this.second, that)
 
 fun <T> 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)
 }
 }
 }
 })