Upload
intive
View
975
Download
3
Embed Size (px)
Citation preview
Kotlin, Spek and testsHow to use Kotlin for your test suite,and why you might want to
Konrad Morawski
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
Thank you!