Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SpekでUnitTestを書こう / unit-test-with-spek

moriiimo
February 08, 2019

SpekでUnitTestを書こう / unit-test-with-spek

moriiimo

February 08, 2019
Tweet

More Decks by moriiimo

Other Decks in Technology

Transcript

  1. 4QFLͰ6OJU5FTUΛॻ͜͏
    "ZBLP.PSJNPUP!NPSJJJNP

    View Slide

  2. ʘ࠷ۙϦχϡʔΞϧ͠·ͨ͠ʗ
    "ZBLP.PSJNPUP
    %..DPNMJGFࣄۀ෦
    ࣗݾ঺հ

    View Slide

  3. View Slide

  4. 4QFLΛ஌Γɺ
    ςετΛॻ͖͍ͨͱࢥͬͯ΋Β͏͜ͱ
    ΰʔϧ

    View Slide

  5. ຊ೔͓࿩͢Δ͜ͱ
    4QFLͱ͸ʁ
    4QFLͰ6OJU5FTUΛॻ͍ͯΈΑ͏
    Ԡ༻ฤ4QFLͰ-JWF%BUB΍3PPNͷ
    6OJU5FTUΛॻ͍ͯΈΑ͏

    View Slide

  6. ຊ೔͓࿩͢Δ͜ͱ
    4QFLͱ͸ʁ
    4QFLͰ6OJU5FTUΛॻ͍ͯΈΑ͏
    Ԡ༻ฤ4QFLͰ-JWF%BUB΍3PPNͷ
    6OJU5FTUΛॻ͍ͯΈΑ͏

    View Slide

  7. ຊ೔͓࿩͢Δ͜ͱ
    4QFLͱ͸ʁ
    4QFLͰ6OJU5FTUΛॻ͍ͯΈΑ͏
    Ԡ༻ฤ4QFLͰ-JWF%BUB΍3PPNͷ
    6OJU5FTUΛॻ͍ͯΈΑ͏

    View Slide

  8. ຊ೔͓࿩͢Δ͜ͱ
    4QFLͱ͸ʁ
    4QFLͰ6OJU5FTUΛॻ͍ͯΈΑ͏
    Ԡ༻ฤ4QFLͰ-JWF%BUB΍3PPNͷ
    6OJU5FTUΛॻ͍ͯΈΑ͏

    View Slide

  9. 4QFLͱ͸ʁ

    View Slide

  10. Copyright (c) 2018, Spek Core Team and Contributors

    View Slide

  11. l5IJTTUZMFJTJOTQJSFECZUFTUGSBNFXPSLTMJLF
    +BTNJOFBOE34QFDz
    IUUQTTQFLGSBNFXPSLPSHTQFDJpDBUJPO

    View Slide

  12. l5IJTTUZMFJTJOTQJSFECZUFTUGSBNFXPSLTMJLF
    +BTNJOFBOE34QFDz
    IUUQTTQFLGSBNFXPSLPSHTQFDJpDBUJPO
    +BWB4DSJQU

    View Slide

  13. l5IJTTUZMFJTJOTQJSFECZUFTUGSBNFXPSLTMJLF
    +BTNJOFBOE34QFDz
    IUUQTTQFLGSBNFXPSLPSHTQFDJpDBUJPO
    3VCZ

    View Slide

  14. l5IJTTUZMFJTJOTQJSFECZUFTUGSBNFXPSLTMJLF
    +BTNJOFBOE34QFDz
    IUUQTTQFLGSBNFXPSLPSHTQFDJpDBUJPO

    View Slide

  15. 5PEP$MBTT
    class Todo(val id: Int) {
    constructor(id: Int, label: String, complete: Boolean,
    deadline: Long) : this(id) {
    this.label = label
    this.complete = complete
    this.deadline = deadline
    }
    var label: String = ""
    private set
    var complete: Boolean = false
    private set
    var deadline: Long = System.currentTimeMillis()
    private set
    }

    View Slide

  16. 5PEP$MBTT
    fun checkComplete() {
    complete = true
    }
    fun unCheckComplete() {
    complete = false
    }
    ऴྃঢ়ଶͷ
    มߋ͕Ͱ͖ͨΓʜ

    View Slide

  17. 5PEP$MBTT
    fun addDayToDeadline(numOfDays: Int) {
    changeDeadline(numOfDays)
    }
    fun reduceDayToDeadline(numOfDays: Int) {
    changeDeadline(-numOfDays)
    }
    private fun changeDeadline(numOfDays: Int) {
    deadline = Calendar.getInstance(TimeZone.getDefault(),
    Locale.JAPAN).apply {
    timeInMillis = deadline
    add(Calendar.DATE, numOfDays)
    }.timeInMillis
    }
    ظݶͷมߋ͕
    Ͱ͖ͨΓ͠·͢

    View Slide

  18. +6OJU5PEP5FTU
    class TodoTest {
    private val now = System.currentTimeMillis()
    private lateinit var todo: Todo
    @Before
    fun setup() {
    todo = Todo(id = 1, label = "Write Unit Test with Spek", complete = false, deadline = now
    )
    }
    @Test
    fun changeCompleteStatus_shouldBeTrue() {
    todo.checkComplete()
    assertTrue(todo.complete)
    }
    @Test
    fun changeCompleteStatus_shouldBeFalse() {
    todo.unCheckComplete()
    assertFalse(todo.complete)
    }
    @Test
    fun addDaysToDeadline_shouldBeThreeDaysLater() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, 3)
    }
    todo.addDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    @Test
    fun reduceDaysToDeadline_shouldBeThreeDaysAgo() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, -3)
    }
    todo.reduceDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    }

    View Slide

  19. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeFinishStatus_shouldBeTrue() {
    todo.checkComplete()
    assert(todo.complete)
    }

    View Slide

  20. ݟͮΒ͍ϙΠϯτ
    ϝιου໊΍ίϝϯτͰ
    ૝ఆΛද͢ඞཁ͕͋Δ
    @Test
    fun changeFinishStatus_shouldBeTrue() {
    todo.checkComplete()
    assert(todo.complete)
    }

    View Slide

  21. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeFinishStatus_shouldBeTrue() {
    todo.checkComplete()
    assert(todo.complete)
    }

    View Slide

  22. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeCompleteStatus_shouldBeTrue() {
    todo.checkComplete()
    assertTrue(todo.complete)
    }
    @Test
    fun changeCompleteStatus_shouldBeFalse() {
    todo.unCheckComplete()
    assertFalse(todo.complete)
    }

    View Slide

  23. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeCompleteStatus_shouldBeTrue() {
    todo.checkComplete()
    assertTrue(todo.complete)
    }
    @Test
    fun changeCompleteStatus_shouldBeFalse() {
    todo.unCheckComplete()
    assertFalse(todo.complete)
    }
    @Test
    fun addDaysToDeadline_shouldBeThreeDaysLater() {
    val expect = Calendar.getInstance(TimeZone.getDefault(),
    Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, 3)
    }
    todo.addDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }

    View Slide

  24. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeCompleteStatus_shouldBeTrue() {
    todo.checkComplete()
    assertTrue(todo.complete)
    }
    @Test
    fun changeCompleteStatus_shouldBeFalse() {
    todo.unCheckComplete()
    assertFalse(todo.complete)
    }
    @Test
    fun addDaysToDeadline_shouldBeThreeDaysLater() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, 3)
    }
    todo.addDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    @Test
    fun reduceDaysToDeadline_shouldBeThreeDaysAgo() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, -3)
    }
    todo.reduceDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    ΧςΰϥΠζɺ
    ֊૚ԽͰ͖ͳ͍

    View Slide

  25. ݟͮΒ͍ϙΠϯτ
    @Test
    fun changeFinishStatus_shouldBeTrue() {
    todo.markFinished()
    assert(todo.finished)
    }
    @Test
    fun changeFinishStatus_shouldBeFalse() {
    todo.markUnFinished()
    assert(!todo.finished)
    }
    @Test
    fun addDaysToDeadline_shouldBeThreeDaysLater() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, 3)
    }
    todo.addDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    @Test
    fun reduceDaysToDeadline_shouldBeThreeDaysAgo() {
    val expect = Calendar.getInstance(TimeZone.getDefault(), Locale.JAPAN).apply {
    timeInMillis = now
    add(Calendar.DATE, -3)
    }
    todo.reduceDayToDeadline(3)
    assertEquals(expect.timeInMillis, todo.deadline)
    }
    ࣮ߦॱং͕อূ
    ͞Εͳ͍

    View Slide

  26. 4QFL5PEP5FTU
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    Given("the todo is not complete") {
    todo = Todo(
    id = 1,
    label = "",
    deadline = now,
    complete = false
    )
    }
    When("complete the todo") {
    todo.checkComplete()
    }
    Then("the todo is complete") {
    assertTrue(todo.complete)
    }
    When("return the todo to incomplete") {
    todo.unCheckComplete()
    }
    Then("the todo is not complete") {
    assertTrue(!todo.complete)
    }
    }
    }
    })

    View Slide

  27. ݟ΍͍͢ϙΠϯτ
    Scenario("complete status") {
    Given("the todo is not complete") {
    todo = Todo(
    id = 1,
    label = "",
    deadline = now,
    complete = false
    )
    }
    When("complete the todo") {
    todo.checkComplete()
    }
    Then("the todo is complete") {
    assertTrue(todo.complete)
    }
    }

    View Slide

  28. ݟ΍͍͢ϙΠϯτ
    Scenario("complete status") {
    Given("the todo is not complete") {
    todo = Todo(
    id = 1,
    label = "",
    deadline = now,
    complete = false
    )
    }
    When("complete the todo") {
    todo.checkComplete()
    }
    Then("the todo is complete") {
    assertTrue(todo.complete)
    }
    }
    ૝ఆ͕
    ॻ͖΍͍͢

    View Slide

  29. ิ଍
    Scenario("changing todo finish status") {
    Given("todo is not finish") {
    todo = Todo(
    id = 1,
    label = "Write Unit Test with Spek",
    finished = false,
    deadline = now
    )
    }
    When("mark finish") {
    todo.markFinished()
    }
    Then("it should be true") {
    assertTrue(todo.finished)
    }
    }
    ૝ఆ͕
    ॻ͖΍͍͢
    ͪͳΈʹʜ͜ͷΑ͏ͳه๏͸(IFSLJOͱ͍͏
    ςετهड़ݴޠϑΥʔϚοτ͕ݩʹͳ͍ͬͯ·͢

    View Slide

  30. ิ଍
    Scenario("changing todo finish status") {
    Given("todo is not finish") {
    todo = Todo(
    id = 1,
    label = "Write Unit Test with Spek",
    finished = false,
    deadline = now
    )
    }
    When("mark finish") {
    todo.markFinished()
    }
    Then("it should be true") {
    assertTrue(todo.finished)
    }
    }
    ૝ఆ͕
    ॻ͖΍͍͢
    ͪͳΈʹʜ͜ͷΑ͏ͳه๏͸(IFSLJOͱ͍͏
    ςετهड़ݴޠϑΥʔϚοτ͕ݩʹͳ͍ͬͯ·͢
    ػೳ50%0
    γφϦΦऴྃεςʔλε͕ਖ਼͘͠มԽ͢Δ
    લఏ50%0ͷऴྃεςʔλε͕ະ׬ྃ
    ΋͠50%0ͷऴྃεςʔλεΛ׬ྃʹͨ͠Β
    ͳΒ͹50%0ͷऴྃεςʔλε͕׬ྃʹͳΔ
    ೔ຊޠͰ΋͔͚ͯɺ
    ॻ͘ͱ͜Μͳײ͡ʹͳΔ

    View Slide

  31. ݟ΍͍͢ϙΠϯτ
    class TodoFeature : Spek({
    Feature("Todo") {
    Scenario(“complete status") {
    . . . ऴྃεςʔλεͷςετ
    }
    Scenario("deadline") {
    . . . క੾ͷςετ
    }
    Scenario(“label") {
    . . . ໊લͷςετ
    }
    }
    })

    View Slide

  32. ݟ΍͍͢ϙΠϯτ
    class TodoFeature : Spek({
    Feature("Todo") {
    Scenario(“complete status") {
    . . . ऴྃεςʔλεͷςετ
    }
    Scenario("deadline") {
    . . . క੾ͷςετ
    }
    Scenario(“label") {
    . . . ໊લͷςετ
    }
    }
    })
    Θ͔Γ΍͘͢Χςΰ
    ϥΠζͰ͖Δ

    View Slide

  33. class TodoFeature : Spek({
    Feature("Todo") {
    Scenario("changing finish status") {
    . . . ऴྃεςʔλεͷςετ
    }
    Scenario("changing deadline") {
    . . . క੾มߋͷςετ
    }
    Scenario("changing label") {
    . . . ໊લมߋͷςετ
    }
    }
    })
    ݟ΍͍͢ϙΠϯτ
    ࣮ߦॱং͕อূ͞ΕΔ

    View Slide

  34. 4QFLͰ6OJU5FTUΛ
    ॻ͍ͯΈΑ͏

    View Slide

  35. ηοτΞοϓ
    https://spekframework.org/setup-android/

    View Slide

  36. ηοτΞοϓ
    https://spekframework.org/setup-android/
    ๨Εͣʹ΍ͬͯ΄͍͠ͷ͕…

    View Slide

  37. ηοτΞοϓ
    https://spekframework.org/setup-android/
    ๨Εͣʹ΍ͬͯ΄͍͠ͷ͕…
    #1 SpekͷAndroidStudioϓϥάΠϯΛೖΕ·͠ΐ͏

    View Slide

  38. ηοτΞοϓ
    https://spekframework.org/setup-android/
    ๨Εͣʹ΍ͬͯ΄͍͠ͷ͕…
    #1 SpekͷAndroidStudioϓϥάΠϯΛೖΕ·͠ΐ͏
    #2 KotlinͷAndroidStudioϓϥάΠϯΛ1.3Ҏ্ʹΞοϓσʔτ͠·
    ͠ΐ͏

    View Slide

  39. ηοτΞοϓ
    https://spekframework.org/setup-android/
    ๨Εͣʹ΍ͬͯ΄͍͠ͷ͕…
    #1 SpekͷAndroidStudioϓϥάΠϯΛೖΕ·͠ΐ͏
    #2 KotlinͷAndroidStudioϓϥάΠϯΛ1.3Ҏ্ʹΞοϓσʔτ͠·
    ͠ΐ͏
    Yͩͱʜ✗

    View Slide

  40. جຊͷॻ͖ํ
    class TodoFeature

    View Slide

  41. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    })

    View Slide

  42. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    }
    })

    View Slide

  43. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    }
    })

    View Slide

  44. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    val todo: Todo by lazy {
    Todo(id = 1, label = "Write Unit Test with Spek",
    complete = false, deadline = now)
    }
    }
    })
    MB[Z΋"

    View Slide

  45. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    }
    }
    })

    View Slide

  46. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    Given(“the todo is not complete") {
    todo = Todo(id = 1, label = "Write Unit Test with Spek”,
    complete = false, deadline = now)
    }
    }
    }
    })

    View Slide

  47. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    Given(“the todo is not complete") {
    todo = Todo(id = 1, label = "Write Unit Test with Spek",
    complete = false, deadline = now)
    }
    When(“complete the todo") {
    todo.checkComplete()
    }
    }
    }
    })

    View Slide

  48. جຊͷॻ͖ํ
    class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    Given(“the todo is not complete") {
    todo = Todo(id = 1, label = "Write Unit Test with Spek",
    complete = false, deadline = now)
    }
    When(“complete the todo") {
    todo.checkComplete()
    }
    Then(“the todo is complete”) {
    assertTrue(todo.complete)
    }
    }
    }
    })

    View Slide

  49. ϧʔϧ
    class TodoFeature : Spek({
    Feature("xxx") {
    Scenario("xxx") {
    . . . xxxͷςετ
    }
    Scenario("yyy") {
    . . . yyyͷςετ
    }
    }
    Feature("zzz") {
    Scenario("zzz") {
    . . . zzzͷςετ
    }
    }
    }) ෳ਺ͷ'FBUVSFɺ'FBUVSF಺ͷ
    ෳ਺ͷγφϦΦʁ

    View Slide

  50. ෳ਺ͷ'FBUVSFɺ'FBUVSF಺ͷ
    ෳ਺ͷγφϦΦʁ
    ϧʔϧ
    class TodoFeature : Spek({
    Feature("xxx") {
    Scenario("xxx") {
    . . . xxxͷςετ
    }
    Scenario("yyy") {
    . . . yyyͷςετ
    }
    }
    Feature("zzz") {
    Scenario("zzz") {
    . . . zzzͷςετ
    }
    }
    })
    "

    View Slide

  51. ϧʔϧ
    class TodoFeature : Spek({
    Feature(“xxx") {
    Scenario("xxx") {
    Scenario(“yyy") {
    Given("xxx") {
    Given(“yyy”) {
    }
    }
    }
    }
    }
    })
    γφϦΦ΍(JWFO8IFO
    5IFOͷωετʁ

    View Slide

  52. γφϦΦ΍(JWFO8IFO
    5IFOͷωετʁ
    ϧʔϧ
    class TodoFeature : Spek({
    Feature(“xxx") {
    Scenario("xxx") {
    Scenario(“yyy") {
    Given("xxx") {
    Given(“yyy”) {
    }
    }
    }
    }
    }
    })
    #

    View Slide

  53. @Synonym(SynonymType.GROUP, prefix = "Feature: ")
    @Descriptions(Description(DescriptionLocation.VALUE_PARAMETER, 0))
    fun GroupBody.Feature(description: String,
    body: FeatureBody.() -> Unit) {
    group("Feature: $description",
    defaultCachingMode = CachingMode.GROUP) {
    body(FeatureBody(this))
    }
    }

    View Slide

  54. @Synonym(SynonymType.GROUP, prefix = "Feature: ")
    @Descriptions(Description(DescriptionLocation.VALUE_PARAMETER, 0))
    fun GroupBody.Feature(description: String,
    body: FeatureBody.() -> Unit) {
    group("Feature: $description",
    defaultCachingMode = CachingMode.GROUP) {
    body(FeatureBody(this))
    }
    }
    ͜Ε

    View Slide

  55. @Synonym(SynonymType.GROUP, prefix = "Feature: ")
    @Descriptions(Description(DescriptionLocation.VALUE_PARAMETER, 0))
    fun GroupBody.Feature(description: String,
    body: FeatureBody.() -> Unit) {
    group("Feature: $description",
    defaultCachingMode = CachingMode.GROUP) {
    body(FeatureBody(this))
    }
    }
    @SpekDsl
    class FeatureBody(val delegate: GroupBody): LifecycleAware by delegate {
    @Synonym(SynonymType.GROUP, prefix = "Scenario: ")
    @Descriptions(Description(DescriptionLocation.VALUE_PARAMETER, 0))
    fun Scenario(description: String, body: ScenarioBody.() -> Unit) {
    delegate.group("Scenario: $description”,
    defaultCachingMode = CachingMode.SCOPE,
    preserveExecutionOrder = true) {
    body(ScenarioBody(this))
    }
    }
    }

    View Slide

  56. @SpekDsl
    class ScenarioBody(val delegate: GroupBody): LifecycleAware by
    delegate {
    fun Given(description: String, body: TestBody.() -> Unit) {
    delegate.test("Given: $description", body = body)
    }
    fun When(description: String, body: TestBody.() -> Unit) {
    delegate.test("When: $description", body = body)
    }
    fun Then(description: String, body: TestBody.() -> Unit) {
    delegate.test("Then: $description", body = body)
    }
    fun And(description: String, body: TestBody.() -> Unit) {
    delegate.test("And: $description", body = body)
    }
    }

    View Slide

  57. @SpekDsl
    class ScenarioBody(val delegate: GroupBody): LifecycleAware by
    delegate {
    fun Given(description: String, body: TestBody.() -> Unit) {
    delegate.test("Given: $description", body = body)
    }
    fun When(description: String, body: TestBody.() -> Unit) {
    delegate.test("When: $description", body = body)
    }
    fun Then(description: String, body: TestBody.() -> Unit) {
    delegate.test("Then: $description", body = body)
    }
    fun And(description: String, body: TestBody.() -> Unit) {
    delegate.test("And: $description", body = body)
    }
    }

    View Slide

  58. @SpekDsl
    class ScenarioBody(val delegate: GroupBody): LifecycleAware by
    delegate {
    fun Given(description: String, body: TestBody.() -> Unit) {
    delegate.test("Given: $description", body = body)
    }
    fun When(description: String, body: TestBody.() -> Unit) {
    delegate.test("When: $description", body = body)
    }
    fun Then(description: String, body: TestBody.() -> Unit) {
    delegate.test("Then: $description", body = body)
    }
    fun And(description: String, body: TestBody.() -> Unit) {
    delegate.test("And: $description", body = body)
    }
    }
    શ෦ಉ͡ʁ

    View Slide

  59. class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("complete status") {
    Then(“the todo is not complete") {
    todo = Todo(id = 1, label = "Write Unit Test with Spek",
    complete = false, deadline = now)
    }
    Then(“complete the todo") {
    todo.checkComplete()
    }
    Then(“the todo is complete”) {
    assertTrue(todo.complete)
    }
    }
    }
    })
    ͭ·Γ
    ͜ΕͰ΋0,

    View Slide

  60. class TodoFeature : Spek({
    Feature("Todo") {
    val now = System.currentTimeMillis()
    lateinit var todo: Todo
    Scenario("changing todo finish status") {
    Then("todo is not finished ") {
    todo = Todo(id = 1, label = "Write Unit Test with Spek",
    finished = false, deadline = now)
    }
    Then("mark finish") {
    todo.markFinished()
    }
    Then("it should be true") {
    assertTrue(todo.finished)
    }
    }
    }
    })
    ॱ൪͸
    อূ͞Ε͍ͯΔ
    @SpekDsl
    class FeatureBody(val delegate: GroupBody): LifecycleAware by delegate {
    @Synonym(SynonymType.GROUP, prefix = "Scenario: ")
    @Descriptions(Description(DescriptionLocation.VALUE_PARAMETER, 0))
    fun Scenario(description: String, body: ScenarioBody.() -> Unit) {
    delegate.group("Scenario: $description”,
    defaultCachingMode = CachingMode.SCOPE,
    preserveExecutionOrder = true) {
    body(ScenarioBody(this))
    }
    }
    }

    View Slide

  61. $PSF$PODFQU
    https://spekframework.org/core-concepts/

    View Slide

  62. Ԡ༻ฤ4QFLͰ
    -JWF%BUB΍3PPNͷ
    6OJU5FTUΛॻ͍ͯΈΑ͏

    View Slide

  63. "OESPJEͷน

    View Slide

  64. -JWF%BUB+6OJU
    -JWF%BUBTFU7BMVF
    -JWF%BUBQPTU7BMVF
    ʢ%FGBVMU5BTL&YFDVUFSʣ

    View Slide

  65. -JWF%BUB+6OJU
    -JWF%BUBTFU7BMVF
    -JWF%BUBQPTU7BMVF
    ʢ%FGBVMU5BTL&YFDVUFSʣ
    KBWBMBOH3VOUJNF&YDFQUJPO.FUIPE
    HFU.BJO-PPQFSJOBOESPJEPT-PPQFSOPUNPDLFE
    PS
    /VMM1PJOUFS&YDFQUJPO
    KBWBMBOH3VOUJNF&YDFQUJPO.FUIPE
    HFU.BJO-PPQFSJOBOESPJEPT-PPQFSOPUNPDLFE
    PS
    /VMM1PJOUFS&YDFQUJPO

    View Slide

  66. -JWF%BUB+6OJU
    class InstantTaskExecutorRule : TestWatcher() {
    protected fun starting(description: Description) {
    super.starting(description)
    ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
    override fun executeOnDiskIO(runnable: Runnable) {
    runnable.run()
    }
    override fun postToMainThread(runnable: Runnable) {
    runnable.run()
    }
    override fun isMainThread(): Boolean {
    return true
    }
    })
    }
    protected fun finished(description: Description) {
    super.finished(description)
    ArchTaskExecutor.getInstance().setDelegate(null)
    }
    }

    View Slide

  67. -JWF%BUB+6OJU
    class InstantTaskExecutorRule : TestWatcher() {
    protected fun starting(description: Description) {
    super.starting(description)
    ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
    override fun executeOnDiskIO(runnable: Runnable) {
    runnable.run()
    }
    override fun postToMainThread(runnable: Runnable) {
    runnable.run()
    }
    override fun isMainThread(): Boolean {
    return true
    }
    })
    }
    protected fun finished(description: Description) {
    super.finished(description)
    ArchTaskExecutor.getInstance().setDelegate(null)
    }
    }
    UFTU*NQMFNFOUBUJPOlBOESPJEBSDIDPSFDPSFUFTUJOH7&34*0/

    View Slide

  68. -JWF%BUB+6OJU
    @Rule
    @JvmField
    val instantTaskExecutorRule = InstantTaskExecutorRule()
    ˏ3VMFͰࢦఆ

    View Slide

  69. -JWF%BUB+6OJU
    fun GroupBody.useLiveData() {
    androidx.arch.core.executor.ArchTaskExecutor.getInstance().setDelega
    te(object : TaskExecutor() {
    override fun executeOnDiskIO(runnable: Runnable) {
    runnable.run()
    }
    override fun isMainThread(): Boolean {
    return true
    }
    override fun postToMainThread(runnable: Runnable) {
    runnable.run()
    }
    })
    }
    4QFL&YUFOUJPOLUΛ
    ࡞੒

    View Slide

  70. -JWF%BUB4QFL
    class TodoFeature : Spek({
    useLiveData()
    Feature("Todo") {
    }
    })
    3PPUͷϥϜμ಺Ͱ
    ݺͼग़͠

    View Slide

  71. -JWF%BUB4QFL
    class TodoListViewModelSpec : Spek({
    useLiveData()
    val application: Application by lazy {
    Mockito.mock(Application::class.java)
    }
    val todoListViewModel: TodoListViewModel by lazy {
    TodoListViewModel(
    application,
    Schedulers.trampoline()
    )
    }
    val observer = mock>()
    lateinit var filterWord: String
    Feature("TodoViewModel") {
    Scenario("TodoͷҰཡϑΟϧλʔ͢ΔͱLiveDataʹ৽͍͠஋͕ೖΔ") {
    Given("LiveData͕ΞΫςΟϒʹͳΔ") {
    todoListViewModel.wordLiveData.observeForever(observer)
    }
    When("ViewModelͷϑΟϧλʔϝιουΛ࣮ߦ͢Δ") {
    filterWord = "Spek"
    todoListViewModel.filter(filterWord)
    }
    Then("LiveDataͷvalue͕SpekʹͳΔ") {
    Mockito.verify(observer).onChanged(filterWord)
    }
    }
    }
    })
    7JFX.PEFMͷ࣋ͭ
    -JWF%BUBͷ஋͕
    มߋ͞Ε͔ͨΛݟΔςετ

    View Slide

  72. -JWF%BUB4QFL
    val observer = mock>()
    lateinit var filterWord: String
    Feature("TodoViewModel") {
    Scenario("TodoͷҰཡϑΟϧλʔ͢ΔͱLiveDataʹ৽͍͠஋͕ೖΔ") {
    Given("LiveData͕ΞΫςΟϒʹͳΔ") {
    todoListViewModel.wordLiveData.observeForever(observer)
    }
    When("ViewModelͷϑΟϧλʔϝιουΛ࣮ߦ͢Δ") {
    filterWord = "Spek"
    todoListViewModel.filter(filterWord)
    }
    Then("LiveDataͷvalue͕SpekʹͳΔ") {
    Mockito.verify(observer).onChanged(filterWord)
    }
    }
    }
    0CTFSWFSͷ
    ϞοΫ࡞੒

    View Slide

  73. -JWF%BUB4QFL
    val observer = mock>()
    lateinit var filterWord: String
    Feature("TodoViewModel") {
    Scenario("TodoͷҰཡϑΟϧλʔ͢ΔͱLiveDataʹ৽͍͠஋͕ೖΔ") {
    Given("LiveData͕ΞΫςΟϒʹͳΔ") {
    todoListViewModel.wordLiveData.observeForever(observer)
    }
    When("ViewModelͷϑΟϧλʔϝιουΛ࣮ߦ͢Δ") {
    filterWord = "Spek"
    todoListViewModel.filter(filterWord)
    }
    Then("LiveDataͷvalue͕SpekʹͳΔ") {
    Mockito.verify(observer).onChanged(filterWord)
    }
    }
    }
    0CTFSWFΛ
    ։࢝͢Δ

    View Slide

  74. -JWF%BUB4QFL
    val observer = mock>()
    lateinit var filterWord: String
    Feature("TodoViewModel") {
    Scenario("TodoͷҰཡϑΟϧλʔ͢ΔͱLiveDataʹ৽͍͠஋͕ೖΔ") {
    Given("LiveData͕ΞΫςΟϒʹͳΔ") {
    todoListViewModel.wordLiveData.observeForever(observer)
    }
    When("ViewModelͷϑΟϧλʔϝιουΛ࣮ߦ͢Δ") {
    filterWord = "Spek"
    todoListViewModel.filter(filterWord)
    }
    Then("LiveDataͷvalue͕SpekʹͳΔ") {
    Mockito.verify(observer).onChanged(filterWord)
    }
    }
    }
    0CTFSWFSͷ
    PO$IBOHFE͕
    ݺ͹Ε͔ͨνΣοΫ

    View Slide

  75. 3PPN3PCPMFDUSJD
    @RunWith(RobolectricTestRunner::class)
    @Config(sdk = [O_MR1])
    class TodoDataBaseTest {
    private lateinit var database: TodoDatabase
    @Before
    fun openDatabase() {
    database = Room
    .inMemoryDatabaseBuilder(
    InstrumentationRegistry.getInstrumentation().targetContext,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .build()
    }
    @After
    fun closeDatabase() {
    database.close()
    }
    @Test
    @Throws(Exception::class)
    fun insert() {
    assertThat(database.todoDao().count(), `is`(0))
    val list = listOf(
    TodoEntity(
    name = "Robolectric࢖͏",
    category = 1
    ),
    TodoEntity(
    name = "JUnit࢖͏",
    category = 1
    )
    )
    database.todoDao().upsertAll(list)
    assertThat(database.todoDao().count(), `is`(2))
    }
    }

    View Slide

  76. 3PPN3PCPMFDUSJD
    @Before
    fun openDatabase() {
    database = Room
    .inMemoryDatabaseBuilder(
    InstrumentationRegistry
    .getInstrumentation()
    .targetContext,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .build()
    }

    View Slide

  77. 3PPN3PCPMFDUSJD
    @Before
    fun openDatabase() {
    database = Room
    .inMemoryDatabaseBuilder(
    InstrumentationRegistry
    .getInstrumentation()
    .targetContext,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .build()
    }
    ίϯςΩετ͕
    ͔ͭ͑Δ

    View Slide

  78. 3PPN3PCPMFDUSJD
    @Before
    fun openDatabase() {
    database = Room
    .inMemoryDatabaseBuilder(
    InstrumentationRegistry
    .getInstrumentation()
    .targetContext,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .build()
    }

    View Slide

  79. 3PPN4QFL
    5FTUPOZPVSIPTUNBDIJOF
    3PPNVTFTUIF42-JUF4VQQPSU-JCSBSZ XIJDIQSPWJEFT
    JOUFSGBDFTUIBUNBUDIUIPTFJOUIF"OESPJE'SBNFXPSLDMBTTFT
    5IJTTVQQPSUBMMPXTZPVUPQBTTDVTUPNJNQMFNFOUBUJPOTPG
    UIFTVQQPSUMJCSBSZUPUFTUZPVSEBUBCBTFRVFSJFT

    View Slide

  80. 3PPN4QFL
    5FTUPOZPVSIPTUNBDIJOF
    3PPNVTFTUIF42-JUF4VQQPSU-JCSBSZ XIJDIQSPWJEFT
    JOUFSGBDFTUIBUNBUDIUIPTFJOUIF"OESPJE'SBNFXPSLDMBTTFT
    5IJTTVQQPSUBMMPXTZPVUPQBTTDVTUPNJNQMFNFOUBUJPOTPG
    UIFTVQQPSUMJCSBSZUPUFTUZPVSEBUBCBTFRVFSJFT
    ˒/PUF&WFOUIPVHIUIJTTFUVQBMMPXTZPVSUFTUTUPSVOWFSZ
    RVJDLMZ JUJTOUSFDPNNFOEFECFDBVTFUIFWFSTJPOPG42-JUF
    SVOOJOHPOZPVSEFWJDFŠBOEZPVSVTFSTEFWJDFTŠNJHIUOPU
    NBUDIUIFWFSTJPOPOZPVSIPTUNBDIJOF

    View Slide

  81. 3PPN4QFL
    class TodoDaoSpec : Spek({
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }
    Feature("TodoDao") {
    lateinit var result: Single
    Scenario("TodoDatabase") {
    Given("two todos have already been registered") {
    val todoList = listOf(
    TodoEntity(name = "SpekͰUnitTestΛॻ͘", finished = false, deadline = 1L),
    TodoEntity(name = "JUnitͰUnitTestΛॻ͘", finished = false, deadline = 1L)
    )
    todoDatabase.todoDao().upsertAll(todoList)
    }
    When("count todo") {
    result = todoDatabase.todoDao().count()
    }
    Then("it should be 2") {
    result.test().await().assertValue(2)
    }
    }
    }
    })

    View Slide

  82. 3PPN4QFL
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }

    View Slide

  83. 3PPN4QFL
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }
    +%#$ͷϔϧύʔ
    Λࢦఆ͢Δ

    View Slide

  84. 3PPN4QFL
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }
    ϝΠϯεϨουͷ
    ֬ೝΛऔΔ

    View Slide

  85. 3PPN4QFL
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }

    View Slide

  86. 3PPN4QFL
    useLiveData()
    val context: Context by lazy {
    Mockito.mock(Context::class.java)
    }
    val todoDatabase: TodoDatabase by lazy {
    Room.inMemoryDatabaseBuilder(
    context,
    TodoDatabase::class.java
    ).allowMainThreadQueries()
    .openHelperFactory(JdbcSQLiteOpenHelperFactory())
    .build()
    }
    ίϯςΩετ͸
    ୅༻Λ༻ҙ

    View Slide

  87. 3PPN4QFL
    Scenario("TodoDatabase") {
    lateinit var result: Single
    Given("two todos have already been registered") {
    val todoList = listOf(
    TodoEntity(name = "SpekͰUnitTestΛॻ͘", complete = false,
    deadline = 1L),
    TodoEntity(name = "JUnitͰUnitTestΛॻ͘", complete =
    false, deadline = 1L)
    )
    todoDatabase.todoDao().upsertAll(todoList)
    }
    When("count todo") {
    result = todoDatabase.todoDao().count()
    }
    Then("it should be 2") {
    result.test().await().assertValue(2)
    }
    }
    ͍͖ͳΓͷ3Y

    View Slide

  88. ·ͱΊ
    ɾ4QFL͸+FU#SBJOTࣾ੡ͷςετϑϨʔϜϫʔΫ
    ɾ+6OJUʹൺ΂ͯه๏͕ಠಛ͕ͩɺΑΓࢿ࢈ʹͳΔςετ͕ॻ͚Δ
    ɾ࣮ߦॱংΛؾʹ্͔ͤͣΒॻ͜͏
    ɾ3PPN΍-JWF%BUBΛ࢖͍ͬͯͯ΋"OESPJEϑϨʔϜϫʔΫ෦෼Λ޻
    ෉͢Δͱ6OJU5FTU͸ॻ͚Δ

    View Slide

  89. ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ

    View Slide