Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!
Search
subroh_0508
February 08, 2019
Technology
9
5.3k
Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!
DroidKaigi 2019 - Day2 Room1 11:20〜
「Spek2+MockK+JaCoCoでイケてる Unit Test環境を手に入れろ!」のセッション資料です
subroh_0508
February 08, 2019
Tweet
Share
More Decks by subroh_0508
See All by subroh_0508
Compose MultiplatformでもHot Reloadが動くらしい
subroh0508
1
150
Compose for Webでポートフォリオサイトを作る
subroh0508
1
210
あらゆるアプリをCompose Multiplatformで書きたい! -ネイティブアプリの「あの機能」を私たちはどう作るか-
subroh0508
1
2.8k
登壇の心理的ハードルをコントロールする技術
subroh0508
2
570
テストコードを書きながらCompose Multiplatformを乗りこなす
subroh0508
0
990
自己効力感を二次元アイドル作品から得ながら社会人としての成長を超加速させる
subroh0508
2
740
担当アイドルを応援する傘を作ろう! (として失敗した話)
subroh0508
0
620
buildSrc/Composite Buildで必要なバージョン情報も Version Catalogから参照したい!
subroh0508
0
1.6k
フロントエンドもJetpack Composeで書きたい! -Compose for WebはモダンWebアプリケーションの夢を見るか?-
subroh0508
0
960
Other Decks in Technology
See All in Technology
Стильный код: натуральный поиск редких атрибутов по картинке. Юлия Антохина, Data Scientist, Lamoda Tech
lamodatech
0
650
IVRyにおけるNLP活用と NLP2025の関連論文紹介
keisukeosone
0
190
サーバレス、コンテナ、データベース特化型機能をご紹介。CloudWatch をもっと使いこなそう!
o11yfes2023
0
130
AIと開発者の共創: エージェント時代におけるAIフレンドリーなDevOpsの実践
bicstone
1
290
DETR手法の変遷と最新動向(CVPR2025)
tenten0727
2
1.3k
Spice up your notifications/try!Swift25
noppefoxwolf
2
350
大AI時代で輝くために今こそドメインにディープダイブしよう / Deep Dive into Domain in AI-Agent-Era
yuitosato
1
320
Ops-JAWS_Organizations小ネタ3選.pdf
chunkof
2
130
ElixirがHW化され、最新CPU/GPU/NWを過去のものとする数万倍、高速+超省電力化されたWeb/動画配信/AIが動く日
piacerex
0
130
クォータ監視、AWS Organizations環境でも楽勝です✌️
iwamot
PRO
1
270
AI AgentOps LT大会(2025/04/16) Algomatic伊藤発表資料
kosukeito
0
130
CBになったのでEKSのこともっと知ってもらいたい!
daitak
1
160
Featured
See All Featured
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
5
520
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
129
19k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Why Our Code Smells
bkeepers
PRO
336
57k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
13
1.4k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.5k
RailsConf 2023
tenderlove
30
1.1k
StorybookのUI Testing Handbookを読んだ
zakiyama
29
5.6k
Six Lessons from altMBA
skipperchong
27
3.7k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
3.8k
Stop Working from a Prison Cell
hatefulcrawdad
268
20k
Transcript
4QFL .PDL, +B$P$PͰΠέͯΔ 6OJU5FTUڥΛखʹೖΕΖʂ %SPJE,BJHJ%BZ3PPNʙ ʹ͜͠Γ͞ͿΖʙ!TVCSPI@ !1
ࣗݾհ w ʹ͜͠Γ͞ͿΖʙ ຊ໊ࡔ্৴ w ౦ژɾҏ౾େౡग़ w גࣜձࣾ#FBS5BJM w
"OESPJEΤϯδχΞ ,PUMJO+BWB w 8FCΤϯδχΞ 3BJMT3FBDU !2
ࣄ w %S8BMMFU ,PUMJOҠߦ࡞ۀதʜ !3
ࣄ w %Sܦඅਫ਼ࢉ ϑϧ,PUMJOʂ .PDL,4QFL ಋೖࡁΈʂ !4
࣍ ͜Ε·Ͱͷ6OJU5FTU ,PUMJOϑϨʔϜϫʔΫϥΠϒϥϦͷհ w .PDL,ϞοΫϥΠϒϥϦ w 4QFLςετϑϨʔϜϫʔΫ
,PUMJOϑϨϯυϦʔͳ6OJU5FTUڥͷߏங w .PDL,ɾ4QFLͷಋೖ w +B$P$PΛͬͨίʔυΧόϨοδࢉग़ !5
ର w ॳΊͯ"OESPJE։ൃͰ6OJU5FTUΛॻ͜͏ͱ͍ͯ͠Δਓ w +BWBϥΠϒϥϦͰͷςετʹർΕͨਓ !6
͜Ε·Ͱͷ6OJU5FTU !7
,PUMJOਖ਼ࣜαϙʔτલ w .PDLJUPϞοΫϥΠϒϥϦ w WSFMFBTFʙࠒ w ཉ͍͠ػೳେମἧ͍ͬͯΔ w ใ͕๛ w
,PUMJO༻ϥούʔϥΠϒϥϦΞϦ w .PDLJUP,PUMJO ݄ʙ !8
w +6OJUςετϑϨʔϜϫʔΫ w "OESPJEϓϩδΣΫτͰͷσϑΥϧτͷϑϨʔϜϫʔΫ w BOOPUBUJPOͰςετϝιουΛࢦఆ ,PUMJOਖ਼ࣜαϙʔτલ !9
,PUMJOਖ਼ࣜαϙʔτલ w ϞοΫϥΠϒϥϦˠ.PDLJUP w ϢχοτςετϑϨʔϜϫʔΫˠ+6OJU w ͲͪΒ+BWB w ,PUMJOରԠ͕ෆे w
,PUMJOͷݴޠ༷Λ׆͔͖͠Εͳ͍ !10
,PUMJOਖ਼ࣜαϙʔτ !(PPHMF*0 !11
ޙʜ w ,PUMJOϥΠϒϥϦϑϨʔϜϫʔΫ w ,PUMJOͷݴޠ༷Λϑϧʹ׆͔ͤΔʂ w ʮ+BWB͔Βݟͨ࣌͜ͷίʔυͲ͏ͳΔΜ͚ͩͬʯˠগͳ͘ͳΔ w ςετॻ࣌͘ʹߟ͑Δ͖͜ͱ͕ݮΔˠܧଓੑͷ͋Δςετ w
҆৺ͯ͑͠Δʂ w ใ͕૿͖͑ͯͨʂ ,PUMJOϥΠϒϥϦϑϨʔϜϫʔΫ ಋೖͷػʂ !12
,PUMJO ϑϨʔϜϫʔΫϥΠϒϥϦͷհ !13
.PDL, w ϞοΫϥΠϒϥϦ w ,PUMJOಠࣗͷݴޠ༷Λཏ w 6OJU5FTU*OTUSVNFOUFE5FTU྆ରԠ w %SPJE,BJHJެࣜΞϓϦͰར༻ (JUIVCNPDLLNPDLL
!14
Ͱ͖Δ͜ͱ w ελϒ w ϝιουͷฦΓͷࠩ͠ସ͑ w ϞοΫ w ϝιουͷҾɺݺΕͨճɺฦΓͷݕূ w
ϝιουͷॲཧಈ࡞͠ͳ͍ w εύΠ w ϝιουͷҾɺݺΕͨճɺฦΓͷݕূ w ϝιουͷॲཧ͕࣮ࡍʹಈ࡞͢Δ ৄ͘͠ "OESPJEςετશॻͰʜ !15
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
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
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
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
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
TQZL5ϝιου 1 val car = spyk(Car()) 2 3 car.drive(Direction.NORTH) 4
5 verify { car.drive(Direction.NORTH) } 6 7 confirmVerified(car) !21
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
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
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
ϞοΫεύΠϚονϟʔ.PDLJUPʹ͋ΔΑʁʁʁ !25
.PDLJUPͷ w 'JOBMͳΫϥεϝιου͕ϞοΫͰ͖ͳ͍ʂ w ,PUMJOͷΫϥεϝιουσϑΥϧτͰ'JOBM w ճආࡦLPUMJOBMMPQFOϓϥάΠϯ CZ+FU#SBJOT
w BOOPUBUJPO͚ͭΔͱPQFOʹͯ͘͠ΕΔɹ ڧҾͩͳʜ w ςετͷͨΊʹຊ൪ίʔυͷ༨ܭͳهड़͕૿͑Δ !26
.PDLJUPͷ w 4UBUJDͳϝιου͕ϞοΫͰ͖ͳ͍ʂ w ֦ுؔɹγϯάϧτϯΦϒδΣΫτ w ճආࡦ1PXFS.PDL w 4UBUJDϝιουɺQSJWBUFϝιουͷϞοΫ͕Ͱ͖Δ
w Θ͟Θ͟ผϥΠϒϥϦΛಋೖ͢Δ͜ͱ͕ඍົ !27
.PDL,ͷڧΈ w ,PUMJOͷΫϥεϝιουΛ΄΅ԿͰϞοΫͰ͖ͯ͠·͏ʂ w 'JOBMͳΫϥεϝιου w γϯάϧτϯΦϒδΣΫτ w ֦ுؔ w
QSJWBUFϝιου w TVTQFOEϝιουFUD !28
γϯάϧτϯΦϒδΣΫτ 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
γϯάϧτϯΦϒδΣΫτ 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
γϯάϧτϯΦϒδΣΫτ 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
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
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
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
ͦͷଞʹ༷ʑͳϝιου͕༻ҙ ˠ.PDL,ͷ3&"%.& IUUQTNPDLLJP Λࢀর !35
4QFL w ςετϑϨʔϜϫʔΫ w 3VCZͷ34QFDͱࣅͨจ๏ w +6OJU্Ͱಈ࡞ w 6OJU5FTUͷΈରԠ w
3PCPMFDUSJDඇରԠ *TTVFʹ্͕͍ͬͯΔ w Wਖ਼ࣜϦϦʔε લ (JUIVCTQFLGSBNFXPSLTQFL ˞3PPNʙͷ ɹηογϣϯͰհ W !36
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
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
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
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
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
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
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
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
+6OJUͱͷൺֱ w +6OJU w BOOPUBUJPOͰςετϝιουΛࢦఆϘΠϥʔϓϨʔτͷ૿Ճ w 4QFL w EFTDSJCFDPOUFYUJU༷ॻͱͯ͠ಡΈ͍͢ɺৼΔ͍ۦಈ w
BOOPUBUJPOෆཁ༨ܭͳهड़͕গͳ͘ɺ࡞ۀίετΛݮΒͤΔ w +6OJUલఏɺ3PCPMFDUSJDະରԠ྆ํͱղܾ͞ΕͨΒ˕ !45
,PUMJOϑϨϯυϦʔͳ 6OJU5FTUڥͷߏங !46
ڥߏஙͷྲྀΕ ϞοΫϥΠϒϥϦಋೖ .PDL, ςετϑϨʔϜϫʔΫಋೖ 4QFL
ΧόϨοδܭଌϥΠϒϥϦಋೖ +B$P$P !47
.PDL,ಋೖ w EFQFOEFODJFTʹߦՃɺ͜Ε͚ͩ 1 testImplementation “io.mockk:mockk:1.9” 2 3 // or
4 5 androidTestImplementation “io.mockk:mockk:1.9” !48
4QFLಋೖ w Δ͖͜ͱ͕͍͔ͭ͋͘Δ ϋϚΓϙΠϯτ w "OESPJEϓϩδΣΫτ্Ͱ+6OJUΛಈ͔͢(SBEMF1MVHJOಋೖ w 4QFLͷςετΤϯδϯΛ(SBEMFʹࢦఆ w
+6OJUͷςετͱͷڞଘઃఆ !49
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
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
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
4QFLಋೖ ʮTQFLʯͱʮ+6OJU7JOUBHFʯͰςετ͕࣮ߦ͞ΕΔ w ʮTQFLʯͱʮ+6OJU7JOUBHFʯͰςετ͕࣮ߦ͞ΕΔ !53
+B$P$Pಋೖ w +B$P$PίʔυΧόϨοδܭଌϥΠϒϥϦ w +BWB w ίʔυΧόϨοδΛ9.-ɾ)5.-ܗࣜͰՄࢹԽ w .PDL,ɾ4QFLͲͪΒରԠ !54
)5.-ग़ྗ !55
$PEFDPWͱ࿈ܞ !56
$PEFDPWͱ࿈ܞ !57
+B$P$Pಋೖ w (SBEMFλεΫͷ࡞͕ඞཁ w ΧόϨοδϨϙʔτ࡞λεΫ w ΧόϨοδϨϙʔτूܭλεΫ w ϚϧνϞδϡʔϧϓϩδΣΫτͰඞਢ !58
ΧόϨοδϨϙʔτ࡞ w Δ͜ͱ w ΧόϨοδରϑΝΠϧͷࢦఆ w ϨϙʔτϑΝΠϧͷग़ྗܗࣜͷࢦఆ w ϨϙʔτϑΝΠϧͷग़ྗઌͷࢦఆ w
Ϩϙʔτ࡞Λ6OJU5FTUͷλεΫ͕ऴΘͬͨޙʹߦ͏ !59
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
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
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
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
ΧόϨοδϨϙʔτूܭ w ϞδϡʔϧຖʹΧόϨοδܭଌ͕࣮ߦ͞ΕΔ w ܭଌ݁ՌͷϑΝΠϧ͕ࢄͯ͠͠·͏ˠूܭλεΫͰ·ͱΊΔ w Δ͜ͱ w ΧόϨοδܭଌΛ6OJU5FTUޙʹ࣮ߦ͢ΔΑ͏ࢦఆ Ϟδϡʔϧຖ
w ֤ϞδϡʔϧͷΧόϨοδରϑΝΠϧɺΧόϨοδܭଌ݁ՌΛ·ͱΊɺ Ϩϙʔτ࡞λεΫʹ͢ !64
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
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
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
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
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
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
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
ΧόϨοδूܭλεΫͷޙʹϨϙʔτ࡞͕ ࣮ߦ͞ΕΔΑ͏ʹ͢Δ 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
ΧόϨοδूܭλεΫͷޙʹϨϙʔτ࡞͕ ࣮ߦ͞ΕΔΑ͏ʹ͢Δ 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
λεΫ࣮ߦ ΧόϨοδϨϙʔτ͕ग़ྗ )5.-ϑΝΠϧͰ֬ೝ 9.-ϑΝΠϧΛ֤छΧόϨοδऔಘαʔϏεʹQPTU $ ./gradlew jacocoTestReport ڥߏஙऴྃʂ͓ർΕ༷Ͱ͢ʂ !74
·ͱΊ w .PDL, w ,PUMJOͰඞཁͱ͞ΕΔϞοΫ༻ϝιουΛ͘Χόʔ w ಋೖ؆୯ɺطଘͷςετίʔυͷதͰؾܰʹѻ͑Δ w 4QFL w
༨ܭͳهड़͕ݮΓɺ༷ॻͱͯ͠ݟ͍͢ܗͰॻ͚Δ w 3PCPMFDUSJDରԠɺ"OESPJEϓϩδΣΫτͰͷ+6OJUରԠ͕ͨΕΔ !75
·ͱΊ ,PUMJOϑϨϯυϦʔͳ6OJU5FTUڥͰ ܧଓੑͷߴ͍ςετΛ࣮ݱ͠·͠ΐ͏ʂ !76
એ גࣜձࣾ#FBS5BJMɹΤϯδχΞืूதʂ ৬छ w "OESPJEΤϯδχΞ ,PUMJO+BWB w J04ΤϯδχΞ 4XJGU0CKFDUJWF$
w 8FCΤϯδχΞ 3VCZPO3BJMT3FBDU3FEVY ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠N @@ N ձࣾհࢿྉIUUQCJUMZ".V"* !77