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

Upload: intive

Post on 13-Apr-2017

975 views

Category:

Software


3 download

TRANSCRIPT

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!