Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Snapshot Testing in iOS
yohei sugigami
April 16, 2019
Technology
6
1.9k
Snapshot Testing in iOS
yohei sugigami
April 16, 2019
Tweet
Share
More Decks by yohei sugigami
See All by yohei sugigami
Redux with iOS
susieyy
0
810
Why use Redux in iOS
susieyy
5
1.6k
ReduxRxを活用したアプリアーキテクチャ
susieyy
8
1.4k
Redux+Rxを活用したiOSアプリアーキテクチャ
susieyy
10
1.5k
Swaggerで始めるAPI定義管理とコードジェネレート
susieyy
15
5.9k
開発中のアプリをXcode9 & Swift4に移行しました
susieyy
0
3.2k
Wantedly People ViewModel and Rx
susieyy
7
6.1k
ReduxDevTools' power to the iOS development
susieyy
0
550
Realm Centered Design
susieyy
5
470
Other Decks in Technology
See All in Technology
Microsoft Build 2022 Azure データ & 分析サービス 最新アップデート
shohei1029
1
380
2022年度新卒技術研修「Docker」講義
excitejp
PRO
0
330
ROS再入門-はじめてのSLAM-
miura55
0
330
DAO (分散型自律組織) vs. 自律分散組織 / DAO vs. Distributed Autonomous Organization
ks91
PRO
0
210
JJUG2022_spring_Keycloak (Red Hat Single Sign-on)
tinoue
0
190
多様な成熟度のデータ活用を総合支援するKADOKAWA Connectedのデータ組織について
kadokawaconnected
PRO
0
170
SI企業が「アジャイル推し」になったら 幸せになれますか?/Can SI company be happy if it becomes “Agile stan” ?
chinmo
1
1k
OpsJAWS Meetup21 システム運用アンチパターンのすすめ
yoshiiryo1
0
1.3k
インタラクティブなメディアの地図投影法: WebメルカトルからAdaptive Projectionsへ / MIERUNE 社内勉強会 #033
sorami
2
210
2022年度新卒技術研修「 ソフトウェアテスト」講義
excitejp
PRO
0
320
現状のFedCMの動作解説と OIDCとの親和性について- OpenID TechNight vol.19
ritou
2
390
Microsoft Build 2022 Recap Party!! Azure のデータ & 分析サービス 注目アップデート / microsoft-build-2022-recap-azure-data-and-analytics
nakazax
0
220
Featured
See All Featured
Documentation Writing (for coders)
carmenhchung
48
2.5k
The Web Native Designer (August 2011)
paulrobertlloyd
74
1.9k
Agile that works and the tools we love
rasmusluckow
319
19k
Build The Right Thing And Hit Your Dates
maggiecrowley
19
1.2k
Build your cross-platform service in a week with App Engine
jlugia
219
17k
JazzCon 2018 Closing Keynote - Leadership for the Reluctant Leader
reverentgeek
172
8.4k
Rebuilding a faster, lazier Slack
samanthasiow
62
7.2k
Art Directing for the Web. Five minutes with CSS Template Areas
malarkey
196
9.4k
GraphQLの誤解/rethinking-graphql
sonatard
27
6.5k
Unsuck your backbone
ammeep
659
55k
Music & Morning Musume
bryan
35
4.2k
How STYLIGHT went responsive
nonsquared
85
3.9k
Transcript
Snapshot Testing ɹ ɹ ɹ ɹ iOS test Night #10
2019/04/16@גࣜձࣾσΟʔɾΤψɾΤʔ Yohei Suginami ( @susieyy )
Profile — Yohei Sugigami — @susieyy — Twitter / Github
/ Qiita — Freelance iOS App Developer — @ FOLIO Co., Ltd.
None
ɹ ɹ εφοϓγϣοτ ΠϝʔδUIςετ
None
None
None
ϦάϨογϣϯςετʢճؼςετʣ — ϓϩάϥϜͷมߋʹ͍ɺγεςϜʹ༧֎ͷӨڹ͕ݱΕͯ ͍ͳ͍͔Ͳ͏͔Λ֬ೝ͢Δςετ — Snapshot Image UI TestUI͕༧ظͤͣมߋ͞Ε͍ͯͳ͍ ͔Λ͔֬ΊΔͷʹඇৗʹ༗༻ͳπʔϧ
None
৽ن࡞ը໘ͷϨϏϡʔෛՙܰݮ εϞʔΫςετʢಈ࡞֬ೝʣͷҰ෦ΛࣗಈԽ — ҟͳΔσόΠεʢը໘αΠζʣɺOSຖͷಈ࡞֬ೝ — ڥքɾ࠷େͷಈ࡞֬ೝ — ҟৗܥಈ࡞֬ೝʹ͢ΔͨΊʹঢ়ଶΛ࠶ݱ͢Δͷ͕͍͠ ঢ়ଶͷಈ࡞֬ೝ
None
None
None
None
ը໘Χλϩά — ঢ়ଶຖʹͲ͏Α͏ͳը໘ͷදࣔͱͳΔͷ͔֬ೝ͕༰қ — σβΠϯ༷ͷը໘ҰཡʢSketchͳͲʣͰͯ͢ͷද ࣔύλʔϯΛཏͨ͠ը໘σβΠϯ͕ͳ͍߹͋Δ — ৽͍͠ࢀՃϝϯόʔͷΩϟοϓΞοϓͰ׆༻ — ো࣌ʹ͋Δঢ়ଶʹ͓͚Δදࣔ֬ೝɾΓ͚Ͱ׆༻
— GihubϦϙδτϦʹMARKDOWNΛࣗಈੜ
None
None
ը໘Χλϩάͱͯ͠ͷ Fastlane snapshotͱͷҧ͍ Fastlane snapshotUITestingΛར༻͓ͯ͠ΓɺϢʔβͷૢ ࡞ΛਅࣅͨίʔυͰද͍ࣔͨ͠ը໘ʹભҠ͢Δ — ಛఆͷ݅Լʹ͓͚Δը໘දࣔΛ࠶ݱ͠ʹ͍͘ — ಛఆͷ݅Լʹ͓͚ΔAPI
ResponseͷMockΛࠩ͠ࠐΈʹ ͍͘ — E2Eςετ૬ͷӡ༻ίετ͔͔ΔʢյΕ͍͢ʣ
ɹ ɹ iOS SnapshotTestCase
iOS SnapshotTestCase — ςετରͷUIView·ͨCALayerͷεφοϓγϣοτը ૾ΛࡱΓɺࣄલʹ४උͨ͠ϦϑΝϨϯεը૾ͱࠩΛൺֱ͠ ͯҰக͢Δ͔Λ֬ೝ͢ΔςεςΟϯάϑϨʔϜϫʔΫ — XCTestCaseͰར༻Ͱ͖Δ — Facebook͕࡞ͨOSS͕ͩͬͨUber͕Ҿ͖ܧ͗
— https://github.com/uber/ios-snapshot-test-case
ಋೖํ๏ɾར༻ํ๏ — What is iOSSnapshotTestCase — https://speakerdeck.com/tamaki/what-is- iossnapshottestcase?slide=35 — Shingo
Tamaki — ಋೖํ๏͔Βجૅతͳ͍ํ·Ͱஸೡʹղઆ͞Ε͓ͯΓΦ εεϝ
ςετίʔυྫ import FBSnapshotTestCase class FBSnapshotTestCaseSwiftTest: FBSnapshotTestCase { override func setUp()
{ super.setUp() // ↓ true ʹ͢ΔͱςετͰͳ͘ϦϑΝϨϯεը૾Λग़ྗ recordMode = false } func testExample() { let vc = UIViewController() vc.view.size = CGSize(width: 370, height: 675) FBSnapshotVerifyView(vc.view) } }
ςετίʔυྫ class HogeViewControllerTests: SnapshotTestCase { func testAuthenticated() { stub(uri("/api/endpoins1"), jsonData(fixtureData("test_data1.json")))
stub(uri("/api/endpoins2"), jsonData(fixtureData("test_data2.json"))) let store = environment.createRedux() login(store) let viewController = HogeViewController(reduxStore: store) let navigationController = UINavigationController(rootViewController: viewController) viewController.request() verifyViewController(navigationController) verifyViewController(viewController, identifier: "fullscreen", options: [.fullscreen]) } }
ϦϑΝϨϯεը૾Λ࡞ — FastlaneͰεΫϦϓτԽͯ͠Ұൃ࣮ߦ — NSProcessInfoܦ༝ͰrecordModeΛCLI͔Βೖ — ଟ༷ͳσόΠεͱOSόʔδϣϯͷΈ߹ΘͤͰੜ — iPhoneSE, iPhoneX,
iPhoneX MAX — iOS10, iOS11, iOS12
௨৴ͷϨεϙϯεΛMockԽ(1/2) — URLSessionͷϨεϙϯεΛMockԽ — ϓϩμΫγϣϯίʔυඇഁյ — OHHTTPStubsMockingjayʢϊʔϝϯςʣ
௨৴ͷϨεϙϯεΛMockԽ(2/2) — ࢦఆͨ͠ϦΫΤετʢύεύϥϝʔλʣ͕Ϛονͨ͠ ߹ʹҙͷϨεϙϯεΛฦ͢Α͏ʹมߋͰ͖Δ — status 200 & JSON body
or status 500 — ϨεϙϯεͷdelayઃఆՄೳ — ௨৴தͷঢ়ଶϦΫΤετλΠϜΞτͷ֬ೝ — ը૾ઃఆՄೳ
None
ɹ ɹ ·͍͠
CI͚ͩTest͕Fail͢Δ͕Ұ࣌ظൃੜ (1/2) — BitriseͷϚγϯϦιʔεෆ͕ݪҼͩͬͨ — ఆ͍ͯ͠ΔΣΠτ࣌ؒʹॲཧʢΞχϝʔγϣϯʣ͕ ྃͤͣϦϑΝϨϯεը૾ͱ͕ࠩग़ͯ͠·͏ — खݩͷϚγϯͰৗʹύε͢Δ —
Bitrise͕ϚγϯϦιʔεΛΞοϓάϨʔυͨ͠ͷͰվળ Update on Mac infrastructure upgrades and queues @ March 14, 2019 - https://blog.bitrise.io/update-mac-infrastructure-
CI͚ͩTest͕Fail͢Δ͕Ұ࣌ظൃੜ (2/2) — WaitΛνϡʔχϯάͯ͠ෛՙ͕ى͖ͯյΕʹ͘͘͢Δ — ঢ়ଶมԽޙͷඳըΞχϝʔγϣϯॲཧ෦ͷΈదٓ wait(etc 0.5sec)ΛೖΕͯௐ — ϕʔεͷwait࣌ؒΛڞ௨Խ
— RxBlockingΛ׆༻͠ඇಉظͰঢ়ଶ͕มԽ͠ऴΘΔͱ͜Ζ· ͰBlockingͰWait
ΞχϝʔγϣϯதʹεΫϦʔϯγϣοτΛऔಘͯ͠͠·͏ͱϦ ϑΝϨϯεը૾ͱ͕ࠩग़ͯ͠·͏ e.g, Kingfisherͷը૾දࣔ࣌ͷϑΣʔυΞχϝʔγϣϯΛTest ࣌ͷΈOFF KingfisherManager.shared.defaultOptions = [.transition(.none)] e.g, NotificationBannerΛΞχϝʔγϣϯΛTest࣌ͷΈOFF
UI༷ɾཁ͕݅มΘΓ͍͢ͷͰςετ͕յΕ͍͢ ϝϦοτ — յΕΔ͜ͱͰҙਤ͠ͳ͍ӨڹൣғͰσάϨʔγϣϯ͍ͯ͠ Δ͜ͱʹؾ͚ͮΔ σϝϦοτ — ը໘ͷද͕ࣔมΘΔͱεΫϦʔϯγϣοτΛऔΓͨ͠ ΓɺςετέʔεMockσʔλΛमਖ਼ͨ͠Γͱϝϯςφϯ είετ͕͔͔Δ
UI༷ɾཁ͕݅มΘΓ͍͢ͷͰςετ͕յΕ͍͢ τϨʔυΦϑ — αʔϏεʹ͓͚ΔROIʢඅ༻ରޮՌʣΛؑΈͯಋೖՄ൱Λݕ ౼ͨ͠Γɺ༻్Λݶఆ͢ΔʢҟৗܥͷΈεΫϦʔϯγϣο τςετ͢ΔʣͳͲͯ͠ɺ͏·͘׆༻͢Δඞཁ͕͋Δ
ɹ ɹ Snapshot Testing ɹ ɹ ɹ Stephen Celis
ɹ ɹ Snapshot Anything
None
UIViewController ը໘Πϝʔδൺֱ assertSnapshot(matching: vc, as: .image) assertSnapshot(matching: vc, as: .image(on:
.iPhoneSe)) assertSnapshot(matching: vc, as: .image(on: .iPhoneSe(.landscape))) assertSnapshot(matching: vc, as: .image(on: .iPhoneX)) assertSnapshot(matching: vc, as: .image(on: .iPadMini(.portrait))) — ෦ͰWindowͱRootViewController͕࡞͘ΒΕaddChild — VCͷϥΠϑαΠΫϧίʔϧόοΫ͞ΕΔ — viewWillAppear, vieDidAppear
UIViewController ViewͷϑϨʔϜͱώΤϥϧΩʔൺֱ assertSnapshot(matching: vc, as: .recursiveDescription) assertSnapshot(matching: vc, as: .recursiveDescription(on:
.iPhoneSe)) assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPhoneSe(.landscape))) assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPhoneX)) assertSnapshot(matching: vc, as: .recursiveDescription(on: .iPadMini(.portrait))) // [ AF LU ] h=--- v=--- NSButton "Push Me" f=(0,0,77,32) b=(-) // [ A LU ] h=--- v=--- NSButtonBezelView f=(0,0,77,32) b=(-) // [ AF LU ] h=--- v=--- NSButtonTextField "Push Me" f=(10,6,57,16) b=(-) // A=autoresizesSubviews, C=canDrawConcurrently, D=needsDisplay, F=flipped, G=gstate,... assertSnapshot(matching: vc, as: .hierarchy) // <UITabBarController>, state: appeared, view: <UILayoutContainerView> // | <UINavigationController>, state: appeared, view: <UILayoutContainerView> // | | <UIPageViewController>, state: appeared, view: <_UIPageViewControllerContentView> // | | | <UIViewController>, state: appeared, view: <UIView> // | <UINavigationController>, state: disappeared, view: <UILayoutContainerView> not in the window // | | <UIViewController>, state: disappeared, view: (view not loaded)
Referenceͷॻ͖ग़͠ import SnapshotTesting import XCTest class HogeeTests: XCTestCase { func
testView() { record = true let vc = MyViewController() assertSnapshot(matching: vc, as: .image) } }
None
URLRequest assertSnapshot(matching: urlRequest, as: .raw) // POST http://localhost:8080/account // Cookie:
pf_session={"userId":"1"} // // email=blob%40pointfree.co&name=Blob
JSON assertSnapshot(matching: user, as: .json) // { // "bio" :
"Blobbed around the world.", // "id" : 1, // "name" : "Blobby" // }
CaseIterable enum Direction: String, CaseIterable { case up, down, left,
right var rotatedLeft: Direction { switch self { case .up: return .left case .down: return .right case .left: return .down case .right: return .up } } } assertSnapshot( matching: { $0.rotatedLeft }, as: Snapshotting<Direction, String>.func(into: .description) ) // "up","left" // "down","right" // "left","down" // "right","up"
Any assertSnapshot(matching: user, as: .dump) // ▿ User // -
bio: "Blobbed around the world." // - id: 1 // - name: "Blobby"
Failed Diff σʔλͷ͕ࠩදࣔ͞ΕΔͷͰΘ͔Γ͍͢ footer: https://www.stephencelis.com/2017/09/snapshot-testing-in-swift
Defining Custom Snapshot Strategies extension Snapshotting where Value == WKWebView,
Format == UIImage { public static let image: Snapshotting = Snapshotting<UIImage, UIImage>.image .asyncPullback { webView in Async { callback in webView.takeSnapshot(with: nil) { image, error in callback(image!) } } } }
ࣄྫհ1 ImagePipeline by Katsumi Kishikawa — Image Pipeline is an
image loading and caching framework — σίʔυͨ͠ը૾ՃॲཧΛͨ͠ը૾ͷൺֱςετͰར ༻ — https://github.com/folio-sec/ImagePipeline
ࣄྫհ2 SwiftRewriter by Yasuhiro Inami — Swift code formatter using
SwiftSyntax. — ΧελϜετϥςδʔΛ࡞͠ϑΥʔϚοτޙͷSwingίʔ υͱϦϑΝϨϯεSwingίʔυΛൺֱ — https://github.com/inamiy/SwiftRewriter
None
·ͱΊ εφοϓγϣοτΠϝʔδUIςετඞཁੑɺඅ༻ର߅Λݕ౼ ͯ͠ಋೖ - ಋೖͱςετΛॻ͖ग़͢͜ͱ༰қͳͷͰখ࢝͘͞ΊΒΕΔ - E2EςετΑΓಋೖɾӡ༻ίετ͍ - ·ͣDomainςετΛ͔ͬ͠Γॻ͘͜ͱ͕༏ઌ Snapshot
TestingϢχοτςετͷ෯Λ͛ΒΕΔͷͰΦ εεϝ