Description of how to use iOSSnapshotTestCase.
iOSSnapshotTestCaseͰϏϡʔͷςετΛߦ͏
View Slide
ࣗݾհ• ۄ৴ޛ• iOS Engineer• iOSDC2018Ͱελοϑ͠·͢
iOSSnapshotTestCaseͱView based testingΛߦ͏ͨΊͷπʔϧݩFBSnapshotTestCaseͰɺͱͱFacebookϏϡʔͷը૾Λ͖߹ΘͤͯมߋҰக͢Δ͔Λςετ͢Δػೳ͕͋Γ·͢ɻ
ḪΔࣄҰલ
ผͷखஈΛݕ౼ͯ͠ΈͨΓWiremockΛͬͨUIςετhttps://speakerdeck.com/tamaki/wiremockdexing-uuitesuto
σϝϦοτ͋Γ·ͨ͠• XCUITestͷ࣮ߦ࣌ؒ• XCUITestͰը໘ͷঢ়ଶΛ࡞Δखؒ
ͱࢥͬͨΒ...
iOSSnapshotTestCaseͱͯ͠෮׆Uber͕Ҿ͖ܧ͙ܗͰ෮׆https://github.com/uber/ios-snapshot-test-case
࠶ͼ͍࢝ΊΔࣄʹ͠·ͨ͠ɻ
iOSSnapshotTestCaseͷ͍ํ
Πϯετʔϧtarget "Tests" douse_frameworks!pod 'iOSSnapshotTestCase'end
ઃఆ• FBREFERENCEIMAGEDIR: $(SOURCEROOT)/$(PROJECT_NAME)Tests/ReferenceImages• IMAGEDIFFDIR: $(SOURCEROOT)/$(PROJECTNAME)Tests/FailureDiffs
ςετ࡞FBSnapshotTestCaseΛܧঝͨ͠ςετέʔεΛ࡞͠·͢ɻclass iOSSnapshotTestCaseSampleTests: FBSnapshotTestCase {}
ςετϝιου࡞FBSnapshotVerifyViewͰରͷϏϡʔΛνΣοΫ͠·͢ɻରͷϏϡʔͷαΠζCGRect(x:y:width:height)ͳͲͰࢦఆ͓͖ͯ͠·͠ΐ͏ɻview.frame = CGRect(x: 0, y: 0, width: 768, height: 1024)FBSnapshotVerifyView(view, identifier: "view id")
ϦϑΝϨϯεը૾ͷ࡞recordMode͕trueͷͰςετΛ࣮ߦ͢ΔͱϦϑΝϨϯε༻ը૾͕ੜ͞Ε·͢ɻoverride func setUp() {super.setUp()recordMode = true}
ϦϑΝϨϯεը૾ReferenceImages_64ͷԼʹςετλʔήοτ໊.ςετέʔε໊ͷσΟϨΫτϦͱɺͦͷԼʹ͖߹ΘͤʹඞཁͱͳΔࢀর༻ը૾͕࡞͞Ε·͢ɻiOSSnapshotTestCaseSampleTests/├── Info.plist├── ReferenceImages_64│ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests│ └── [email protected]└── TopScreenSnapshotTests.swift
ϦϑΝϨϯεը૾
ςετͷ࣮ߦrecordModeΛfalseʹ͢ΔࣄΛΕͳ͍Α͏ʹɻoverride func setUp() {super.setUp()recordMode = false}
ςετ݁Ռ
ը໘͕มߋ͠ςετΛ࣮ߦ͢Δͱ...ૂ͍௨Γςετࣦഊ͠·ͨ͠Ͷʁ
FailureDiffsΛݟͯΈΑ͏├── FailureDiffs│ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests│ ├── [email protected]│ ├── [email protected]│ └── [email protected]├── Info.plist├── ReferenceImages_64│ └── iOSSnapshotTestCaseSampleTests.TopScreenSnapshotTests│ └── [email protected]└── TopScreenSnapshotTests.swift
FailureDiffsͷதFailureDiffsσΟϨΫτϦ͕ੜ͞Εɺ3ͭͷϑΝΠϧ͕ೖͬͯ·͢file name descriptiondiff_*.png Ϗϡʔͷ͖߹Θͤࠩfailed_*.png ݱࡏͷϏϡʔreference_*.png ࢀর༻ͷϏϡʔ(͋Δ͖Ϗϡʔͷը૾)
FailureDiffsͷத
ࢀর༻ͷը૾ͷ࠶ੜઌ΄ͲͷςετϏϡʔʹ͕ࠩ͋Γςετࣦഊ͠·͕ͨ͠ɺఆͨ͠௨Γͳ༁Ͱ͔͢Βɺ࠶ࢀর༻ͷը૾Λొ͠͠·͢ɻ
جຊతͳ͍ํ(·ͱΊ)͜ͷΑ͏ͳ࡞ۀΛ܁Γฦ͢ࣄͰҎԼͷΑ͏ͳԸܙΛड͚Δ͜ͱ͕Ͱ͖·͢ɻ• ҙਤ͠ͳ͍ը໘่ΕΛݕग़དྷΔ• PR࣌ʹϦϑΝϨϯεը૾ؚ͕·ΕΔࣄͰը໘ͷ֬ೝָ͕ʹͳΔ
Tips
ෳαΠζͷରԠϏϡʔͷαΠζॎԣͷରԠͳͲͨ͘͠ͳΓ·͢ΑͶʁྫ͑ྻڍܕͰσόΠεαΠζΛఆٛ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,]}
ෳαΠζͷରԠ͖ຖͷ֤σόΠεͷαΠζ(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}}}
ෳαΠζͷରԠ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 inlet view = vc.view!view.frame = size.frame(with: orientation)FBSnapshotVerifyView(view, identifier: activity.name)})}}}
Ϗϡʔͷঢ়ଶ࡞Γʹ͍ͭͯ• XCUIDevice.shared.orientationΛsetupͰ੍ޚग़དྷΔʁ• ࣦഊ͢Δ͜ͱ͕͋ͬͨ(or ग़དྷͳ͔ͬͨ) هԱʹͳ͍• ࣮ߦΓ͘ͳͬͨ• UIςετͷΈΛ͏ࣄʹҧײΛײͨ͡• ૉʹΠχγϟϥΠβͰDI͢Δࣄʹͨ͠
ɹϨϏϡʔ࣌ʹཱͭGithubGitlabը૾ࠩΛදࣔͰ͖ΔΑ͏ʹͳͬͨͷͰϨϏϡʔ࣌ʹԿ͕มΘ͔ͬͨΛ֬ೝ͍͢͠
ݒ೦• ը໘͕ଟ͍ΞϓϦͷ߹ͷϦϙδτϦͷαΠζ૿Ճ• Uber͕ϝϯςΊΔ• ࠓࣗͰר͖औͬͯϝϯς͢Δؾ࣋ͪΛ࣋ͱ͏͔ͳͱɻ
·ͱΊ• UIςετE2EͰͳ͘ϏϡʔͷΈΛςετ͍ͨ͠߹ʹ͓͢͢ΊͰ͢• ௨ৗͷ୯ମςετΑΓ͔͔࣌ؒΓ·͢• ϏϡʔͷϓϩύςΟͳͲʹΞΫηεͯ͠ςετ͢Δํ๏͋Δ
Ҏ্ɺ͝੩ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ɻ
ࢀߟhttps://github.com/facebookarchive/ios-snapshot-test-casehttps://github.com/facebookarchive/ios-snapshot-test-case/issues/227#issuecomment-335175385https://www.objc.io/issues/15-testing/snapshot-testing/