Share
Embed
Testing with Kotlin (using Spek and Mockito)
These are the slides that support my talk given at DevFest Lisbon! I talked about how you could test Kotlin code using both Spek and Mockito.
Fábio Carballo
September 24, 2016
Transcript
51 slides
-
“More than the act of testing, the act of designing
tests is one of the best bug preventers known.” – Boris Beizer Testing With Kotlin Why do we need tests? -
“Software testing proves the existing of bugs, not their absence.”
– Edsger W. Dijkstra Testing With Kotlin Why do we need tests? -
“Tests are no more and no less than executable documentation.
When writing new code, it makes sense that it should be documented by the person closest to it: the author. That’s my rationale for developer testing. – Noah Sussman Testing With Kotlin Why do we need tests? -
• J-Unit tests have a flat structure • RSpec •
TDD test framework for Kotlin • www.github.com/jetbrains/Spek https://github.com/JetBrains/spekhttps://github.com/JetBrains/spek Testing With Kotlin Why Spek? -
• Two-player game used to reach a decision. • One
player guesses the number will be even, the other guesses it will be odd. • The two players pick a number and the winner will be the one that guessed the parity of the sum of those numbers Testing With Kotlin Spek Demo - “Par ou Ímpar” Game -
Testing With Kotlin Spek Demo - Odd/Even Game enum class
Player { EVEN, ODD } -
Testing With Kotlin Spek Demo - Odd/Even Game open class
OddEvenGame() { private var evenPlayerNumber: Int? = null private var oddPlayerNumber: Int? = null fun recordChoice(player: Player, number: Int) { when (player) { Player.EVEN -> evenPlayerNumber = number Player.ODD -> oddPlayerNumber = number } } } enum class Player { EVEN, ODD } -
Testing With Kotlin Spek Demo - Odd/Even Game open class
OddEvenGame() { private var evenPlayerNumber: Int? = null private var oddPlayerNumber: Int? = null fun recordChoice(player: Player, number: Int) { when (player) { Player.EVEN -> evenPlayerNumber = number Player.ODD -> oddPlayerNumber = number } } } enum class Result { EVEN_PLAYER, ODD_PLAYER, NO_RESULT } enum class Player { EVEN, ODD } -
Testing With Kotlin Spek Demo - Odd/Even Game open class
OddEvenGame() { private var evenPlayerNumber: Int? = null private var oddPlayerNumber: Int? = null fun recordChoice(player: Player, number: Int) { when (player) { Player.EVEN -> evenPlayerNumber = number Player.ODD -> oddPlayerNumber = number } } val winner: Result get() { return ifNotNull(evenPlayerNumber, oddPlayerNumber) { evenNumber, oddNumber -> val isEven = ((evenNumber + oddNumber) % 2 == 0) if (isEven) Result.EVEN_PLAYER else Result.ODD_PLAYER } ?: Result.NO_RESULT } } enum class Result { EVEN_PLAYER, ODD_PLAYER, NO_RESULT } enum class Player { EVEN, ODD } -
Testing With Kotlin open class OddEvenGame() { private var evenPlayerNumber:
Int? = null private var oddPlayerNumber: Int? = null fun recordChoice(player: Player, number: Int) { when (player) { Player.EVEN -> evenPlayerNumber = number Player.ODD -> oddPlayerNumber = number } } val winner: Result get() { return ifNotNull(evenPlayerNumber, oddPlayerNumber) { evenNumber, oddNumber -> val isEven = ((evenNumber + oddNumber) % 2 == 0) if (isEven) Result.EVEN_PLAYER else Result.ODD_PLAYER } ?: Result.NO_RESULT } fun clear() { evenPlayerNumber = null oddPlayerNumber = null } } enum class Result { EVEN_PLAYER, ODD_PLAYER, NO_RESULT } enum class Player { EVEN, ODD } Spek Demo - “Par ou Ímpar” Game -
• What would happen in JUnit? Testing With Kotlin class
DemoJunitTest { var oddEvenGame = OddEvenGame(); @Test fun “----------------------------------------------------”() { // Arrange oddEvenGame.recordChoice(Player.EVEN, 4) oddEvenGame.recordChoice(Player.ODD, 3) } @After fun teardown() { oddEvenGame.clear() } } Spek Demo - “Par ou Ímpar” Game -
• What would happen in JUnit? Testing With Kotlin class
DemoJunitTest { var oddEvenGame = OddEvenGame(); @Test fun “----------------------------------------------------”() { // Arrange oddEvenGame.recordChoice(Player.EVEN, 4) oddEvenGame.recordChoice(Player.ODD, 3) // Act val winner = oddEvenGame.winner } @After fun teardown() { oddEvenGame.clear() } } Spek Demo - “Par ou Ímpar” Game -
• What would happen in JUnit? Testing With Kotlin class
DemoJunitTest { var oddEvenGame = OddEvenGame(); @Test fun “----------------------------------------------------”() { // Arrange oddEvenGame.recordChoice(Player.EVEN, 4) oddEvenGame.recordChoice(Player.ODD, 3) // Act val winner = oddEvenGame.winner // Assert Assert.assertEquals(Result.ODD_PLAYER, winner) } @After fun teardown() { oddEvenGame.clear() } } Spek Demo - “Par ou Ímpar” Game -
• What would happen in JUnit? Testing With Kotlin class
DemoJunitTest { var oddEvenGame = OddEvenGame(); @Test fun if_player_odd_chooses_odd_number_and_player_even_chooses_even_number_then_the_winner_is_player_odd() { // Arrange oddEvenGame.recordChoice(Player.EVEN, 4) oddEvenGame.recordChoice(Player.ODD, 3) // Act val winner = oddEvenGame.winner // Assert Assert.assertEquals(Result.ODD_PLAYER, winner) } @After fun teardown() { oddEvenGame.clear() } } Spek Demo - “Par ou Ímpar” Game -
• What would happen in JUnit? Testing With Kotlin class
DemoJunitTest { var oddEvenGame = OddEvenGame(); @Test fun if_player_odd_chooses_odd_number_and_player_even_chooses_even_number_then_the_winner_is_player_odd() { // Arrange oddEvenGame.recordChoice(Player.EVEN, 4) oddEvenGame.recordChoice(Player.ODD, 3) // Act val winner = oddEvenGame.winner // Assert Assert.assertEquals(Result.ODD_PLAYER, winner) } @After fun teardown() { oddEvenGame.clear() } } Spek Demo - “Par ou Ímpar” Game Totally readable! -
• What would happen in JUnit? Testing With Kotlin Spek
Demo - “Par ou Ímpar” Game -
@RunWith(JUnitPlatform::class) class GameSpek : Spek({ describe("playing odd-even") { val oddEvenGame
= OddEvenGame() afterEach { oddEvenGame.clear() } context("when odd player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.ODD, 3) } context("when even player picks a even number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 4) } it("should declare the odd player as the winner") { assertEquals(Result.ODD_PLAYER, oddEvenGame.winner) } } context("when even player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 3) } it("should declare the even player as the winner") { assertEquals(Result.EVEN_PLAYER, oddEvenGame.winner) } } (...) } Testing With Kotlin Spek Demo - “Par ou Ímpar” Game -
@RunWith(JUnitPlatform::class) class GameSpek : Spek({ describe("playing odd-even") { val oddEvenGame
= OddEvenGame() afterEach { oddEvenGame.clear() } context("odd player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.ODD, 3) } context("even player picks a even number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 4) } it("should declare the odd player as the winner") { assertEquals(Result.ODD_PLAYER, oddEvenGame.winner) } } } } context("when even player picks an odd number") { assertEquals(Result.EVEN_PLAYER, oddEvenGame.winner) } } (...) Testing With Kotlin Spek Demo - “Par ou Ímpar” Game -
@RunWith(JUnitPlatform::class) class GameSpek : Spek({ describe("playing odd-even") { val oddEvenGame
= OddEvenGame() afterEach { oddEvenGame.clear() } context("odd player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.ODD, 3) } context("even player picks a even number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 4) } it("should declare the odd player as the winner") { assertEquals(Result.ODD_PLAYER, oddEvenGame.winner) } } context("even player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 3) } it("should declare the even player as the winner") { assertEquals(Result.EVEN_PLAYER, oddEvenGame.winner) } } } (...) } Testing With Kotlin Spek Demo - “Par ou Ímpar” Game -
@RunWith(JUnitPlatform::class) class GameSpek : Spek({ describe("playing odd-even") { val oddEvenGame
= OddEvenGame() afterEach { oddEvenGame.clear() } context("odd player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.ODD, 3) } context("even player picks a even number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 4) } it("should declare the odd player as the winner") { assertEquals(Result.ODD_PLAYER, oddEvenGame.winner) } } context("even player picks an odd number") { beforeEach { oddEvenGame.recordChoice(Player.EVEN, 3) } it("should declare the even player as the winner") { assertEquals(Result.EVEN_PLAYER, oddEvenGame.winner) } } } (...) } Testing With Kotlin Spek Demo - “Par ou Ímpar” Game -
Testing With Kotlin Spek - Subject and Shared Examples The
Cat! • When it moves, it gets hungry. • When it eats, it stops being hungry -
Testing With Kotlin Spek - Subject and Shared Examples open
class Cat { var isHungry = false open fun eat() { isHungry = false } open fun move() { isHungry = true } } -
Testing With Kotlin Spek - Subject and Shared Examples The
Fat Cat! • When it moves, it gets hungry. • When eating for the first time, remains hungry. • When eating for the second time, stops being hungry -
Testing With Kotlin Spek - Subject and Shared Examples class
FatCat : Cat() { private var ateOnce = false override fun eat() { if (!ateOnce) { ateOnce = true } else { isHungry = false } } override fun move() { ateOnce = false super.move() } } -
Testing With Kotlin Spek - Subject and Shared Examples @RunWith(JUnitPlatform::class)
class MovingCatSpek : SubjectSpek<Cat>({ subject { Cat() } given("a Cat") { describe("#move") { context("when it is hungry") { beforeEach { subject.isHungry = true } it("should remain hungry") { subject.move() assertTrue(subject.isHungry) } } context("when it is not hungry") { beforeEach { subject.isHungry = false } it("should become hungry") { subject.move() assertTrue(subject.isHungry) } } } } }) -
Testing With Kotlin Spek - Subject and Shared Examples @RunWith(JUnitPlatform::class)
class MovingCatSpek : SubjectSpek<Cat>({ subject { Cat() } given("a Cat") { describe("#move") { context("when it is hungry") { beforeEach { subject.isHungry = true } it("should remain hungry") { subject.move() assertTrue(subject.isHungry) } } context("when it is not hungry") { beforeEach { subject.isHungry = false } it("should become hungry") { subject.move() assertTrue(subject.isHungry) } } } } }) Subject under test! -
Testing With Kotlin Spek - Subject and Shared Examples @RunWith(JUnitPlatform::class)
class FatCatSpek : SubjectSpek<FatCat>({ subject { FatCat() } given("a Fat Cat") { context("when is hungry") { beforeEach { subject.isHungry = true } context("eats for the first time") { beforeEach { subject.eat() } it("remains hungry") { assertTrue(subject.isHungry) } context("eats for the second time") { beforeEach { subject.eat() } it("becomes not hungry") { assertFalse(subject.isHungry) } } } } } }) -
Testing With Kotlin Spek - Subject and Shared Examples @RunWith(JUnitPlatform::class)
class FatCatSpek : SubjectSpek<FatCat>({ subject { FatCat() } itBehavesLike(MovingCatSpek::class) given("a Fat Cat") { context("when is hungry") { beforeEach { subject.isHungry = true } context("eats for the first time") { beforeEach { subject.eat() } it("remains hungry") { assertTrue(subject.isHungry) } context("eats for the second time") { beforeEach { subject.eat() } it("becomes not hungry") { assertFalse(subject.isHungry) } } } } } }) Sharing behaviour!! -
Testing With Kotlin Spek - Subject and Shared Examples @RunWith(JUnitPlatform::class)
class FatCatSpek : SubjectSpek<FatCat>({ subject { FatCat() } itBehavesLike(MovingCatSpek::class) given("a Fat Cat") { context("when is hungry") { beforeEach { subject.isHungry = true } context("eats for the first time") { beforeEach { subject.eat() } it("remains hungry") { assertTrue(subject.isHungry) } context("eats for the second time") { beforeEach { subject.eat() } it("becomes not hungry") { assertFalse(subject.isHungry) } } } } } }) -
• https://github.com/nhaarman/mockito-kotlin Testing With Kotlin Mockito interface WebService { fun
isOffline(): Boolean fun login(userEmail: String, password: String) fun logout() } data class UserInteractor( private val webService: WebService, private val email: String, private val password: String) { fun login() { if (!webService.isOffline()) { webService.login(email, password) } } fun logout() { if (!webService.isOffline()) { webService.logout() } } } -
• Creating a mock Testing With Kotlin Mockito val mockWebService:
WebService = mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) -
• Verify interactions Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.logout() -
• Verify interactions Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.logout() verify(mockWebService).logout() -
• Verify interactions Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.logout() verify(mockWebService, times(1)).logout() times(2) times(...) never() -
• Verify interactions Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.login() verify(mockWebService).login(USER_EMAIL, PASSWORD) -
• Verify interactions Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.login() verify(mockWebService).login(USER_EMAIL, PASSWORD) -
• Matchers Testing With Kotlin Mockito val mockWebService: WebService =
mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.login() verify(mockWebService).login(any(), any()) -
• In-Order • Testing With Kotlin Mockito val mockWebService: WebService
= mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.login() userInteractor.logout() -
• In-Order Testing With Kotlin Mockito val mockWebService: WebService =
mock() val userInteractor = UserInteractor(mockWebService, USER_EMAIL, PASSWORD) userInteractor.login() userInteractor.logout() inOrder(mockWebService).apply { verify(mockWebService).login(USER_EMAIL, PASSWORD) verify(mockWebService).logout() } -
• Stubbing methods Testing With Kotlin Mockito whenever(mockWebService.isOffline()).thenReturn(false, true, false)
userInteractor.login() verify(mockWebService, never()).login(any(), any()) -
• Unable to mock: ◦ Private methods ◦ Hash Code
and Equals ◦ Static methods ◦ Final classes ◦ Final methods https://github.com/dpreussler/kotlin-testrunner https://github.com/dpreussler/kotlin-testrunner Testing With Kotlin Mockito -
• Unable to mock: ◦ Private methods ◦ Hash Code
and Equals ◦ Static methods ◦ Final classes ◦ Final methods https://github.com/dpreussler/kotlin-testrunner https://github.com/dpreussler/kotlin-testrunner Testing With Kotlin Mockito https://github.com/dpreussler/kotlin-testrunner -
Testing With Kotlin Simple Android Example What about a simple
Android example? https://github.com/fabiocarballo/gdg-lisbon-odd-even -
Testing With Kotlin Simple Android Example • “Par ou Ímpar”
game! ◦ View - GameActivity ◦ Presenter - GamePresenter ◦ Model - OddEvenGame