Kotlin, Spek and tests

51
Kotlin, Spek and tests How to use Kotlin for your test suite, and why you might want to Konrad Morawski

Transcript of Kotlin, Spek and tests

Page 1: Kotlin, Spek and tests

Kotlin, Spek and testsHow to use Kotlin for your test suite,and why you might want to

Konrad Morawski

Page 2: Kotlin, Spek and tests

http://kotlinlang.org/

Page 3: Kotlin, Spek and tests

http://kotlinlang.org/

● first unveiled in 2011● 1.0 only just released! (on February 15)

● 100% interoperability with Java

● backed by JetBrains (IntelliJ Idea ➠ Android Studio)

● open-source

● available outside of, but suited for Android● supported in Android Studio (IDE plugins)

● resembling: Swift, Scala, C#?

Page 4: Kotlin, Spek and tests

Kotlin in action

JavaString str = "type value";

int add(int a, int b)

Kotlinval str: String = "value: type"

val str = "value (inferred...)"

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

Page 5: Kotlin, Spek and tests

Lambdas

val sum: (Int, Int) -> Int = { x, y -> x + y }

sum(1, 2) == 3 // true

fun apply(i: Int, f: (Int -> Int) = f(i)

apply(2, { x -> x + 25})

apply(2) { x -> x + 25}

listOf(1, 2, 3).filter { it > 2 }

button.setOnClickListener {

Log.d("clicked!")

}

Page 6: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Java)Iterable<String> getTopWords(String text) {

String[] words = text.split(" ");

Map<String, Integer> indexed = new HashMap<>();

for (String word : words) {

if (word.length() <= 3) continue;

if (!indexed.containsKey(word)) indexed.put(word, 0);

indexed.put(word, indexed.get(word) + 1);

}

List<Map.Entry<String, Integer>> sorted = new ArrayList<>(indexed.entrySet());

Collections.sort(sorted, new Comparator<Map.Entry<String, Integer>>() {

@Override

public int compare(Map.Entry<String, Integer> one, Map.Entry<String, Integer> another) {

return another.getValue().compareTo(one.getValue());

}});

List<String> topTen = new ArrayList<>();

for (Map.Entry<String, Integer> top : sorted) {

topTen.add(top.getKey());

if (topTen.size() == 10)

break;

}

return topTen;

}

Page 7: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Java)Iterable<String> getTopWords(String text) {

String[] words = text.split(" ");

Map<String, Integer> indexed = new HashMap<>();

for (String word : words) {

if (word.length() <= 3) continue;

if (!indexed.containsKey(word)) indexed.put(word, 0);

indexed.put(word, indexed.get(word) + 1);

}

List<Map.Entry<String, Integer>> sorted = new ArrayList<>(indexed.entrySet());

Collections.sort(sorted, new Comparator<Map.Entry<String, Integer>>() {

@Override

public int compare(Map.Entry<String, Integer> one, Map.Entry<String, Integer> another) {

return another.getValue().compareTo(one.getValue());

}});

List<String> topTen = new ArrayList<>();

for (Map.Entry<String, Integer> top : sorted) {

topTen.add(top.getKey());

if (topTen.size() == 10)

break;

}

return topTen;

}

Page 8: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Java)Iterable<String> getTopWords(String text) {

String[] words = text.split(" ");

Map<String, Integer> indexed = new HashMap<>();

for (String word : words) {

if (word.length() <= 3) continue;

if (!indexed.containsKey(word)) indexed.put(word, 0);

indexed.put(word, indexed.get(word) + 1);

}

List<Map.Entry<String, Integer>> sorted = new ArrayList<>(indexed.entrySet());

Collections.sort(sorted, new Comparator<Map.Entry<String, Integer>>() {

@Override

public int compare(Map.Entry<String, Integer> one, Map.Entry<String, Integer> another) {

return another.getValue().compareTo(one.getValue());

}});

List<String> topTen = new ArrayList<>();

for (Map.Entry<String, Integer> top : sorted) {

topTen.add(top.getKey());

if (topTen.size() == 10)

break;

}

return topTen;

}

Page 9: Kotlin, Spek and tests

Iterable<String> getTopWords(String text) {

String[] words = text.split(" ");

Map<String, Integer> indexed = new HashMap<>();

for (String word : words) {

if (word.length() <= 3) continue;

if (!indexed.containsKey(word)) indexed.put(word, 0);

indexed.put(word, indexed.get(word) + 1);

}

List<Map.Entry<String, Integer>> sorted = new ArrayList<>(indexed.entrySet());

Collections.sort(sorted, new Comparator<Map.Entry<String, Integer>>() {

@Override

public int compare(Map.Entry<String, Integer> one, Map.Entry<String, Integer> another) {

return another.getValue().compareTo(one.getValue());

}});

List<String> topTen = new ArrayList<>();

for (Map.Entry<String, Integer> top : sorted) {

topTen.add(top.getKey());

if (topTen.size() == 10)

break;

}

return topTen;

}

Top 10 words, at least 3 characters long (Kotlin)fun getTopWords(text: String): Iterable<String> {

val words = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

val indexed = HashMap<String, Int>()

for (word in words) {

if (word.length <= 3) continue

if (!indexed.containsKey(word)) indexed.put(word, 0)

indexed.put(word, indexed[word]!! + 1)

}

val sorted = ArrayList(indexed.entries)

Collections.sort(sorted) { one, another -> another.value.compareTo(one.value) }

val topTen = ArrayList<String>()

for (top in sorted) {

topTen.add(top.key)

if (topTen.size == 10) break

}

return topTen

}

Page 10: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Kotlin)fun getTopWords(text: String): Iterable<String> {

val words = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

val indexed = HashMap<String, Int>()

for (word in words) {

if (word.length <= 3) continue

if (!indexed.containsKey(word)) indexed.put(word, 0)

indexed.put(word, indexed[word]!! + 1)

}

val sorted = ArrayList(indexed.entries)

Collections.sort(sorted) { one, another -> another.value.compareTo(one.value) }

val topTen = ArrayList<String>()

for (top in sorted) {

topTen.add(top.key)

if (topTen.size == 10) break

}

return topTen

}

Page 11: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Kotlin)fun getTopWords(text: String): Iterable<String> {

val words = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

val indexed = HashMap<String, Int>()

for (word in words) {

if (word.length <= 3) continue

if (!indexed.containsKey(word)) indexed.put(word, 0)

indexed.put(word, indexed[word]!! + 1)

}

val sorted = ArrayList(indexed.entries)

Collections.sort(sorted) { one, another -> another.value.compareTo(one.value) }val topTen = ArrayList<String>()

for (top in sorted) {

topTen.add(top.key)

if (topTen.size == 10) break

}

return topTen

}

Page 12: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Kotlin)Collections.sort(sorted) { one, another -> another.value.compareTo(one.value) }

Page 13: Kotlin, Spek and tests

Top 10 words, at least 3 characters longCollections.sort(sorted) { one, another -> another.value.compareTo(one.value) }

new Comparator<Map.Entry<String, Integer>>() {

@Override

public int compare(Map.Entry<String, Integer> one, Map.Entry<String, Integer> another) {

return another.getValue().compareTo(one.getValue());

}

});

THIS ISN'T NORMAL.

BUT IN JAVA IT IS.

Page 14: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (Kotlin)fun getTopWords(text: String): Iterable<String> {

val words = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

val indexed = HashMap<String, Int>()

for (word in words) {

if (word.length <= 3) continue

if (!indexed.containsKey(word)) indexed.put(word, 0)

indexed.put(word, indexed[word]!! + 1)

}

val sorted = ArrayList(indexed.entries)

Collections.sort(sorted) { one, another -> another.value.compareTo(one.value) }

val topTen = ArrayList<String>()

for (top in sorted) {

topTen.add(top.key)

if (topTen.size == 10) break

}

return topTen

}

Page 15: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (idiomatic Kotlin)fun getTopWords(text: String): Iterable<String> = text

.splitToSequence(" ")

.filter { it.length > 3 }

.groupBy { it }

.asIterable()

.sortedByDescending { it.value.count() }

.take(10)

.map { it.key }

Page 16: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (idiomatic Kotlin)

fun getTopWords(text: String): Iterable<String> = text

.splitToSequence(" ")

.filter { it.length > 3 }

.groupBy { it }

.asIterable()

.sortedByDescending { it.value.count() }

.take(10)

.map { it.key }

Page 17: Kotlin, Spek and tests

Top 10 words, at least 3 characters long (lambdas).filter { it.length > 3 }

.filter { word -> word.length > 3 }

Page 18: Kotlin, Spek and tests

Extension functions

private fun String.last(): Char {return this[this.length - 1];

}

fun Activity.toast(msg: String) = Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()

inline fun <T> Iterable<T>.none(predicate: (T) -> Boolean): Boolean {for (element in this) if (predicate(element)) return falsereturn true

}

listOf(1, 2, 3).none { number -> number < 0 } // true

Page 19: Kotlin, Spek and tests

Iterations

for ((key, value) in map) for ((index, value) in list. withIndex())

Ranges

for (i in 1..10)for (i in 4 downTo 1)for (i in 1.0..2.0 step 0.3)val fontSizes = (12..22) map { it.toFloat() }

Infix functions

mapOf(1 to "one", 2 to "two")

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

public infix fun Int.downTo(to: Int): IntProgression { return IntProgression.fromClosedRange(this, to, -1)}

Page 20: Kotlin, Spek and tests

Null safety

if (user != null) {if (address != null) {

// ...

user?.address?.street

val id: String? = "123"id.length // won't compileid?.length // ok

if (id != null)id.length // ok again

id?.length ?: -1 // ok and more idiomatic

fun acceptId(id: String) // signature indicates a non-null valueacceptId(id) // won't compile until ensured id isn't null

Page 21: Kotlin, Spek and tests

Smart casts

if (x is Int) { val y = x * 5 // compiles!

}

when (outcome) { // some object...is SomeData -> onResult(outcome) // onResult(SomeData), no castingis Exception -> onError(outcome) // again no castingelse -> Log.d("?", "weird")

}

Data classes

data class User(val id: Int, val name: String )// that's it! // 1. immutable. // 2. equals, hashCode, toString out of the box.// 3. deep copies: user.copy(name = "Another name")// 4. named parameters!

Page 22: Kotlin, Spek and tests

Operator overloading

class Money(val amount: Int) {operator fun plus(money: Money): Money =

Money(this.amount + money.amount)}

Money(1) + Money(2) // builds, equals Money(3)

Also starring● reified generics

● language-level support for delegation pattern

● easy immutability

● dynamic types

● ...

Page 23: Kotlin, Spek and tests

DSL in Kotlin. Example #1: dealing with Android APIs

// extension functioninline fun SharedPreferences.edit(

func: SharedPreferences.Editor.() -> Unit) {val editor = edit()editor.func()editor.apply()

}

// and then in code...preferences.edit{

putString("some_key", "value")// etc.

}

⬆ Never again forget calling apply().

Page 24: Kotlin, Spek and tests

DSL in Kotlin. Example #2: dealing with Android APIs

// extension functioninline fun SQLiteDatabase.inTransaction(

func: SQLiteDatabase.() -> Unit) {beginTransaction()try {

func() // executing the function passed as a parameter setTransactionSuccessful()

} finally { endTransaction()

}}

// and then in code...db.inTransaction {

delete("users", "id = ?", arrayOf(19))// etc. never missing setTransactionSuccessful!

}

Page 25: Kotlin, Spek and tests

DSL in Kotlin. Type-safe Groovy-style builders. Example #3: Anko

verticalLayout {padding = dip(30)editText {

hint = "Name"textSize = 24f

}editText {

hint = "Password"textSize = 24f

}button("Login") {

textSize = 26f}

}https://github.com/Kotlin/anko

Page 26: Kotlin, Spek and tests

DSL in Kotlin. Example #4: anything you want

expect { Romanizer().convert(it) }.toTransform(

10 to "X",61 to "CLXI",1999 to "MCMCDIX")

// not that hard to set upfun Spek.expect(func: (Int) -> String) = this to func

private fun Pair<Spek, Function1<Int, String>>.toTransform(vararg pairs: Pair<Int, String>) {val function = this.secondval map = mapOf(*pairs)this.first.givenData(map.keys) {

on("converted to Roman numerals") {val actual = function(it)it("should be equal to ${map[it]}") {

shouldEqual(map[it], actual)}}}}

Page 27: Kotlin, Spek and tests

PERSPECTIVES FOR USING KOTLIN

➕ Better, more modern and expressive

➕ Full access to Java goodness

➖ Your colleagues

➖ Your boss

➖ Your boss’s customer

Page 28: Kotlin, Spek and tests

WHAT ARE UNIT TESTS FOR?

▪ Executable documentation

▪ Aiding good design

(single responsibility; TDD; edge cases)

▪ Tighter feedback loop

▪ Bug detection

Page 29: Kotlin, Spek and tests

COMMON PROBLEMS WITH UNIT TESTS

▪ What tests?“We would not trust a doctor who didn’t wash his or her hands. (...)No-one should trust software developed without unit tests.”

Martin Fowler

Page 30: Kotlin, Spek and tests

COMMON PROBLEMS WITH UNIT TESTS

▪ What tests? ▪ They're dead

Page 31: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

public void testTaskPresenter() {task.setRecipient(OWN_JID);TaskScreen screen = mock(TaskScreen.class);TaskPresenter presenter = new TaskPresenter(screen, OWN_JID);presenter.setTask(task);presenter.setRoom(Room.getEmpty());presenter.initViews();verify(screen).setResponseEnabled(true);

}

What does this test check?

Page 32: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

public void testInitViews() {task.setRecipient(OWN_JID);TaskScreen screen = mock(TaskScreen.class);TaskPresenter presenter = new TaskPresenter(screen, OWN_JID);presenter.setTask(task);presenter.setRoom(Room.getEmpty());presenter.initViews();verify(screen).setResponseEnabled(true);

}

What does this test check?

Page 33: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

public void testTaskWithNoResponse_iAmReceiver_responseButtonShown() {task.setRecipient(OWN_JID);TaskScreen screen = mock(TaskScreen.class);TaskPresenter presenter = new TaskPresenter(screen, OWN_JID);presenter.setTask(task);presenter.setRoom(Room.getEmpty());presenter.initViews();verify(screen).setResponseEnabled(true);

}

What does this test check?

Page 34: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

testTaskWithNoResponse_iAmReceiver_responseButtonShown()http://osherove.com/

Page 35: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

public void testTaskWithNoResponse_iAmReceiver_responseButtonShown() {task.setRecipient(OWN_JID);TaskScreen screen = mock(TaskScreen.class);TaskPresenter presenter = new TaskPresenter(screen, OWN_JID);presenter.setTask(task);presenter.setRoom(Room.getEmpty());presenter.initViews();verify(screen).setResponseEnabled(true);

}

What does this test check?

Page 36: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

public void testTaskWithNoResponse_iAmReceiver_responseButtonShown() {// ARRANGEtask.setRecipient(OWN_JID);TaskScreen screen = mock(TaskScreen.class);TaskPresenter presenter = new TaskPresenter(screen, OWN_JID);presenter.setTask(task);presenter.setRoom(Room.getEmpty());

// ACTpresenter.initViews();

// ASSERTverify(screen).setResponseEnabled(true);

}

What does this test check?

Page 37: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

given("a task with no response and assigned to myself") {val screen = mock(TaskScreen::class)val presenter = TaskPresenter(screen)presenter.task = Task().apply { recipient = OWN_JID }presenter.room = Room.empty()

on("presenter initializes views") {presenter.initViews()

it("should enable the response button") {verify(screen).setResponseEnabled(true)

}}

}

What does this test check?

Page 38: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

given("a task with no response and assigned to myself") {val screen = mock(TaskScreen::class)val presenter = TaskPresenter(screen)presenter.task = Task().apply { recipient = OWN_JID }presenter.room = Room.empty()

on("presenter initializes views") {presenter.initViews()

it("should enable the response button") {verify(screen).setResponseEnabled(true)

}}

}

What does this test check?

Page 39: Kotlin, Spek and tests

KEEPING YOUR TESTS TIDY

What does this test check?

Page 40: Kotlin, Spek and tests

ARRANGE(unit of work)

ACT(state under test)

ASSERT(expected behaviour)

GIVEN...initial context

WHEN...event occurs

THEN...ensure some outcomes

unit tests BDD

Page 41: Kotlin, Spek and tests

Testing on bulk dataval data = listOf( "0" to "0th", "1" to "1st", "2" to "2nd" ...)givenData(data) { val (input, expected) = it on("calling ordinalize on string", { val actual = input.ordinalize() it("should become ${it.component2()}", { shouldEqual(expected, actual) }) })}

https://github.com/MehdiK/Humanizer.jvm

Page 42: Kotlin, Spek and tests

Pending and skip

on("API does not respond") {it("should retry 3 times") {

pending("not implemented yet")}

it ("should display an error") {skip("obsolete now")// …

}}

Page 43: Kotlin, Spek and tests

...combine with other perks of Kotlin

given("a pub") { val pub = Pub()

on("trying to buy beer") { val customer = Guy("Robert")

val underage = customer.copy(age = 14) it("refuses to serve minors like $underage") { shouldBeFalse(pub.sellsBeer(underage) }

val adult = customer.copy(age = 18) it("sells to adults like $adult") { shouldBeTrue(pub.sellsBeer(adult)) } }

Page 44: Kotlin, Spek and tests

...combine with other perks of Kotlin

given("a pub") { val pub = Pub()

on("trying to buy beer") { val customer = Guy("Robert")

val underage = customer.copy(age = 14) it("refuses to serve minors like $underage") { shouldBeFalse(pub.sellsBeer(underage) }

val adult = customer.copy(age = 18) it("sells to adults like $adult") { shouldBeTrue(pub.sellsBeer(adult)) } }

Page 45: Kotlin, Spek and tests

IT’S DIFFICULT TO SET UP, RIGHT?

Page 46: Kotlin, Spek and tests

YOU BE THE JUDGE

(* )

Page 47: Kotlin, Spek and tests

ONCE YOU’VE GOT THE PLUGIN

Page 48: Kotlin, Spek and tests

GRADLE SCRIPTS UPDATED AUTOMATICALLY

Page 49: Kotlin, Spek and tests

AND SPEK:

All set.

Page 50: Kotlin, Spek and tests

MORE RESOURCES▪ https://youtu.be/A2LukgT2mKc - Android Development with Kotlin (Jake Wharton)▪ https://goo.gl/AQB7af - more detailed summary (same author)▪ http://blog.jetbrains.com/kotlin/2016/01/kotlin-digest-2015/#more-3400 - selection of

articles▪ http://www.javacodegeeks.com/2016/01/mimicking-kotlin-builders-java-python.html

on typesafe data builders (DSL)▪ http://antonioleiva.com/kotlin-awesome-tricks-for-android/ ▪ https://medium.com/@CodingDoug/kotlin-android-a-brass-tacks-experiment-part-4-

4b7b501fa457#.4xh886p73 - from a Google employee, a series of 4 parts (as of now)

▪ http://beust.com/weblog/2015/10/30/exploring-the-kotlin-standard-library/▪ https://github.com/nhaarman/mockito-kotlin - helper functions to work with Mockito

Page 51: Kotlin, Spek and tests

Konrad Morawski

Software Engineer Specialist

[email protected]

Thank you!