Slide 1

Slide 1 text

©2021 Wantedly, Inc. ,..ͷςετͷUJQT ,PUMJO.VMUJQMBUGPSN.PCJMFษڧձ8BOUFEMZºνʔϜϥϘ ٱอग़խढ़ 2021/07/29

Slide 2

Slide 2 text

©2021 Wantedly, Inc. XXXXBOUFEMZDPNJELVCPEF

Slide 3

Slide 3 text

©2021 Wantedly, Inc. w ,..ͷςετʹ͍ͭͯ w ,PUMJO+7.ͱͷҧ͍ w ҧ͍ΛຒΊΔUJQTͷ঺հ "HFOEB

Slide 4

Slide 4 text

©2021 Wantedly, Inc. ,..ͷςετʹ͍ͭͯ

Slide 5

Slide 5 text

©2021 Wantedly, Inc. ,..͸6*Λ࣋ͨͳ͍ ,..Λςετ͢Δʹ͸ʁ 🙅ϚχϡΞϧ 🙆ςετίʔυ ,..ͷςετͷඞཁੑ https://speakerdeck.com/malvinstn/automated-testing-in-a-kmm-project?slide=6

Slide 6

Slide 6 text

©2021 Wantedly, Inc. ,..ͷςετͷσΟϨΫτϦߏ੒ iOS/AndroidͲͪΒͰ΋ςετ͞ΕΔίʔυ iOSͰͷΈςετ͞ΕΔίʔυ AndroidͰͷΈςετ͞ΕΔίʔυ

Slide 7

Slide 7 text

©2021 Wantedly, Inc. ,PUMJO+7.ͱͷҧ͍

Slide 8

Slide 8 text

©2021 Wantedly, Inc. ,PUMJO$PNNPOͷ"1*͔͠ར༻Ͱ͖ͳ͍ w +6OJU 3VMFͳͲ w NPDLL w DPSPVUJOFUFTU w FUD DPNNPO5FTUͱ+7.5FTUͱͷҧ͍ // 🙅 @Rule @JvmField val rule = SomeTestRule() // 🙅 @MockK lateinit var mock: SomeInterface @Test fun test() { // 🙅 every { mock.doSomething() } just Runs doTest() // 🙅 verify { mock.doSomething() } }

Slide 9

Slide 9 text

©2021 Wantedly, Inc. ҧ͍ΛຒΊΔUJQTͷ঺հ

Slide 10

Slide 10 text

©2021 Wantedly, Inc. 5FTU3VMF

Slide 11

Slide 11 text

©2021 Wantedly, Inc. w %#Λ࢖ͬͨ෦෼ͷςετΛ͍ͨ͠ w ςετͷͨͼʹΫϦʔϯͳ%#Λ࢖͍͍ͨ w ΫϦΞ͢ΔίʔυΛຖճॻ͘ͷ͸ਏ͍ʜ 5FTU3VMF class MyTest1 { lateinit var db: VisitAppDb @BeforeTest fun setup() { db = VisitAppDb(testSqlDriver()) } @AfterTest fun tearDown() { db.clean() } } class MyTest2 { lateinit var db: VisitAppDb @BeforeTest fun setup() { db = VisitAppDb(testSqlDriver()) } @AfterTest fun tearDown() { db.clean() } } class MyTest3 { lateinit var db: VisitAppDb @BeforeTest fun setup() { db = VisitAppDb(testSqlDriver()) } @AfterTest fun tearDown() { db.clean() } } class MyTest4… { lateinit var db: Visi @BeforeTest fun setup db = VisitAppDb(t } @AfterTest fun tearDo db.clean() } }

Slide 12

Slide 12 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ interface TestRule { fun onBeforeTest() = Unit fun onAfterTest() = Unit } class TestRules(vararg testRules: TestRule) : List by testRules.toList() interface HasTestRules { val testRules: TestRules @BeforeTest fun setupTestRules() { testRules.forEach { it.onBeforeTest() } } @AfterTest fun tearDownTestRules() { testRules.forEach { it.onAfterTest() } } }

Slide 13

Slide 13 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ interface TestRule { fun onBeforeTest() = Unit fun onAfterTest() = Unit } class TestRules(vararg testRules: TestRule) : List by testRules.toList() interface HasTestRules { val testRules: TestRules @BeforeTest fun setupTestRules() { testRules.forEach { it.onBeforeTest() } } @AfterTest fun tearDownTestRules() { testRules.forEach { it.onAfterTest() } } } ϧʔϧΛ࡞੒͢Δʹ͸5FTU3VMFΛ࣮૷

Slide 14

Slide 14 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ interface TestRule { fun onBeforeTest() = Unit fun onAfterTest() = Unit } class TestRules(vararg testRules: TestRule) : List by testRules.toList() interface HasTestRules { val testRules: TestRules @BeforeTest fun setupTestRules() { testRules.forEach { it.onBeforeTest() } } @AfterTest fun tearDownTestRules() { testRules.forEach { it.onAfterTest() } } } 5FTU3VMFΛ࢖͍͍ͨ5FTUΫϥεʹ࣮૷ͤ͞Δ ͜ΕΒͷσϑΥϧτ࣮૷͕5FTUΫϥεͰࣗಈͰݺ͹ΕΔ

Slide 15

Slide 15 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ class DbTestRule : TestRule { private lateinit var sqlDriver: SqlDriver lateinit var db: VisitAppDb private set override fun onBeforeTest() { sqlDriver = testSqlDriver() db = VisitAppDb(driver = sqlDriver) } override fun onAfterTest() { sqlDriver.close() } } class MyTest : HasTestRules { val dbTestRule = DbTestRule() override val testRules = TestRules(dbTestRule) @Test fun test() { dbTestRule.db.userQueries.insertOrReplace(User()) val all = dbTestRule.db.userQueries.selectAll().executeAsList() assertEquals(1, all.size) } }

Slide 16

Slide 16 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ class DbTestRule : TestRule { private lateinit var sqlDriver: SqlDriver lateinit var db: VisitAppDb private set override fun onBeforeTest() { sqlDriver = testSqlDriver() db = VisitAppDb(driver = sqlDriver) } override fun onAfterTest() { sqlDriver.close() } } class MyTest : HasTestRules { val dbTestRule = DbTestRule() override val testRules = TestRules(dbTestRule) @Test fun test() { dbTestRule.db.userQueries.insertOrReplace(User()) val all = dbTestRule.db.userQueries.selectAll().executeAsList() assertEquals(1, all.size) } } 5FTU3VMFΛ࣮૷ͯ͠#FGPSF"GUFSΛهड़ UFTU4RM%SJWFS ͕ຖճ%#ΫϦΞ͍ͯ͠Δ

Slide 17

Slide 17 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ class DbTestRule : TestRule { private lateinit var sqlDriver: SqlDriver lateinit var db: VisitAppDb private set override fun onBeforeTest() { sqlDriver = testSqlDriver() db = VisitAppDb(driver = sqlDriver) } override fun onAfterTest() { sqlDriver.close() } } class MyTest : HasTestRules { val dbTestRule = DbTestRule() override val testRules = TestRules(dbTestRule) @Test fun test() { dbTestRule.db.userQueries.insertOrReplace(User()) val all = dbTestRule.db.userQueries.selectAll().executeAsList() assertEquals(1, all.size) } } 5FTUΫϥεʹ)BT5FTU3VMFTΛ࣮૷

Slide 18

Slide 18 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ class DbTestRule : TestRule { private lateinit var sqlDriver: SqlDriver lateinit var db: VisitAppDb private set override fun onBeforeTest() { sqlDriver = testSqlDriver() db = VisitAppDb(driver = sqlDriver) } override fun onAfterTest() { sqlDriver.close() } } class MyTest : HasTestRules { val dbTestRule = DbTestRule() override val testRules = TestRules(dbTestRule) @Test fun test() { dbTestRule.db.userQueries.insertOrReplace(User()) val all = dbTestRule.db.userQueries.selectAll().executeAsList() assertEquals(1, all.size) } } ࢖͍͍ͨ5FTU3VMFΛࢦఆ

Slide 19

Slide 19 text

©2021 Wantedly, Inc. 5FTU3VMF࣮૷ class DbTestRule : TestRule { private lateinit var sqlDriver: SqlDriver lateinit var db: VisitAppDb private set override fun onBeforeTest() { sqlDriver = testSqlDriver() db = VisitAppDb(driver = sqlDriver) } override fun onAfterTest() { sqlDriver.close() } } class MyTest : HasTestRules { val dbTestRule = DbTestRule() override val testRules = TestRules(dbTestRule) @Test fun test() { dbTestRule.db.userQueries.insertOrReplace(User(userId = 1, name = "Foo")) val all = dbTestRule.db.userQueries.selectAll().executeAsList() assertEquals(1, all.size) } } !#FGPSF5FTU!"GUFS5FTUΛॻ͔ͳͯ͘Α͘ͳΔ

Slide 20

Slide 20 text

©2021 Wantedly, Inc. .PDL

Slide 21

Slide 21 text

©2021 Wantedly, Inc. w ͍͍ײ͡ͷ.PDLϥΠϒϥϦ͸ͳ͍ w ࣗ෼Ͱॻ͔͘͠ͳ͍ .PDL

Slide 22

Slide 22 text

©2021 Wantedly, Inc. w ςελϒϧͳઃܭʹ͢Δ w ந৅ JOUFSGBDF ʹґଘ w ۩৅ DMBTT PCKFDU HMPCBMGVO ʹґଘ͠ͳ͍ ςελϒϧͳઃܭ

Slide 23

Slide 23 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ class ApiClient { fun createUser(): User { // ࣮ࡍ͸௨৴͢Δ return User(…) } } class UserRepository { fun create(): User { return ApiClient().createUser() } }

Slide 24

Slide 24 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ class ApiClient { fun createUser(): User { // ࣮ࡍ͸௨৴͢Δ return User(…) } } class UserRepository { fun create(): User { return ApiClient().createUser() } } "QJ$MJFOUͱ͍͏۩৅Ϋϥεʹґଘ͍ͯ͠Δ

Slide 25

Slide 25 text

©2021 Wantedly, Inc. @Test fun test() { val user = UserRepository().create() // ௨৴݁Ռ͕Θ͔Βͳ͍ͷͰςετͰ͖ͳ͍ // ௨৴ࣦഊͨ͠ΒͲ͏ͳΔʁ } ςελϒϧͳઃܭ

Slide 26

Slide 26 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ interface ApiClient { fun createUser(): User } class ApiClientImpl : ApiClient { override fun createUser(): User { // ࣮ࡍ͸௨৴͢Δ return User() } } class UserRepository(private val apiClient: ApiClient) { fun create(): User { return apiClient.createUser() } } "QJ$MJFOUͱ͍͏JOUFSGBDFʹ͢Δ

Slide 27

Slide 27 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ interface ApiClient { fun createUser(): User } class ApiClientImpl : ApiClient { override fun createUser(): User { // ࣮ࡍ͸௨৴͢Δ return User() } } class UserRepository(private val apiClient: ApiClient) { fun create(): User { return apiClient.createUser() } } JOUFSGBDFΛ࣮૷ͨ͠*NQMΫϥεʹ͢Δ

Slide 28

Slide 28 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ interface ApiClient { fun createUser(): User } class ApiClientImpl : ApiClient { override fun createUser(): User { // ࣮ࡍ͸௨৴͢Δ return User() } } class UserRepository(private val apiClient: ApiClient) { fun create(): User { return apiClient.createUser() } } JOUFSGBDFΛ஫ೖͯ͠JOUFSGBDFʹґଘ͢Δ

Slide 29

Slide 29 text

©2021 Wantedly, Inc. ςελϒϧͳઃܭ class ApiClientMock : ApiClient { lateinit var mock: () -> User override fun createUser() = mock() } NPDLʹϥϜμΛೖΕΔ͜ͱͰDSFBUF6TFS ͷ݁ՌΛࠩ͠ସ͑

Slide 30

Slide 30 text

©2021 Wantedly, Inc. @Test fun test() { val apiClient = ApiClientMock() apiClient.mock = { User("test") } val user = UserRepository(apiClient).create() assertEquals("test", user.id) } @Test fun testFailed() { val apiClient = ApiClientMock() apiClient.mock = { throw ExpectedException() } try { UserRepository(apiClient).create() fail() } catch (e: ExpectedException) { // Expected } } ςελϒϧͳઃܭ

Slide 31

Slide 31 text

©2021 Wantedly, Inc. @Test fun test() { val apiClient = ApiClientMock() apiClient.mock = { User("test") } val user = UserRepository(apiClient).create() assertEquals("test", user.id) } @Test fun testFailed() { val apiClient = ApiClientMock() apiClient.mock = { throw ExpectedException() } try { UserRepository(apiClient).create() fail() } catch (e: ExpectedException) { // Expected } } ςελϒϧͳઃܭ NPDLϓϩύςΟʹϥϜμΛೖΕͯϞοΫ͢Δ

Slide 32

Slide 32 text

©2021 Wantedly, Inc. @Test fun test() { val apiClient = ApiClientMock() apiClient.mock = { User("test") } val user = UserRepository(apiClient).create() assertEquals("test", user.id) } @Test fun testFailed() { val apiClient = ApiClientMock() apiClient.mock = { throw ExpectedException() } try { UserRepository(apiClient).create() fail() } catch (e: ExpectedException) { // Expected } } ςελϒϧͳઃܭ .PDLΛ஫ೖɺNPDL͕࢖ΘΕΔ

Slide 33

Slide 33 text

©2021 Wantedly, Inc. @Test fun test() { val apiClient = ApiClientMock() apiClient.mock = { User("test") } val user = UserRepository(apiClient).create() assertEquals("test", user.id) } @Test fun testFailed() { val apiClient = ApiClientMock() apiClient.mock = { throw ExpectedException() } try { UserRepository(apiClient).create() fail() } catch (e: ExpectedException) { // Expected } } ςελϒϧͳઃܭ .PDLͷ಺༰͔Βظ଴͞ΕΔ݁ՌΛςετ

Slide 34

Slide 34 text

©2021 Wantedly, Inc. @Test fun test() { val apiClient = ApiClientMock() apiClient.mock = { User("test") } val user = UserRepository(apiClient).create() assertEquals("test", user.id) } @Test fun testFailed() { val apiClient = ApiClientMock() apiClient.mock = { throw ExpectedException() } try { UserRepository(apiClient).create() fail() } catch (e: ExpectedException) { // Expected } } ςελϒϧͳઃܭ ྫ֎͕ى͖ͨͱ͖ͷςετ΋༰қʹ

Slide 35

Slide 35 text

©2021 Wantedly, Inc. ·ͱΊ

Slide 36

Slide 36 text

©2021 Wantedly, Inc. w ,..ͷςετ͸·ͩπʔϧ͕গͳ͍ w ͳ͔ͬͨΒ࡞͍ͬͯ͘ਫ਼ਆ💪 w *OUFSGBDF*NQMΛ෼͚Δςελϒϧͳઃܭ͕ॏཁ w ϘΠϥʔϓϨʔτ͸૿͑ͯ͠·͏ w DPNQJMFSQMVHJOͰ͍͍ײ͡ʹ͍ͨ͠ ·ͱΊ

Slide 37

Slide 37 text

ϖʔδλΠτϧ ϖʔδαϒλΠτϧ ©2021 Wantedly, Inc. 5IBOLT