Slide 1

Slide 1 text

STORES 株式会社 テストを楽に書きたい DroidKaigi 2024 おつかれさまパーティ 2024.10.11 @tomorrowkey

Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

自己紹介 ● 山下智樹 / @tomorrowkey ● STORES株式会社 - STORES ブランドアプリ ● Android エンジニア (14年生) 3

Slide 4

Slide 4 text

導入

Slide 5

Slide 5 text

導入 品質をあげたい 開発効率をあげたい 安心してコードを変更したい 5

Slide 6

Slide 6 text

導入 モチベーションは十分 だが実際はどうだ? テスト書くのつらい! 6

Slide 7

Slide 7 text

導入 テストを書いているときにつらいと思うこと - 実行速度 - ターンアラウンドタイムで1秒でおわってほしい - 名前をつけるのがつらい - いい感じのテスト名が勝手についてほしい 7

Slide 8

Slide 8 text

導入 テストを書いているときにつらいと思うこと - 実行速度 - ターンアラウンドタイムで1秒でおわってほしい - 名前をつけるのがつらい - いい感じのテスト名が勝手についてほしい 8

Slide 9

Slide 9 text

名前がどうつらいのか

Slide 10

Slide 10 text

JUnit class PersonTest { @Test fun birthdayAsString_test() { val birthday = LocalDate.of(2000, 11, 23) val person = Person(birthday) assertThat(person.birthdayAsString("yyyy/MM/dd")).isEqualTo("2000/11/23") } } 10 - テスト名は一意でなければならない - 抽象的な名前では重複してしまう - 具体的な名前をつけようとするとメソッド名が長くなりがち - 認知負荷が高い

Slide 11

Slide 11 text

テストを書くのが楽しいRSpecから学ぶ

Slide 12

Slide 12 text

RSpec RSpec.describe Person do describe '#birthday_as_string' do let(:person) { Person.new(birthday) } context 'when birthday is 2000/11/23' do let(:birthday) { Date.new(2000, 11, 23) } context 'when format is %Y/%m/%d' do let(:format) { '%Y/%m/%d' } it do expect(person.birthday_as_string(format)). to eq('2000/11/23') end end end end end 12 - 構造的に記述できる - テストケースの多様性を 表現しやすい - 適切な情報量

Slide 13

Slide 13 text

JUnit + Kotest class PersonTest: DescribeSpec() { describe("#birthdayAsString") { context("when birthday is 2000/11/23") { val birthday = newDate(2000, Calendar.NOVEMBER, 23) context("when format is yyyy/MM/dd") { val format = "yyyy/MM/dd" it("returns 2000/11/23") { val person = Person(birthday) person.birthdayAsString(format) shouldBe "2000/11/23" } } } } } 13 Kotestを使えば構造化されたテストを記述できるため、解決する

Slide 14

Slide 14 text

KotestはAndroidでは使えません 14 https://kotest.io/docs/quickstart 󰢄

Slide 15

Slide 15 text

KotestはAndroidでは使えません 厳密には使用できますが、制限のことを考えるとまだ使えないと認識すべきです。 - Kotestは公式にはJUnit5しかサポートしていない - mannodermaus/android-junit5 を使えばAndroidでもJUnit5を使える - しかし、AndroidのテストにおいてRobolectricの存在が大きい - RobolectricはJUnit5をまだサポートしていない - 厳密には有志によってJUnit5をサポートする開発が進められています 詳しくはDroidKaigi 2024「A New Era of Testing」をご確認ください 15

Slide 16

Slide 16 text

DroidKaigi Conference Appはどうしてるの

Slide 17

Slide 17 text

DroidKaigi Conference Appのアプローチ @RunWith(ParameterizedRobolectricTestRunner::class) class KaigiAppTest(private val testCase: DescribedBehavior) { @Test fun runTest() { runRobot(kaigiAppRobot) { testCase.execute(kaigiAppRobot) } } companion object { @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") fun behaviors(): List> { return describeBehaviors(name = "KaigiApp") { describe("when app is starting") { doIt { waitUntilIdle() } itShould("show timetable items") { captureScreenWithChecks { runRobot(timetableScreenRobot) { checkTimetableListItemsDisplayed() } } } } } } } } 17

Slide 18

Slide 18 text

DroidKaigi Conference Appのアプローチ @RunWith(ParameterizedRobolectricTestRunner::class) class KaigiAppTest(private val testCase: DescribedBehavior) { @Test fun runTest() { runRobot(kaigiAppRobot) { testCase.execute(kaigiAppRobot) } } companion object { @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") fun behaviors(): List> { return describeBehaviors(name = "KaigiApp") { describe("when app is starting") { doIt { waitUntilIdle() } itShould("show timetable items") { captureScreenWithChecks { runRobot(timetableScreenRobot) { checkTimetableListItemsDisplayed() } } } } } } } } 18

Slide 19

Slide 19 text

DroidKaigi Conference Appのアプローチ @RunWith(ParameterizedRobolectricTestRunner::class) class KaigiAppTest(private val testCase: DescribedBehavior) { @Test fun runTest() { runRobot(kaigiAppRobot) { testCase.execute(kaigiAppRobot) } } companion object { @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") fun behaviors(): List> { return describeBehaviors(name = "KaigiApp") { describe("when app is starting") { doIt { waitUntilIdle() } itShould("show timetable items") { captureScreenWithChecks { runRobot(timetableScreenRobot) { checkTimetableListItemsDisplayed() } } } } } } } } 19

Slide 20

Slide 20 text

DroidKaigi Conference Appのアプローチ @RunWith(ParameterizedRobolectricTestRunner::class) class KaigiAppTest(private val testCase: DescribedBehavior) { @Test fun runTest() { runRobot(kaigiAppRobot) { testCase.execute(kaigiAppRobot) } } companion object { @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") fun behaviors(): List> { return describeBehaviors(name = "KaigiApp") { describe("when app is starting") { doIt { waitUntilIdle() } itShould("show timetable items") { captureScreenWithChecks { runRobot(timetableScreenRobot) { checkTimetableListItemsDisplayed() } } } } } } } } 20 https://robolectric.org/javadoc/4.1/org/robolectric/ParameterizedRobolectricTestRunner.Parameters.html

Slide 21

Slide 21 text

DroidKaigi Conference Appのアプローチ - 実現しているのはrobospecというライブラリ - 構造化されたテストを記述するためにDSLを定義している 21

Slide 22

Slide 22 text

おわり