TDD with Kotlin

Install Homebrew

We're going to start by installing Homebrew which will make installing everything else we need much easier.

Open a Terminal window and copy and paste the following into your terminal:

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Then ensure you have the latest version of Homebrew by typing the following:

$ brew update

And finally, ensure that Homebrew's environment is in a healthy state:

$ brew doctor

This may give you some commands to run in order to fix any problems, which you should do before continuing.

Install Kotlin

Now we're ready to install Kotlin on our system. We'll be using Homebrew to do that.

Kotlin runs on the JVM, so you’ll need to install the Java JDK. We can do that using Homebrew:

$ brew install java

Creating a Project

Let’s create our project structure using Maven:

$ mvn archetype:generate -DgroupId=tdd.piglatin -DartifactId=piglatin-kotlin -Dversion=0.0.1-SNAPSHOT -DarchetypeGroupId=com.github.mhshams -DarchetypeArtifactId=kotlin-quickstart-archetype -DarchetypeVersion=0.2.1 -DinteractiveMode=false
$ cd piglatin-kotlin

Then delete the Greeting class and test defined by the Maven archetype and clean out the resulting class:

$ rm src/main/kotlin/tdd/Greeting.kt
$ rm src/test/kotlin/tdd/GreetingTest.kt
$ mvn clean

Dependency Management

When building a software project you will often use libraries created by other people. In order to both specify and automatically download these dependencies you'll want to use a dependency management system.

Add Testing Framework

In order to express the requirements of our code we're going to use a testing framework. Testing frameworks, at their most basic, allow you to test the output of a function when given a certain input.

Our generated project includes JUnit, the testing library we’ll be using. You can see it listed as a dependency scoped to tests in the generated pom.xml.

Now run mvn test to ensure everything is set up correctly.

Our First Failing Test

Let's create a test for our first requirement - translating apple to appleway.

Then let’s create src/test/kotlin/tdd/PiglatinTest.kt with the following contents:

package tdd.piglatin

import org.junit.Test
import kotlin.test.assertEquals

class PiglatinTest {

    @Test
    fun testTranslateWord_startingWithSingleConsonant() {
        assertEquals("appleway", translateWord("apple"))
    }
}

And run our test using mvn test:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.334 s
[INFO] Finished at: 2016-12-24T16:55:08-05:00
[INFO] Final Memory: 37M/387M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.jetbrains.kotlin:kotlin-maven-plugin:1.0.5-2:test-compile (test-compile) on project piglatin-kotlin: Compilation failure
[ERROR] /Users/bkelly/temp/piglatin-kotlin/src/test/kotlin/tdd/piglatin/PiglatinTest.kt:[9,34] Unresolved reference: Piglatin
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

It’s failing because we haven’t actually created a method for translateWord inside the tdd.piglatin package yet.

Create the Piglatin Class

Create the file src/main/tdd/piglatin/Piglatin.tk with the following contents:

package tdd.piglatin

fun translateWord() {

}

Now we run our tests again:

$ mvn test

[ERROR] /Users/bkelly/temp/piglatin-kotlin/src/test/kotlin/tdd/piglatin/PiglatinTest.kt: (10, 9) Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.
[ERROR] /Users/bkelly/temp/piglatin-kotlin/src/test/kotlin/tdd/piglatin/PiglatinTest.kt: (10, 48) Too many arguments for public fun translateWord(): kotlin.Unit defined in tdd.piglatin

We haven’t met the method contract we’ve defined in the test, we need to return a string:

package tdd.piglatin

fun translateWord(word: String): String {
    return ""
}

And if we run mvn test again we can see that we are not returning the expected value.

$ mvn test

Failed tests:   testTranslateWord_startingWithSingleConsonant(tdd.piglatin.PiglatinTest): expected:<[appleway]> but was:<[]>

Create the Translate Word Method

Fix the Method Signature

Return the Correct Answer

What’s the easiest way to make this pass? How about just returning the string appleway:

package tdd.piglatin

fun translateWord(word: String): String {
    return "appleway"
}

And now running mvn test should result in a passing test:

$ mvn test

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Another Word

Let’s add a second word to our test:

package tdd.piglatin

import org.junit.Test
import kotlin.test.assertEquals

class PiglatinTest {
    @Test
    fun testTranslateWord_startingWithSingleConsonant() {
        assertEquals("appleway", translateWord("apple"))
        assertEquals("orangeway", translateWord("orange"))
    }
}

We run mvn test again and see one test passes and one test fail:

Failed tests:   testTranslateWord_startingWithSingleConsonant(tdd.piglatin.PiglatinTest): expected:<[orang]eway> but was:<[appl]eway>

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

This is because we hardcoded our method to return appleway.

Some Actual Code

Let's add some code to actually do something with the word we are passed.

The Pig Latin rules state that when the word starts with a vowel we just add `way` to the end of it.

Since we only have tests for words starting with vowels, let's do the minimum amount of work to get our test passing - appending `way` to the word that's passed it.

package tdd.piglatin

fun translateWord(word: String): String {
    return word + "way"
}

Then run our test again:

$ mvn test

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0