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
3年以上運用しているアプリにUIテストを導入した #orecon_ios #fastlane_study_jp /orecon_ios_ui_test_20171003
Search
fromkk
October 03, 2017
Programming
2
1.2k
3年以上運用しているアプリにUIテストを導入した #orecon_ios #fastlane_study_jp /orecon_ios_ui_test_20171003
俺コン Vol.1 Day.2 にて発表した3年以上運用しているアプリにUIテストを導入した話です。
https://orecon.connpass.com/event/64285/
fromkk
October 03, 2017
Tweet
Share
More Decks by fromkk
See All by fromkk
note社の全員野球で品質向上活動について / note_qa_challenge #iOS_test_teatime
fromkk
2
1.6k
1年分のデータが見たいと言われてやったこと/yearly_data_with_note
fromkk
0
790
note iOSチームの自動化 ver.2021/automation_with_iOS_team_on_note_ver2021
fromkk
0
1.8k
Bitrise体験会説明資料/bitrise_explore
fromkk
1
980
noteのiOSアプリで実装したアクセシビリティの全て #iosdc #a /a11y_with_iOS_App_on_note
fromkk
1
2.9k
dSYMのアップロードで SPMを活用する/use_spm_with_upload_dsyms
fromkk
1
2.4k
Bitriseのリモートアクセス機能 #bitrise_meetup/remote_access_of_bitrise
fromkk
1
480
note社でのMagic Pod活用事例 #af_iosdc/magicpod_with_note
fromkk
2
9.8k
iOSには無いmacOS独自機能をCatalystで実装する #iosdc #d/make_macos_apps_with_catalyst
fromkk
9
1.9k
Other Decks in Programming
See All in Programming
if constexpr文はテンプレート世界のラムダ式である
faithandbrave
3
640
Goのエラースタックトレースの歴史と今後
sonatard
7
1.2k
Elm 0.19.0 Changes
bkuhlmann
0
490
[技育CAMPアカデミア]アイディアを形に!【超入門】スマホアプリ開発〜リリースまでの流れをご紹介
teamlab
PRO
0
360
PHPはいつから死んでいるかの調査
chiroruxx
1
400
PHPの次期バージョンはこの時期どうなっているのか - Internalsの開発体制について - PHPカンファレンス小田原
youkidearitai
PRO
1
190
0→1と1→10の狭間で Javaという技術選定を振り返る/Reflecting on the Decision to Choose Java Between Scaling from 0 to 1 and 1 to 10
jaguar_imo
2
380
デフォルトにして至高、RubyMineの大好きな所
ruzia
0
310
OpenAPIを中心に考えるAPI開発入門 / Introduction to API Development with a Focus on OpenAPI
seike460
PRO
2
170
PostmanでAPIの動作確認が楽になった話
h455h1
0
170
Git Lint
bkuhlmann
4
750
エンターテイメント業界で利用されるAWS
demuyan
0
210
Featured
See All Featured
Pencils Down: Stop Designing & Start Developing
hursman
117
11k
Keith and Marios Guide to Fast Websites
keithpitt
408
22k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
60
14k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
78
42k
Git: the NoSQL Database
bkeepers
PRO
422
63k
Atom: Resistance is Futile
akmur
259
25k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
501
140k
Building Effective Engineering Teams - LeadDev
addyosmani
28
1.8k
Web Components: a chance to create the future
zenorocha
305
41k
We Have a Design System, Now What?
morganepeng
43
6.7k
Mobile First: as difficult as doing things right
swwweet
216
8.6k
Building Your Own Lightsaber
phodgson
99
5.7k
Transcript
3Ҏ্ӡ༻͍ͯ͠ΔΞϓϦʹUIςετΛಋೖͨ͠ iOSDC Japan 2017 Զίϯ Vol.1 / Day. 2 1
Profile Kazuya Ueoka Timers inc.ͷiOSΤϯδχΞ Twitter: @fromkk Github: fromkk Qiita:
fromkk 2
3
4
5
ΤϯδχΞืूத 6
ࠓճUIςετΛಋೖͨ͠ΞϓϦ 7
8
Famm • 2014/05ϦϦʔεͷՈ͚ΫϩʔζυSNSΞϓϦ • iOS/AndroidରԠ • ຖ݄ϑΥτΧϨϯμʔΛҹͯ͠Ϣʔβʔʹಧ͚Δ 9
UIςετৼΓฦΓ 10
WWDC 2015 • UIཁૉΛݕࡧɾΞΫγϣϯΛ࣮ߦ • UIͷϓϩύςΟͱঢ়ଶΛݕূ • UI recording •
ςετ݁ՌΛϨϙʔτ 11
ʊਓਓਓਓਓਓਓਓਓਓਓਓਓਓਓʊ ʼɹԿ͍͍͔͔ͯ͠Βͳ͍ ɹʻ ʉY^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Yʉ 12
UIςετͷਏΈ • ͍͖ͳΓॳظը໘͕ىಈ͢Δ • ຖճཁૉΛݕࡧ͢Δͷ͕໘ • ͍ 13
UIςετΛಋೖ͢ΔʹͨͬͯఘΊͨࣄ 14
طଘը໘ͷςετΛॻ͘ࣄఘΊͨ • طଘίʔυ͕ີ݁߹ա͗ͨ(ViewControllerͰϞσϧ Λݺͼग़ͨ͠ΓURLʹΞΫηεͨ͠Γ...) • ৽نը໘ɾཁૉʹ accessibilityIdentifier Λؤுͬ ͯৼΔ༷ʹҙࣝ 15
ͬͨࣄ ͦͷ1 16
ΞϓϦىಈ࣌ʹจࣈྻͰViewControllerΛ ࢦఆͯ͠ىಈग़དྷΔ༷ʹͨ͠ • iOSDC 2016ͷ @dealforest ͞ΜͷൃදΛࢀߟ 17
ॳظը໘ΛStoryboard͔Β։͘ઃఆΛআ ↓ 18
ViewController༻ͱΦϓγϣϯ༻ͷΩʔΛܾΊ͓ͯ͘ struct LaunchKeys { static let viewController: String = "LAUNCH_VIEW_CONTROLLER"
static let userInfo: String = "LAUNCH_USER_INFO" } 19
εΩʔϜΛฤू ઌ΄ͲܾΊͨΩʔʹରͯ͠Λઃఆ͢Δ 20
Creatable protocol protocol Creatable { func create<T>(_ identifier: String, userInfo:
[AnyHashable: Any]?) -> T? } 21
Adopt to Creatable struct Creator: Creatable { func create<T>(_ identifier:
String, userInfo: [AnyHashable : Any]?) -> T? { switch identifier { case "ViewController": return viewController() as? T default: return nil } } } extension Creator { func viewController() -> ViewController { let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main) return storyboard.instantiateInitialViewController() as! ViewController } } 22
Launcher struct Launcher { var creator: Creatable init(with creator: Creatable)
{ self.creator = creator } func launch<T>() -> T? { guard let viewControllerName: String = ProcessInfo.processInfo.environment[LaunchKeys.viewController] else { return nil } var userInfo: [AnyHashable: Any]? = nil if let userInfoString: String = ProcessInfo.processInfo.environment[LaunchKeys.userInfo], let userInfoData: Data = userInfoString.data(using: .utf8) { userInfo = (try? JSONSerialization.jsonObject(with: userInfoData, options: [])) as? [AnyHashable : Any] } return creator.create(viewControllerName, userInfo: userInfo) } } 23
AppDelegate.swift let creator = Creator() let rootViewController: UIViewController #if DEBUG
if let viewController: UIViewController = Launcher(with: creator).launch() { rootViewController = viewController } else { rootViewController = creator.rootViewController() } #else rootViewController = creator.rootViewController() #endif window?.rootViewController = rootViewController 24
ͬͨࣄ ͦͷ2 25
UIςετ͔ΒViewControllerΛࢦఆͯ͠ ςετग़དྷΔ༷ʹͨ͠ 26
ςετଆͷλʔήοτʹ Launcher Λ࡞ 27
import XCTest struct Launcher { var viewControllerName: String var userInfo:
[AnyHashable: Any]? init(viewControllerName: String, userInfo: [AnyHashable: Any]? = nil) { self.viewControllerName = viewControllerName self.userInfo = userInfo } var env: [String: String] { var result: [String: String] = [LaunchKeys.viewController: self.viewControllerName] if let userInfo: [AnyHashable: Any] = self.userInfo { if let data: Data = try? JSONSerialization.data(withJSONObject: userInfo, options: []), let userInfoString: String = String(data: data, encoding: .utf8) { result[LaunchKeys.userInfo] = userInfoString } } return result } func launch() -> XCUIApplication { let app: XCUIApplication = XCUIApplication() app.launchEnvironment = env app.launch() return app } } 28
ͬͨࣄ ͦͷ3 29
ϖʔδͷཁૉΛཧ • σβΠφʔ͔ΒZeplinͱ͍͏πʔϧͰ UIΛ͏ • UIΛͬͨஈ֊ͰཁૉΛચ͍ग़͠ • ςετλʔήοτͰཁૉϦετΛ࡞ ʢPage Object
Design Patternʣ 30
PageObjectsRepresentable protocol protocol PageObjectsRepresentable { var app: XCUIApplication init(app: XCUIApplication)
} 31
Adopt to PageObjectsRepresentable struct ViewControllerPage: PageObjectsRepresentable { var app: XCUIApplication
init(app: XCUIApplication) { self.app = app } var label: XCUIElement { return app.staticTexts["label"] } } 32
Test import XCTest class LunchViewControllerTests: XCTestCase { var viewControllerName: String
{ return "ViewController" } func testLunchLabel() { let launcher = Launcher(viewControllerName: viewControllerName) let app = launcher.launch() let page = ViewControllerPage(app: app) XCTAssertTrue(page.label.exists) XCTAssertEqual(page.label.label, "Lunch") } } 33
͋Εʁ͜ΕϥΠϒϥϦԽ ग़དྷΔΜ͡Όͳ͍ʁ 34
https://github.com/ fromkk/Lunch 35
Lunch • Launcher • Creatable • LaunchKeys • PageObjectsRepresentable •
ViewControllerTestable ͷΈఆٛ 36
! ͍ͩ͘͞ 37
Ԡ༻ฤ 38
UIςετͰϞοΫͷΛөͤͯ͞ςετ 39
దͳϞσϧΛఆٛ struct HogeModel { var hoge: String } 40
ViewControllerʹϞσϧΛอ࣋ class HogeViewController: UIViewController { @IBOutlet weak var hogeLabel: UILabel!
var hogeModel: HogeModel { didSet { hogeLabel.text = hogeModel.hoge } } } 41
CreatorΛ֦ு struct Creator: Creatable { func create<T>(_ identifier: String, userInfo:
[AnyHashable : Any]?) -> T? { switch identifier { case "HogeViewController": guard let data = (userInfo?["MOCK_JSON"] as? String)?.data(using: .utf8), let json: [String: String] = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: String], let hoge = json["hoge"] else { return nil } let model = HogeModel(hoge: hoge) return hogeViewController(with: model) as? T default: return nil } } } extension Creator { func hogeViewController(with hoge: HogeModel) -> HogeViewController { let viewController = HogeViewController() viewController.hogeModel = hoge return viewController } } 42
UIςετ͔ΒJSONจࣈྻΛΞϓϦʹͯ͠ςετ func testModel() { let launcher = Launcher(viewControllerName: viewControllerName, userInfo:
["MOCK_JSON": "{\"hoge\": \"fuga\"}"]) let app = launcher.launch() let page = HogeViewControllerPage(app: app) XCTAssertTrue(page.hogeLabel.exists) XCTAssertEqual("fuga", page.hogeLabel.label) } 43
! 44
ͦͷଞͷςΟοϓε 45
UIͷ ঢ়ଶ Λςετ͢Δ • ཁૉ Λઃఆ͢Δͷ accessibilityIdentifier • ঢ়ଶ Λઃఆ͢Δͷ
accessibilityValue • छྨ Λઃఆ͢Δͷ accessibilityTraits • UIςετଆͰͦΕͧΕ identifier , value, elementType ͰΞΫηεग़དྷΔ 46
CIͰUIςετ࣮ߦ͠ͳ͍༷ʹ • CIڥ(Bitrise)ͰUIςετΛ࣮ߦ͢Δͱֻ͕͔࣌ؒΓ͗͢ Δ • xcodebuild test ʹ -only-testing:$UITEST_SCHEME ΛՃ͢ΔࣄͰςετ͢ΔεΩʔϜΛબ
• ಉ༷ʹ -skip-testing:$UITEST_SCHEME Ͱಛఆͷςε τͷεΩʔϜΛεΩοϓ͢Δࣄ͕ग़དྷΔ 47
ϩʔΧϥΠζରԠ • ϩέʔϧใΛมߋ͢Δʹ XCUIApplication ͷ launchArguments Λมߋ • ྫ: ["-AppleLanguages",
"(ja)", "- AppleLocale", "ja-JP"] Lunch ͷ߹ launcher(targetViewController: self, locale: "ja-JP") ͷ༷ʹࢦఆՄೳ 48
UIςετΛಋೖͯ͠Έͯ • Ұؾʹશ෦Λςετ͢ΔࣄΓ͍͠ • ग़དྷΔࣄ͔Βগͣͭ͠ " • ͕͜͜ಈ࡞͠ͳ͍ͱΞϓϦͱͯ͠Γཱͨͳ͍ͱ͍͏࠷ݶͷ ॴ͔ΒςετΛॻ͍ͯߦ͘ͷ͕ྑͦ͞͏ •
ϢχοτςετͰςετग़དྷΔՕॴͳΔ͘Ϣχοτςε τΛॻ͍ͨํ͕͍͍͔ $ 49
Summary • LunchͰUIςετͷϋʔυϧ͕গ͠Լ͕Γ·ͨ͠ • UIςετͰཁૉΛڞ௨Խ͢ΔࣄͰ͍ճ͕͠؆୯ • ಛఆͷViewController͚ͩ։͖͍ͨ࣌ʹεΩʔϜฤू͚ͩͰ ָνϯ # •
෭࣍తʹΠϯελϯεԽ͢ΔॲཧΛڞ௨Խग़དྷͨ 50
! ͍ͩ͘͞ https://github.com/fromkk/Lunch 51
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ 52