Upgrade to Pro — share decks privately, control downloads, hide ads and more …

3年以上運用しているアプリにUIテストを導入した #orecon_ios #fastlane_study_jp /orecon_ios_ui_test_20171003

fromkk
October 03, 2017

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

More Decks by fromkk

Other Decks in Programming

Transcript

  1. 3

  2. 4

  3. 5

  4. 8

  5. 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
  6. 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
  7. 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
  8. 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
  9. Adopt to PageObjectsRepresentable struct ViewControllerPage: PageObjectsRepresentable { var app: XCUIApplication

    init(app: XCUIApplication) { self.app = app } var label: XCUIElement { return app.staticTexts["label"] } } 32
  10. 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
  11. ViewControllerʹϞσϧΛอ࣋ class HogeViewController: UIViewController { @IBOutlet weak var hogeLabel: UILabel!

    var hogeModel: HogeModel { didSet { hogeLabel.text = hogeModel.hoge } } } 41
  12. 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
  13. 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
  14. UIͷ ঢ়ଶ Λςετ͢Δ • ཁૉ Λઃఆ͢Δͷ͸ accessibilityIdentifier • ঢ়ଶ Λઃఆ͢Δͷ͸

    accessibilityValue • छྨ Λઃఆ͢Δͷ͸ accessibilityTraits • UIςετଆͰ͸ͦΕͧΕ identifier , value, elementType ͰΞΫηεग़དྷΔ 46
  15. ϩʔΧϥΠζରԠ • ϩέʔϧ৘ใΛมߋ͢Δʹ͸ XCUIApplication ͷ launchArguments Λมߋ • ྫ: ["-AppleLanguages",

    "(ja)", "- AppleLocale", "ja-JP"] Lunch ͷ৔߹ launcher(targetViewController: self, locale: "ja-JP") ͷ༷ʹࢦఆՄೳ 48