Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!

7c3b3366947123ba6772698b09edf4e2?s=47 subroh_0508
February 08, 2019

Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!

DroidKaigi 2019 - Day2 Room1 11:20〜
「Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!」のセッション資料です

7c3b3366947123ba6772698b09edf4e2?s=128

subroh_0508

February 08, 2019
Tweet

Transcript

  1. 4QFL .PDL, +B$P$PͰΠέͯΔ 6OJU5FTU؀ڥΛखʹೖΕΖʂ %SPJE,BJHJ%BZ3PPNʙ ʹ͜͠Γ͞ͿΖʙ!TVCSPI@ !1

  2. ࣗݾ঺հ w ʹ͜͠Γ͞ͿΖʙ ຊ໊ࡔ্੖৴  w ౦ژ౎ɾҏ౾େౡग़਎ w גࣜձࣾ#FBS5BJM w

    "OESPJEΤϯδχΞ ,PUMJO+BWB  w 8FCΤϯδχΞ 3BJMT3FBDU !2
  3. ࢓ࣄ w %S8BMMFU ,PUMJOҠߦ࡞ۀதʜ !3

  4. ࢓ࣄ w %Sܦඅਫ਼ࢉ ϑϧ,PUMJOʂ .PDL,4QFL ಋೖࡁΈʂ !4

  5. ໨࣍  ͜Ε·Ͱͷ6OJU5FTU  ,PUMJO੡ϑϨʔϜϫʔΫϥΠϒϥϦͷ঺հ w .PDL,ϞοΫϥΠϒϥϦ w 4QFLςετϑϨʔϜϫʔΫ 

    ,PUMJOϑϨϯυϦʔͳ6OJU5FTU؀ڥͷߏங w .PDL,ɾ4QFLͷಋೖ w +B$P$PΛ࢖ͬͨίʔυΧόϨοδࢉग़ !5
  6. ର৅ w ॳΊͯ"OESPJE։ൃͰ6OJU5FTUΛॻ͜͏ͱ͍ͯ͠Δਓ w +BWB੡ϥΠϒϥϦͰͷςετʹർΕͨਓ !6

  7. ͜Ε·Ͱͷ6OJU5FTU !7

  8. ,PUMJOਖ਼ࣜαϙʔτલ w .PDLJUPϞοΫϥΠϒϥϦ w WSFMFBTFʙ೥ࠒ w ཉ͍͠ػೳ͸େମἧ͍ͬͯΔ w ৘ใ͕๛෋ w

    ,PUMJO༻ϥούʔϥΠϒϥϦ΋ΞϦ w .PDLJUP,PUMJO ೥݄ʙ !8
  9. w +6OJUςετϑϨʔϜϫʔΫ w "OESPJEϓϩδΣΫτͰͷσϑΥϧτͷϑϨʔϜϫʔΫ w BOOPUBUJPOͰςετϝιουΛࢦఆ ,PUMJOਖ਼ࣜαϙʔτલ !9

  10. ,PUMJOਖ਼ࣜαϙʔτલ w ϞοΫϥΠϒϥϦˠ.PDLJUP w ϢχοτςετϑϨʔϜϫʔΫˠ+6OJU w ͲͪΒ΋+BWB੡ w ,PUMJOରԠ͕ෆे෼ w

    ,PUMJOͷݴޠ࢓༷Λ׆͔͖͠Εͳ͍ !10
  11. ,PUMJOਖ਼ࣜαϙʔτ !(PPHMF*0 !11

  12. ೥൒ޙʜ w ,PUMJO੡ϥΠϒϥϦϑϨʔϜϫʔΫ w ,PUMJOͷݴޠ࢓༷Λϑϧʹ׆͔ͤΔʂ w ʮ+BWB͔Βݟͨ࣌͜ͷίʔυͲ͏ͳΔΜ͚ͩͬʯˠগͳ͘ͳΔ w ςετॻ࣌͘ʹߟ͑Δ΂͖͜ͱ͕ݮΔˠܧଓੑͷ͋Δςετ w

    ҆৺ͯ͠࢖͑Δʂ w ৘ใ͕૿͖͑ͯͨʂ ,PUMJO੡ϥΠϒϥϦϑϨʔϜϫʔΫ ಋೖͷ޷ػʂ !12
  13. ,PUMJO੡ ϑϨʔϜϫʔΫϥΠϒϥϦͷ঺հ !13

  14. .PDL, w ϞοΫϥΠϒϥϦ w ,PUMJOಠࣗͷݴޠ࢓༷Λ໢ཏ w 6OJU5FTU*OTUSVNFOUFE5FTU྆ରԠ w %SPJE,BJHJެࣜΞϓϦͰ΋ར༻ (JUIVCNPDLLNPDLL

    !14
  15. Ͱ͖Δ͜ͱ w ελϒ w ϝιουͷฦΓ஋ͷࠩ͠ସ͑ w ϞοΫ w ϝιουͷҾ਺ɺݺ͹Εͨճ਺ɺฦΓ஋ͷݕূ w

    ϝιου಺ͷॲཧ͸ಈ࡞͠ͳ͍ w εύΠ w ϝιουͷҾ਺ɺݺ͹Εͨճ਺ɺฦΓ஋ͷݕূ w ϝιου಺ͷॲཧ͕࣮ࡍʹಈ࡞͢Δ ৄ͘͠͸ "OESPJEςετશॻͰʜ !15
  16. NPDLL5ϝιου 1 val car = mockk<Car>() 2 3 every {

    car.drive(Direction.NORTH) } returns Outcome.OK 4 5 car.drive(Direction.NORTH) // returns OK 6 7 verify(exactly = 1) { car.drive(Direction.NORTH) } 8 9 confirmVerified(car) !16
  17. NPDLL5ϝιου 1 val car = mockk<Car>() 2 3 every {

    car.drive(Direction.NORTH) } returns Outcome.OK 4 5 car.drive(Direction.NORTH) // returns OK 6 7 verify(exactly = 1) { car.drive(Direction.NORTH) } 8 9 confirmVerified(car) NPDLL5 ϝιου $BSܕͷϞοΫΠϯελϯεΛ࡞੒ !17
  18. NPDLL5ϝιου 1 val car = mockk<Car>() 2 3 every {

    car.drive(Direction.NORTH) } returns Outcome.OK 4 5 car.drive(Direction.NORTH) // returns OK 6 7 verify(exactly = 1) { car.drive(Direction.NORTH) } 8 9 confirmVerified(car) FWFSZ\ʜ^ϝιου ϞοΫʹϝιουͱҾ਺ɺฦΓ஋Λڭ͑ࠐΉ ESJWFϝιου಺ͷ࣮ࡍͷॲཧ͸ಈ࡞͠ͳ͍ !18
  19. NPDLL5ϝιου 1 val car = mockk<Car>() 2 3 every {

    car.drive(Direction.NORTH) } returns Outcome.OK 4 5 car.drive(Direction.NORTH) // returns OK 6 7 verify(exactly = 1) { car.drive(Direction.NORTH) } 8 9 confirmVerified(car) DBSʹ wʮ%JSFDUJPO/035)ʯΛҾ਺ʹड͚ wʮ0VUDPNF0,ʯΛฦ͢ wʮESJWFʯϝιου͕͋Δɹ͜ͱΛڭ͑ࠐΉ !19
  20. NPDLL5ϝιου 1 val car = mockk<Car>() 2 3 every {

    car.drive(Direction.NORTH) } returns Outcome.OK 4 5 car.drive(Direction.NORTH) // returns OK 6 7 verify(exactly = 1) { car.drive(Direction.NORTH) } 8 9 confirmVerified(car) WFSJGZ\ʜ^ϝιου WFSJGZ಺ͷϝιου͕ݺ͹Εͨ͜ͱΛνΣοΫ Ҿ਺FYBDUMZʹݺ͹ΕΔճ਺ΛࢦఆͰ͖Δ !20
  21. TQZL5ϝιου 1 val car = spyk(Car()) 2 3 car.drive(Direction.NORTH) 4

    5 verify { car.drive(Direction.NORTH) } 6 7 confirmVerified(car) !21
  22. TQZL5ϝιου 1 val car = spyk(Car()) 2 3 car.drive(Direction.NORTH) 4

    5 verify { car.drive(Direction.NORTH) } 6 7 confirmVerified(car) TQZL5 ϝιου $BSܕͷεύΠΠϯελϯεΛ࡞੒ !22
  23. TQZL5ϝιου 1 val car = spyk(Car()) 2 3 car.drive(Direction.NORTH) 4

    5 verify { car.drive(Direction.NORTH) } 6 7 confirmVerified(car) WFSJGZ\ʜ^ϝιου WFSJGZ಺ͷϝιου͕ݺ͹Εͨ͜ͱΛνΣοΫ ESJWFϝιου಺ͷॲཧ͕࣮ࡍʹಈ࡞͢Δ !23
  24. 1 val car = mockk<Car>() 2 3 // any(): ͋ΒΏΔ1ͭͷҾ਺ʹϚον

    4 every { car.drive(any()) } return Outcome.OK 5 6 // isNull(inverse = true): nullͰͳ͍1ͭͷҾ਺ʹϚον 7 every { car.drive(isNull(inverse = true)) } return Outcome.OK 8 9 // eq(value): Ҿ਺͕valueͱҰக͢Δ࣌Ϛον(deep equals) 10 every { car.drive(eq(Direction.NORTH)) } return Outcome.OK 11 12 // ofType(kClass): Ҿ਺ͷܕ͕kClassͱҰக͢Δ࣌ʹϚον 13 every { car.drive(ofType(String::class)) } return Outcome.OK 14 ϚονϟʔҾ਺΍ฦΓ஋ͷݕূʹ࢖͏ϝιου BOENPSFʜ !24
  25. ϞοΫ΋εύΠ΋Ϛονϟʔ΋.PDLJUPʹ͋ΔΑʁʁʁ !25

  26. .PDLJUPͷ໰୊఺  w 'JOBMͳΫϥεϝιου͕ϞοΫͰ͖ͳ͍ʂ w ,PUMJOͷΫϥεϝιου͸σϑΥϧτͰ'JOBM w ճආࡦLPUMJOBMMPQFOϓϥάΠϯ CZ+FU#SBJOT 

    w BOOPUBUJPO͚ͭΔͱPQFOʹͯ͘͠ΕΔɹ ڧҾͩͳʜ  w ςετͷͨΊʹຊ൪ίʔυ΁ͷ༨ܭͳهड़͕૿͑Δ !26
  27. .PDLJUPͷ໰୊఺  w 4UBUJDͳϝιου͕ϞοΫͰ͖ͳ͍ʂ w ֦ுؔ਺ɹγϯάϧτϯΦϒδΣΫτ w ճආࡦ1PXFS.PDL w 4UBUJDϝιουɺQSJWBUFϝιουͷϞοΫ͕Ͱ͖Δ

    w Θ͟Θ͟ผϥΠϒϥϦΛಋೖ͢Δ͜ͱ͕ඍົ !27
  28. .PDL,ͷڧΈ w ,PUMJOͷΫϥεϝιουΛ΄΅ԿͰ΋ϞοΫͰ͖ͯ͠·͏ʂ w 'JOBMͳΫϥεϝιου w γϯάϧτϯΦϒδΣΫτ w ֦ுؔ਺ w

    QSJWBUFϝιου w TVTQFOEϝιουFUD !28
  29. γϯάϧτϯΦϒδΣΫτ 1 object MockObj { 2 fun add(a: Int, b:

    Int) = a + b 3 } 4 5 mockkObject(MockObj) 6 7 assertEquals(3, MockObj.add(1, 2)) 8 9 every { MockObj.add(1, 2) } returns 55 10 11 assertEquals(55, MockObj.add(1, 2)) !29
  30. γϯάϧτϯΦϒδΣΫτ 1 object MockObj { 2 fun add(a: Int, b:

    Int) = a + b 3 } 4 5 mockkObject(MockObj) 6 7 assertEquals(3, MockObj.add(1, 2)) 8 9 every { MockObj.add(1, 2) } returns 55 10 11 assertEquals(55, MockObj.add(1, 2)) !30
  31. γϯάϧτϯΦϒδΣΫτ 1 object MockObj { 2 fun add(a: Int, b:

    Int) = a + b 3 } 4 5 mockkObject(MockObj) 6 7 assertEquals(3, MockObj.add(1, 2)) 8 9 every { MockObj.add(1, 2) } returns 55 10 11 assertEquals(55, MockObj.add(1, 2)) !31
  32. 1 // ϞοΫର৅ͷ֦ுؔ਺(File.ktʹ࣮૷͞Ε͍ͯΔͱԾఆ) 2 fun File.extension() = absolutePath.split(“.”).last() 3 4

    // ύοέʔδ໊ʮpkg.exampleʯ 5 mockkStatic(“pkg.example.FileKt”) 6 7 every { File(“test.txt”).extension() } returns “txt" 8 9 assertEquals(“txt”, File(“test.txt").extension()) 10 11 verify { File(“test.txt”).extension() } ֦ுؔ਺ !32
  33. 1 // ϞοΫର৅ͷ֦ுؔ਺(File.ktʹ࣮૷͞Ε͍ͯΔͱԾఆ) 2 fun File.extension() = absolutePath.split(“.”).last() 3 4

    // ύοέʔδ໊ʮpkg.exampleʯ 5 mockkStatic(“pkg.example.FileKt”) 6 7 every { File(“test.txt”).extension() } returns “txt" 8 9 assertEquals(“txt”, File(“test.txt").extension()) 10 11 verify { File(“test.txt”).extension() } ֦ுؔ਺ NPDLL4UBUJDʹόΠτίʔυม׵ޙͷ ֦ுؔ਺ͷύεΛ౉͢ ˞5PPMT,PUMJO4IPX,PUMJO#ZUFDPEF !33
  34. 1 // ϞοΫର৅ͷ֦ுؔ਺(File.ktʹ࣮૷͞Ε͍ͯΔͱԾఆ) 2 fun File.extension() = absolutePath.split(“.”).last() 3 4

    // ύοέʔδ໊ʮpkg.exampleʯ 5 mockkStatic(“pkg.example.FileKt”) 6 7 every { File(“test.txt”).extension() } returns “txt" 8 9 assertEquals(“txt”, File(“test.txt").extension()) 10 11 verify { File(“test.txt”).extension() } ֦ுؔ਺ !34
  35. ͦͷଞʹ΋༷ʑͳϝιου͕༻ҙ ˠ.PDL,ͷ3&"%.& IUUQTNPDLLJP Λࢀর !35

  36. 4QFL w ςετϑϨʔϜϫʔΫ w 3VCZͷ34QFDͱࣅͨจ๏ w +6OJU্Ͱಈ࡞ w 6OJU5FTUͷΈରԠ w

    3PCPMFDUSJD΋ඇରԠ *TTVFʹ͸্͕͍ͬͯΔ  w Wਖ਼ࣜϦϦʔε ೔લ (JUIVCTQFLGSBNFXPSLTQFL ˞3PPNʙͷ ɹηογϣϯͰ΋঺հ W !36
  37. 4QFL֎؍ w ྫ +40/Λ%PNBJO0CKFDUʹม׵͢Δ֦ுؔ਺ͷ6OJU5FTU 1 // JSONΛද͢Ϋϥε 2 data class

    PersonJson(val name: String, val sex: String) 3 4 // Domain Object 5 interface Person 6 7 data class Male(val name: String): Person 8 9 data class Female(val name: String): Person 10 11 // ςετର৅ͷ֦ுؔ਺ 12 fun PersonJson.toPerson(): Person = … !37
  38. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } !38
  39. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } 4QFLΫϥεΛܧঝͨ͠γϯάϧτϯΦϒδΣΫτ಺ʹ ςετέʔεΛهड़ !39
  40. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } w1FSTPO+TPOUP1FSTPOϝιου͕ wTFYNBMFͷ࣌ wਖ਼͍͠1FSTPOΠϯελϯεΛฦ͔͢Ͳ͏͔ !40
  41. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } EFTDSJCFDPOUFYUΛೖΕࢠʹ૊Έ ςετର৅ɾ৚݅౳Λهड़͢Δ EFTDSJCFςετର৅ DPOUFYUςετ৚݅Λॻ͘͜ͱ͕ଟ͍ !41
  42. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } ֤EFTDSJCFDPOUFYUϒϩοΫ಺ͷςετ͕૸Δલʹ ࣮ߦ͍ͨ͠ॲཧΛCFGPSF&BDI5FTUʹهड़ !42
  43. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } JUϒϩοΫ಺ʹςετέʔεΛهड़ దٓΞαʔγϣϯϝιουΛ࢖ͬͯ஋ͷνΣοΫ !43
  44. 1 @RunWith(JUnitPlatform::class) 2 object EntryFormMapperSpec : Spek({ 3 describe("PersonJson#toPerson") {

    4 lateinit var json: PersonJson 5 6 beforeEachTest { 7 mockkStatic(“example.PersonJsonKt") 8 } 9 10 context("when sex is male”) { 11 beforeEachTest { 12 json = mockk<PersonJson>(“json1”).also { 13 every { it.toPerson() } returns mockk<Male>() 14 } 15 } 16 17 it("should return Male instance") { 18 assertTrue(json.toPerson() is Male) 19 } 20 } 21 … 22 afterEachTest { 23 unmockkStatic(“example.PersonJsonKt") 24 } 25 } 26 } ֤EFTDSJCFDPOUFYUϒϩοΫ಺ͷςετ͕૸ͬͨޙʹ ࣮ߦ͍ͨ͠ॲཧΛBGUFS&BDI5FTUʹهड़ !44
  45. +6OJUͱͷൺֱ w +6OJU w BOOPUBUJPOͰςετϝιουΛࢦఆϘΠϥʔϓϨʔτͷ૿Ճ w 4QFL w EFTDSJCFDPOUFYUJU࢓༷ॻͱͯ͠ಡΈ΍͍͢ɺৼΔ෣͍ۦಈ w

    BOOPUBUJPOෆཁ༨ܭͳهड़͕গͳ͘ɺ࡞ۀίετΛݮΒͤΔ w +6OJUલఏɺ3PCPMFDUSJDະରԠ྆ํͱ΋ղܾ͞ΕͨΒ˕ !45
  46. ,PUMJOϑϨϯυϦʔͳ 6OJU5FTU؀ڥͷߏங !46

  47. ؀ڥߏஙͷྲྀΕ  ϞοΫϥΠϒϥϦಋೖ .PDL,   ςετϑϨʔϜϫʔΫಋೖ 4QFL  

    ΧόϨοδܭଌϥΠϒϥϦಋೖ +B$P$P !47
  48. .PDL,ಋೖ w EFQFOEFODJFTʹߦ௥Ճɺ͜Ε͚ͩ 1 testImplementation “io.mockk:mockk:1.9” 2 3 // or

    4 5 androidTestImplementation “io.mockk:mockk:1.9” !48
  49. 4QFLಋೖ w ΍Δ΂͖͜ͱ͕͍͔ͭ͋͘Δ ϋϚΓϙΠϯτ  w "OESPJEϓϩδΣΫτ্Ͱ+6OJUΛಈ͔͢(SBEMF1MVHJOಋೖ w 4QFLͷςετΤϯδϯΛ(SBEMFʹࢦఆ w

    +6OJUͷςετͱͷڞଘઃఆ !49
  50. 4QFLಋೖ w BOESPJEKVOJUͷಋೖ w (JUIVCNBOOPEFSNBVTBOESPJEKVOJU buile.gradle 1 buildscript { 2

    dependencies { 3 classpath "de.mannodermaus.gradle.plugins:android-junit5:1.3.2.0" 4 } 5 } app/build.gradle 1 apply plugin: “de.mannodermaus.android-junit5” !50
  51. 4QFLಋೖ w EFQFOEFODJFTʹ4QFLͷґଘؔ܎௥Ճ w WWͰจ๏͕େ͖͘มΘ͍ͬͯΔ app/build.gradle 1 repositories { 2

    … 3 maven { url "https://dl.bintray.com/spekframework/spek-dev" } 4 } 5 6 dependencies { 7 testImplementation “org.spekframework.spek2:spek-dsl-jvm:2.0.0” 8 testImplementation “org.spekframework.spek2:spek-runner-junit5:2.0.0“ 9 // JUnit4ͷςετΛ࣮ߦ͢ΔͨΊʹඞཁ 10 testImplementation “junit:junit:4.12” 11 testImplementation “org.junit.vintage:junit-vintage-engine:5.2.0” 12 } !51
  52. 4QFLಋೖ w +6OJU1MBUGPSNʹʮTQFLʯʮKVOJUWJOUBHFʯΛ௥Ճ w KVOJUWJOUBHF+6OJU্Ͱ+6OJUͷςετΛ࣮ߦ͢ΔΤϯδϯ w 3PCPMFDUSJDΛ࢖͍͍ͨ৔߹ɺ+6OJUͰॻ͘ඞཁ͕͋ΔͨΊ 1 android {

    2 testOptions.junitPlatform { 3 filters { 4 engine.include “spek2”, “junit-vintage" 5 } 6 } 7 } !52
  53. 4QFLಋೖ ʮTQFLʯͱʮ+6OJU7JOUBHFʯͰςετ͕࣮ߦ͞ΕΔ w ʮTQFLʯͱʮ+6OJU7JOUBHFʯͰςετ͕࣮ߦ͞ΕΔ !53

  54. +B$P$Pಋೖ w +B$P$PίʔυΧόϨοδܭଌϥΠϒϥϦ w +BWB੡ w ίʔυΧόϨοδΛ9.-ɾ)5.-ܗࣜͰՄࢹԽ w .PDL,ɾ4QFLͲͪΒ΋ରԠ !54

  55. )5.-ग़ྗ !55

  56. $PEFDPWͱ࿈ܞ !56

  57. $PEFDPWͱ࿈ܞ !57

  58. +B$P$Pಋೖ w (SBEMFλεΫͷ࡞੒͕ඞཁ w ΧόϨοδϨϙʔτ࡞੒λεΫ w ΧόϨοδϨϙʔτूܭλεΫ w ϚϧνϞδϡʔϧϓϩδΣΫτͰ͸ඞਢ !58

  59. ΧόϨοδϨϙʔτ࡞੒ w ΍Δ͜ͱ w ΧόϨοδର৅ϑΝΠϧͷࢦఆ w ϨϙʔτϑΝΠϧͷग़ྗܗࣜͷࢦఆ w ϨϙʔτϑΝΠϧͷग़ྗઌͷࢦఆ w

    Ϩϙʔτ࡞੒Λ6OJU5FTUͷλεΫ͕ऴΘͬͨ௚ޙʹߦ͏ !59
  60. 1 apply plugin: “jacoco” 2 3 jacoco { 4 toolVersion

    = “0.8.1” 5 } 6 7 def excludeFiles = [ /* ूܭ͔Βআ֎͍ͨ͠ϑΝΠϧ໊ */ ] 8 9 task jacocoTestReports(type: JacocoReport, dependsOn: “testDebugUnitTest”) { 10 group = “Reporting” 11 description = “Generate Jacoco coverage reports for the build.” 12 13 classDirectories = files( 14 fileTree( 15 dir: “${buildDir}/tmp/kotlin-classes/debug”, 16 excludes: excludeFiles 17 ) 18 ) 19 sourceDirectories = files([“${buildDir}/src/main/kotlin”]) 20 21 reports { 22 xml.enabled true 23 html.enabled true 24 csv.enabled false 25 26 xml.destination file(“${buildDir}/reports/jacoco/report.xml”) 27 html.destination file(“${buildDir}/reports/jacoco/html”) 28 } 29 } !60
  61. 9 task jacocoTestReports(type: JacocoReport, dependsOn: “testDebugUnitTest”) { 10 group =

    “Reporting” 11 description = “Generate Jacoco coverage reports for the build.” 12 13 classDirectories = files( 14 fileTree( 15 dir: “${buildDir}/tmp/kotlin-classes/debug”, 16 excludes: excludeFiles 17 ) 18 ) 19 sourceDirectories = files([“${buildDir}/src/main/kotlin”]) 20 21 reports { 22 xml.enabled true 23 html.enabled true 24 csv.enabled false 25 26 xml.destination file(“${buildDir}/reports/jacoco/report.xml”) 27 html.destination file(“${buildDir}/reports/jacoco/html”) 28 } 29 } UZQFΛʮ+BDPDP3FQPSUʯʹࢦఆ͠ɺλεΫΛએݴ EFQFOET0OʹʮUFTU%FCVH6OJU5FTUʯΛࢦఆ͠ɺ 6OJU5FTUλεΫͷޙʹϨϙʔτ࡞੒͕࣮ߦ͞ΕΔΑ͏ʹ͢Δ !61
  62. 9 task jacocoTestReports(type: JacocoReport, dependsOn: “testDebugUnitTest”) { 10 group =

    “Reporting” 11 description = “Generate Jacoco coverage reports for the build.” 12 13 classDirectories = files( 14 fileTree( 15 dir: “${buildDir}/tmp/kotlin-classes/debug”, 16 excludes: excludeFiles 17 ) 18 ) 19 sourceDirectories = files([“${buildDir}/src/main/kotlin”]) 20 21 reports { 22 xml.enabled true 23 html.enabled true 24 csv.enabled false 25 26 xml.destination file(“${buildDir}/reports/jacoco/report.xml”) 27 html.destination file(“${buildDir}/reports/jacoco/html”) 28 } 29 } ΧόϨοδର৅ͷϑΝΠϧɾΫϥεΛࢦఆ ˞আ֎͢ΔϑΝΠϧͷྫ  3DMBTT #VJME$POpH   5FTU    %BHHFS $PNQPOFOU    .PEVMF !62
  63. 9 task jacocoTestReports(type: JacocoReport, dependsOn: “testDebugUnitTest”) { 10 group =

    “Reporting” 11 description = “Generate Jacoco coverage reports for the build.” 12 13 classDirectories = files( 14 fileTree( 15 dir: “${buildDir}/tmp/kotlin-classes/debug”, 16 excludes: excludeFiles 17 ) 18 ) 19 sourceDirectories = files([“${buildDir}/src/main/kotlin”]) 20 21 reports { 22 xml.enabled true 23 html.enabled true 24 csv.enabled false 25 26 xml.destination file(“${buildDir}/reports/jacoco/report.xml”) 27 html.destination file(“${buildDir}/reports/jacoco/html”) 28 } 29 } Ϩϙʔτͷग़ྗܗࣜɾग़ྗઌͷࢦఆ !63
  64. ΧόϨοδϨϙʔτूܭ w ϞδϡʔϧຖʹΧόϨοδܭଌ͕࣮ߦ͞ΕΔ w ܭଌ݁ՌͷϑΝΠϧ͕෼ࢄͯ͠͠·͏ˠूܭλεΫͰ·ͱΊΔ w ΍Δ͜ͱ w ΧόϨοδܭଌΛ6OJU5FTUޙʹ࣮ߦ͢ΔΑ͏ࢦఆ Ϟδϡʔϧຖ

     w ֤ϞδϡʔϧͷΧόϨοδର৅ϑΝΠϧɺΧόϨοδܭଌ݁ՌΛ·ͱΊɺ Ϩϙʔτ࡞੒λεΫʹ౉͢ !64
  65. 1 task jacocoMergeReports(type: JacocoMerge) { 2 group = "Reporting" 3

    description = "Merge all JaCoCo reports from projects into one." 4 5 gradle.afterProject { p, state -> 6 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 7 dependsOn "${p.path}:testDebugUnitTest" 8 executionData "${p.buildDir}/jacoco/testDebugUnitTest.exec" 9 } 10 } 11 } !65
  66. 1 task jacocoMergeReports(type: JacocoMerge) { 2 group = "Reporting" 3

    description = "Merge all JaCoCo reports from projects into one." 4 5 gradle.afterProject { p, state -> 6 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 7 dependsOn "${p.path}:testDebugUnitTest" 8 executionData "${p.buildDir}/jacoco/testDebugUnitTest.exec" 9 } 10 } 11 } UZQFΛʮ+BDPDP.FSHFʯʹࢦఆ͠ɺλεΫΛએݴ !66
  67. 1 task jacocoMergeReports(type: JacocoMerge) { 2 group = "Reporting" 3

    description = "Merge all JaCoCo reports from projects into one." 4 5 gradle.afterProject { p, state -> 6 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 7 dependsOn "${p.path}:testDebugUnitTest" 8 executionData "${p.buildDir}/jacoco/testDebugUnitTest.exec" 9 } 10 } 11 } ֤Ϟδϡʔϧͷ6OJU5FTUλεΫऴྃޙʹ ϨϙʔτܭଌλεΫ͕࣮ߦ͞ΕΔΑ͏ʹࢦఆ !67
  68. 1 task jacocoMergeReports(type: JacocoMerge) { 2 group = "Reporting" 3

    description = "Merge all JaCoCo reports from projects into one." 4 5 gradle.afterProject { p, state -> 6 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 7 dependsOn "${p.path}:testDebugUnitTest" 8 executionData "${p.buildDir}/jacoco/testDebugUnitTest.exec" 9 } 10 } 11 } ֤ϞδϡʔϧͷΧόϨοδܭଌ݁ՌͷύεΛ౉͢ ˠ౉ͨ͠ܭଌ݁Ռ͕࠷ޙʹϚʔδ͞ΕΔ !68
  69. 1 task jacocoMergeReports(type: JacocoMerge) { 2 group = "Reporting" 3

    description = "Merge all JaCoCo reports from projects into one." 4 5 gradle.afterProject { p, state -> 6 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 7 dependsOn "${p.path}:testDebugUnitTest" 8 executionData "${p.buildDir}/jacoco/testDebugUnitTest.exec" 9 } 10 } 11 } ʮBQQMZQMVHJOKBDPDPʯ͕༗ޮͳϞδϡʔϧͷΈ࣮ߦ !69
  70. 1 task jacocoTestReports(type: JacocoReport, dependsOn: "jacocoMergeReports") { 2 … 3

    4 // [before] 5 // classDirectories = … 6 // sourceDirectories = … 7 8 executionData jacocoMergeReports.destinationFile 9 10 sourceDirectories = files() 11 classDirectories = files() 12 13 gradle.afterProject { p, state -> 14 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 15 classDirectories = classDirectories + files([ 16 fileTree( 17 dir: “${p.buildDir}/tmp/kotlin-classes/release", 18 excludes: excludeFiles 19 ) 20 ]) 21 sourceDirectories = sourceDirectories + files([ 22 "${p.buildDir}/src/main/kotlin" 23 ]) 24 } 25 26 } 27 28 … 29 } Ұ෦ൈਮ λεΫϑΝΠϧશମ͸IUUQCJUMZ)DE(9+ !70
  71. 1 task jacocoTestReports(type: JacocoReport, dependsOn: "jacocoMergeReports") { 2 … 3

    4 // [before] 5 // classDirectories = … 6 // sourceDirectories = … 7 8 executionData jacocoMergeReports.destinationFile 9 10 sourceDirectories = files() 11 classDirectories = files() 12 13 gradle.afterProject { p, state -> 14 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 15 classDirectories = classDirectories + files([ 16 fileTree( 17 dir: “${p.buildDir}/tmp/kotlin-classes/release", 18 excludes: excludeFiles 19 ) 20 ]) 21 sourceDirectories = sourceDirectories + files([ 22 "${p.buildDir}/src/main/kotlin" ΧόϨοδूܭλεΫͷ௚ޙʹϨϙʔτ࡞੒͕ ࣮ߦ͞ΕΔΑ͏ʹ͢Δ !71
  72. ΧόϨοδूܭλεΫͷ௚ޙʹϨϙʔτ࡞੒͕ ࣮ߦ͞ΕΔΑ͏ʹ͢Δ 5 // classDirectories = … 6 // sourceDirectories

    = … 7 8 executionData jacocoMergeReports.destinationFile 9 10 sourceDirectories = files() 11 classDirectories = files() 12 13 gradle.afterProject { p, state -> 14 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 15 classDirectories = classDirectories + files([ 16 fileTree( 17 dir: “${p.buildDir}/tmp/kotlin-classes/release", 18 excludes: excludeFiles 19 ) 20 ]) 21 sourceDirectories = sourceDirectories + files([ 22 "${p.buildDir}/src/main/kotlin" 23 ]) 24 } 25 26 } 27 ΧόϨοδूܭλεΫʹ౉͞Ε֤ͨϞδϡʔϧͷ ܭଌ݁ՌͷύεΛ౉͢ !72
  73. ΧόϨοδूܭλεΫͷ௚ޙʹϨϙʔτ࡞੒͕ ࣮ߦ͞ΕΔΑ͏ʹ͢Δ 5 // classDirectories = … 6 // sourceDirectories

    = … 7 8 executionData jacocoMergeReports.destinationFile 9 10 sourceDirectories = files() 11 classDirectories = files() 12 13 gradle.afterProject { p, state -> 14 if (p.rootProject != p && p.plugins.hasPlugin("jacoco")) { 15 classDirectories = classDirectories + files([ 16 fileTree( 17 dir: “${p.buildDir}/tmp/kotlin-classes/release", 18 excludes: excludeFiles 19 ) 20 ]) 21 sourceDirectories = sourceDirectories + files([ 22 "${p.buildDir}/src/main/kotlin" 23 ]) 24 } 25 26 } 27 ֤Ϟδϡʔϧʹ෼ࢄ͍ͯ͠Δ ΧόϨοδର৅ΫϥεɾϑΝΠϧΛࢦఆ !73
  74. λεΫ࣮ߦ ΧόϨοδϨϙʔτ͕ग़ྗ )5.-ϑΝΠϧͰ֬ೝ 9.-ϑΝΠϧΛ֤छΧόϨοδऔಘαʔϏεʹQPTU $ ./gradlew jacocoTestReport ؀ڥߏஙऴྃʂ͓ർΕ༷Ͱ͢ʂ !74

  75. ·ͱΊ w .PDL, w ,PUMJOͰඞཁͱ͞ΕΔϞοΫ༻ϝιουΛ޿͘Χόʔ w ಋೖ΋؆୯ɺطଘͷςετίʔυͷதͰ΋ؾܰʹѻ͑Δ w 4QFL w

    ༨ܭͳهड़͕ݮΓɺ࢓༷ॻͱͯ͠΋ݟ΍͍͢ܗͰॻ͚Δ w 3PCPMFDUSJDରԠɺ"OESPJEϓϩδΣΫτͰͷ+6OJUରԠ͕଴ͨΕΔ !75
  76. ·ͱΊ ,PUMJOϑϨϯυϦʔͳ6OJU5FTU؀ڥͰ ܧଓੑͷߴ͍ςετΛ࣮ݱ͠·͠ΐ͏ʂ !76

  77. એ఻ גࣜձࣾ#FBS5BJMɹΤϯδχΞืूதʂ ৬छ w "OESPJEΤϯδχΞ ,PUMJO+BWB  w J04ΤϯδχΞ 4XJGU0CKFDUJWF$

     w 8FCΤϯδχΞ 3VCZPO3BJMT3FBDU3FEVY  ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠N @@ N ձࣾ঺հࢿྉIUUQCJUMZ".V"* !77