Kotlin, Spek and tests

Post on 13-Apr-2017

975 views 3 download

Transcript of Kotlin, Spek and tests

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

Konrad Morawski

http://kotlinlang.org/

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#?

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

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!")

}

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;

}

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;

}

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;

}

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

}

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

}

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

}

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

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.

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

}

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 }

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 }

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

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

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

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

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

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!

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

● ...

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

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!

}

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

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

PERSPECTIVES FOR USING KOTLIN

➕ Better, more modern and expressive

➕ Full access to Java goodness

➖ Your colleagues

➖ Your boss

➖ Your boss’s customer

WHAT ARE UNIT TESTS FOR?

▪ Executable documentation

▪ Aiding good design

(single responsibility; TDD; edge cases)

▪ Tighter feedback loop

▪ Bug detection

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

COMMON PROBLEMS WITH UNIT TESTS

▪ What tests? ▪ They're dead

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?

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?

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?

KEEPING YOUR TESTS TIDY

testTaskWithNoResponse_iAmReceiver_responseButtonShown()http://osherove.com/

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?

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?

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?

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?

KEEPING YOUR TESTS TIDY

What does this test check?

ARRANGE(unit of work)

ACT(state under test)

ASSERT(expected behaviour)

GIVEN...initial context

WHEN...event occurs

THEN...ensure some outcomes

unit tests BDD

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

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")// …

}}

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

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

IT’S DIFFICULT TO SET UP, RIGHT?

YOU BE THE JUDGE

(* )

ONCE YOU’VE GOT THE PLUGIN

GRADLE SCRIPTS UPDATED AUTOMATICALLY

AND SPEK:

All set.

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

Konrad Morawski

Software Engineer Specialist

konrad.morawski@blstream.pl

Thank you!