Slide 1

Slide 1 text

iOSSnapshotTestCase ͰϏϡʔͷςετΛߦ͏

Slide 2

Slide 2 text

ࣗݾ঺հ • ۄ৓৴ޛ • iOS Engineer • iOSDC2018Ͱελοϑ͠·͢

Slide 3

Slide 3 text

iOSSnapshotTestCaseͱ͸ View based testingΛߦ͏ͨΊͷπʔϧ ݩFBSnapshotTestCaseͰɺ΋ͱ΋ͱ͸Facebook੡ Ϗϡʔͷը૾Λ෇͖߹ΘͤͯมߋҰக͢Δ͔Λςετ͢Δػೳ ͕͋Γ·͢ɻ

Slide 4

Slide 4 text

ḪΔࣄ໿Ұ೥લ

Slide 5

Slide 5 text

ḪΔࣄ໿Ұ೥લ

Slide 6

Slide 6 text

ผͷखஈΛݕ౼ͯ͠ΈͨΓ WiremockΛ࢖ͬͨUIςετ https://speakerdeck.com/tamaki/wiremockdexing-uuitesuto

Slide 7

Slide 7 text

σϝϦοτ΋͋Γ·ͨ͠ • XCUITestͷ࣮ߦ࣌ؒ • XCUITestͰը໘ͷঢ়ଶΛ࡞Δखؒ

Slide 8

Slide 8 text

ͱࢥͬͨΒ...

Slide 9

Slide 9 text

iOSSnapshotTestCaseͱͯ͠෮׆ Uber͕Ҿ͖ܧ͙ܗͰ෮׆ https://github.com/uber/ios-snapshot-test-case

Slide 10

Slide 10 text

࠶ͼ࢖͍࢝ΊΔࣄʹ͠·ͨ͠ɻ

Slide 11

Slide 11 text

iOSSnapshotTestCase ͷ࢖͍ํ

Slide 12

Slide 12 text

Πϯετʔϧ target "Tests" do use_frameworks! pod 'iOSSnapshotTestCase' end

Slide 13

Slide 13 text

ઃఆ • FBREFERENCEIMAGEDIR: $(SOURCEROOT)/$ (PROJECT_NAME)Tests/ReferenceImages • IMAGEDIFFDIR: $(SOURCEROOT)/$(PROJECTNAME)Tests/ FailureDiffs

Slide 14

Slide 14 text

ςετ࡞੒ FBSnapshotTestCaseΛܧঝͨ͠ςετέʔεΛ࡞੒͠·͢ɻ class iOSSnapshotTestCaseSampleTests: FBSnapshotTestCase { }

Slide 15

Slide 15 text

ςετϝιου࡞੒ FBSnapshotVerifyViewͰର৅ͷϏϡʔΛνΣοΫ͠·͢ɻ ର৅ͷϏϡʔͷαΠζ͸CGRect(x:y:width:height)ͳͲͰࢦఆ͠ ͓͖ͯ·͠ΐ͏ɻ view.frame = CGRect(x: 0, y: 0, width: 768, height: 1024) FBSnapshotVerifyView(view, identifier: "view id")

Slide 16

Slide 16 text

ϦϑΝϨϯεը૾ͷ࡞੒ recordMode͕trueͷͰςετΛ࣮ߦ͢ΔͱϦϑΝϨϯε༻ը૾ ͕ੜ੒͞Ε·͢ɻ override func setUp() { super.setUp() recordMode = true }

Slide 17

Slide 17 text

ϦϑΝϨϯεը૾ ReferenceImages_64ͷ഑Լʹςετλʔήοτ໊.ςετέʔ ε໊ͷσΟϨΫτϦͱɺ ͦͷԼʹ෇͖߹ΘͤʹඞཁͱͳΔࢀর༻ը૾͕࡞੒͞Ε·͢ɻ iOSSnapshotTestCaseSampleTests/ ├── Info.plist ├── ReferenceImages_64 │ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests │ └── [email protected] └── TopScreenSnapshotTests.swift

Slide 18

Slide 18 text

ϦϑΝϨϯεը૾

Slide 19

Slide 19 text

ςετͷ࣮ߦ recordModeΛfalseʹ͢ΔࣄΛ๨Εͳ͍Α͏ʹɻ override func setUp() { super.setUp() recordMode = false }

Slide 20

Slide 20 text

ςετ݁Ռ

Slide 21

Slide 21 text

ը໘͕มߋ͠ςετΛ࣮ߦ͢Δͱ... ૂ͍௨Γςετ͸ࣦഊ͠·ͨ͠Ͷʁ

Slide 22

Slide 22 text

FailureDiffsΛݟͯΈΑ͏ ├── FailureDiffs │ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests │ ├── [email protected] │ ├── [email protected] │ └── [email protected] ├── Info.plist ├── ReferenceImages_64 │ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests │ └── [email protected] └── TopScreenSnapshotTests.swift

Slide 23

Slide 23 text

FailureDiffsͷத਎ FailureDiffsσΟϨΫτϦ͕ੜ੒͞Εɺ3ͭͷϑΝΠϧ͕ೖͬͯ· ͢ file name description diff_*.png Ϗϡʔͷ෇͖߹Θͤࠩ෼ failed_*.png ݱࡏͷϏϡʔ reference_*.png ࢀর༻ͷϏϡʔ(͋Δ΂͖Ϗϡʔͷը૾)

Slide 24

Slide 24 text

FailureDiffsͷத਎

Slide 25

Slide 25 text

ࢀর༻ͷը૾ͷ࠶ੜ੒ ઌ΄Ͳͷςετ͸Ϗϡʔʹࠩ෼͕͋Γςετ͸ࣦഊ͠·ͨ͠ ͕ɺ૝ఆͨ͠௨Γͳ༁Ͱ͔͢Βɺ࠶౓ࢀর༻ͷը૾Λొ࿥͠௚ ͠·͢ɻ

Slide 26

Slide 26 text

جຊతͳ࢖͍ํ(·ͱΊ) ͜ͷΑ͏ͳ࡞ۀΛ܁Γฦ͢ࣄͰҎԼͷΑ͏ͳԸܙΛड͚Δ͜ͱ ͕Ͱ͖·͢ɻ • ҙਤ͠ͳ͍ը໘่ΕΛݕ஌ग़དྷΔ • PR࣌ʹϦϑΝϨϯεը૾ؚ͕·ΕΔࣄͰը໘ͷ֬ೝָ͕ʹͳ Δ

Slide 27

Slide 27 text

Tips

Slide 28

Slide 28 text

ෳ਺αΠζ΁ͷରԠ ϏϡʔͷαΠζ΍ॎԣͷରԠͳͲ΋ͨ͘͠ͳΓ·͢ΑͶʁ ྫ͑͹ྻڍܕͰσόΠεαΠζΛఆٛ enum DeviceSize: String { case size_10_5_inch = "10.5inch" case size_12_9_inch = "12.9inch" static func all() -> [DeviceSize] { return [ .size_10_5_inch, .size_12_9_inch, ] }

Slide 29

Slide 29 text

ෳ਺αΠζ΁ͷରԠ ޲͖ຖͷ֤σόΠεͷαΠζ(Rect)Λฦ͢ϝιουΛੜ΍ͨ͠Γ ͢ΔͱָʹͳΓͦ͏Ͱ͢ɻ func frame(with orientation: UIInterfaceOrientation) -> CGRect { switch (self, orientation) { case (.size_10_5_inch, .portrait): return CGRect(x: 0, y: 0, width: 834, height: 1112) case (.size_10_5_inch, .landscapeLeft): return CGRect(x: 0, y: 0, width: 1112, height: 834) case (.size_12_9_inch, .portrait): return CGRect(x: 0, y: 0, width: 1024, height: 1366) case (.size_12_9_inch, .landscapeLeft): return CGRect(x: 0, y: 0, width: 1366, height: 1024) default: return CGRect.zero } } }

Slide 30

Slide 30 text

ෳ਺αΠζ΁ͷରԠ FBSnapshotVerifyView͸جຊతʹ1ϏϡʔͣͭVerify͍ͯ͘͠ͷ Ͱ͕͢ɺSnapshotVerifyViewAllSizeΛ࡞ͬͯෳ਺αΠζΛҰ ؾʹςετग़དྷΔΑ͏ʹͨ͠Γ͍ͯ͠·͢ɻ extension FBSnapshotTestCase { public func SnapshotVerifyViewAllSize(_ vc: UIViewController, identifier: String = "", orientation: UIInterfaceOrientation) { for size in DeviceSize.all() { XCTContext.runActivity(named: "\(identifier)_\(size.rawValue)", block: { activity in let view = vc.view! view.frame = size.frame(with: orientation) FBSnapshotVerifyView(view, identifier: activity.name) }) } } }

Slide 31

Slide 31 text

Ϗϡʔͷঢ়ଶ࡞Γʹ͍ͭͯ • XCUIDevice.shared.orientationΛsetupͰ੍ޚग़དྷΔʁ • ࣦഊ͢Δ͜ͱ͕͋ͬͨ(or ग़དྷͳ͔ͬͨ) ΋͸΍ هԱʹͳ ͍ • ࣮ߦ଎౓͸΍͸Γ஗͘ͳͬͨ • UIςετͷ࢓૊ΈΛ࢖͏ࣄʹҧ࿨ײΛײͨ͡ • ૉ௚ʹΠχγϟϥΠβͰDI͢Δࣄʹͨ͠

Slide 32

Slide 32 text

ɹϨϏϡʔ࣌ʹ΋໾ཱͭ Github΍Gitlab͸ը૾ࠩ෼ΛදࣔͰ͖ΔΑ͏ʹͳͬͨͷͰϨ Ϗϡʔ࣌ʹԿ͕มΘ͔ͬͨΛ֬ೝ͠΍͍͢

Slide 33

Slide 33 text

ݒ೦ • ը໘਺͕ଟ͍ΞϓϦͷ৔߹ͷϦϙδτϦͷαΠζ૿Ճ • Uber͕ϝϯς΍ΊΔ • ࠓ౓͸ࣗ෼Ͱר͖औͬͯϝϯς͢Δؾ࣋ͪΛ࣋ͱ͏͔ͳ ͱɻ

Slide 34

Slide 34 text

·ͱΊ • UIςετ΍E2EͰ͸ͳ͘ϏϡʔͷΈΛςετ͍ͨ͠৔߹ʹ͓ ͢͢ΊͰ͢ • ௨ৗͷ୯ମςετΑΓ࣌ؒ͸͔͔Γ·͢ • ϏϡʔͷϓϩύςΟͳͲʹΞΫηεͯ͠ςετ͢Δํ๏΋ ͋Δ

Slide 35

Slide 35 text

Ҏ্ɺ͝੩ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠ɻ

Slide 36

Slide 36 text

ࢀߟ https://github.com/facebookarchive/ios-snapshot-test-case https://github.com/facebookarchive/ios-snapshot-test-case/ issues/227#issuecomment-335175385 https://www.objc.io/issues/15-testing/snapshot-testing/