Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
3年以上運用しているアプリにUIテストを導入した #orecon_ios #fastlane_...
Search
fromkk
October 03, 2017
Programming
1.5k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
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
More Decks by fromkk
See All by fromkk
note社の全員野球で品質向上活動について / note_qa_challenge #iOS_test_teatime
fromkk
3
2.1k
1年分のデータが見たいと言われてやったこと/yearly_data_with_note
fromkk
0
1.1k
note iOSチームの自動化 ver.2021/automation_with_iOS_team_on_note_ver2021
fromkk
0
2.1k
Bitrise体験会説明資料/bitrise_explore
fromkk
1
1.2k
noteのiOSアプリで実装したアクセシビリティの全て #iosdc #a /a11y_with_iOS_App_on_note
fromkk
2
5.3k
dSYMのアップロードで SPMを活用する/use_spm_with_upload_dsyms
fromkk
1
3.3k
Bitriseのリモートアクセス機能 #bitrise_meetup/remote_access_of_bitrise
fromkk
1
700
note社でのMagic Pod活用事例 #af_iosdc/magicpod_with_note
fromkk
2
12k
iOSには無いmacOS独自機能をCatalystで実装する #iosdc #d/make_macos_apps_with_catalyst
fromkk
9
2.3k
Other Decks in Programming
See All in Programming
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
140
Workers Cache を知る
syumai
0
320
Androidだけじゃない、DroidKaigiで広がった私の技術スタック
akkeylab
0
110
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
750
AHC070解法紹介
eijirou
0
150
AWSに止められる覚悟してますか?
morizo_1984
2
500
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
330
設計次第でAIコードの読む量は減らせる / designing-for-code-reading
minodriven
33
14k
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
130
Agentic Software Factoryに、すごく賢いIF文を。 / super smart IF statement into the Agentic Software Factory.
rkaga
1
440
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
120
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
6k
Featured
See All Featured
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
260
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8k
Thoughts on Productivity
jonyablonski
76
5.4k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
66k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
20k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.6k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
390
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
We Have a Design System, Now What?
morganepeng
55
8.3k
How to Ace a Technical Interview
jacobian
280
24k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.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