Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ツールとして利用するUIテスト #iosdc #a #前夜祭 /use UI Testing ...

fromkk
August 30, 2018

ツールとして利用するUIテスト #iosdc #a #前夜祭 /use UI Testing as a tool in iOSDC 2018

iOSDC 2018 前夜祭で発表した内容です。

https://fortee.jp/iosdc-japan-2018/speaker/proposal/view/559ef049-ecdf-43bf-96db-6ce343a100e5 より

> Xcode 9よりXCTestにスクリーンショット撮影機能が実装されました。XCTestはテストコードから地域や言語を指定する事が可能で、xcodebuildで複数のシミュレーターでテストが実行可能なので、様々な画面サイズと条件のスクリーンを集める事が出来るのです。デザイン崩れ等はUIテストでも判別が難しい物の一つですが、半自動的にデザイン崩れを解消する事が可能になったので発表したいと思います。

参考リンク情報 1: WWDC 2015 UI Testing in Xcode https://developer.apple.com/videos/play/wwdc2015/406/
参考リンク情報 2: Lunch https://github.com/fromkk/Lunch とその発表 https://speakerdeck.com/fromkk/orecon-ios-ui-test-20171003
参考リンク情報 3: WWDC 2017 What's New in Testing https://developer.apple.com/videos/play/wwdc2017/409/
参考リンク情報 4: UI Testのシェルスクリプト https://gist.github.com/fromkk/95b7fd046a081da5bfac913e1c7b31a8
参考リンク情報 5: WWDC 2018 What's New in Testing https://developer.apple.com/videos/play/wwdc2018/403/
参考リンク情報 6: TestSummaries https://github.com/fromkk/TestSummaries とその解説(HTML ver) https://qiita.com/fromkk/items/38d8d4139053ae33773f
[PR] Timers Inc. では絶賛エンジニア募集しています!家族向けアプリ、カップル向けアプリや印刷・月額課金系のシステに興味の方いましたら是非お声がけください! https://timers-inc.com
[PR] 今朝ツイートしましたがリジェクトコンで様々な募集が始まりました! https://iosdcrc.firebaseapp.com/ どしどしご応募ください!

fromkk

August 30, 2018
Tweet

More Decks by fromkk

Other Decks in Programming

Transcript

  1. Profile struct Profile { let name = "Kazuya Ueoka" let

    twitter = "@fromkk" let github = "fromkk" let qiita = "fromkk" let company = "Timers Inc." } • !2
  2. LunchͷΠϯετʔϧ • Carthageܦ༝ͰΠϯετʔϧՄೳ • Cartfileʹgithub "fromkk/Lunch"Λ௥Ճ • carthage updateΛ࣮ߦ •

    ΞϓϦɾUIςετͦΕͧΕͷBuild Phraseʹ/usr/local/bin/carthage copy-frameworksΛ௥Ճ • ΞϓϦλʔήοτͷInput Filesʹ$(SRCROOT)/Carthage/Build/iOS/ Lunch.frameworkΛ௥Ճ • UIςετλʔήοτͷInput Filesʹ$(SRCROOT)/Carthage/Build/iOS/ LunchTest.frameworkΛ௥Ճ !11
  3. Lunchͷ࢖͍ํ(ΞϓϦଆ) import Lunch class Creator: Creatable { func create<T>(_ identifier:

    String, userInfo: [AnyHashable: Any]?) -> T? { guard let name = ViewControllerName(rawValue: identifier) else { return nil } switch name { case .rootViewController: return RootViewController() as? T case .registerViewController: return RegisterViewController() as? T case .helpViewController: return HelpViewController() as? T case .contactViewController: return ContactViewController() as? T } } !12
  4. Lunchͷ࢖͍ํ(ΞϓϦଆ) import UIKit import Lunch func application(_ application: UIApplication, didFinishLaunchingWithOptions

    launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white let rootViewController: UIViewController let creator: Creator = Creator() #if DEBUG if let viewController: UIViewController = Launcher(with: creator).launch() { rootViewController = viewController } else { rootViewController = creator.rootViewController() } #else rootViewController = creator.rootViewController() #endif window?.rootViewController = rootViewController window?.makeKeyAndVisible() } !13
  5. Lunchͷ࢖͍ํ(ΞϓϦଆ) import UIKit import Lunch func application(_ application: UIApplication, didFinishLaunchingWithOptions

    launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white let rootViewController: UIViewController let creator: Creator = Creator() #if DEBUG if let viewController: UIViewController = Launcher(with: creator).launch() { rootViewController = viewController } else { rootViewController = creator.rootViewController() } #else rootViewController = creator.rootViewController() #endif window?.rootViewController = rootViewController window?.makeKeyAndVisible() } !14
  6. Lunchͷ࢖͍ํ(ΞϓϦଆ) import UIKit import Lunch func application(_ application: UIApplication, didFinishLaunchingWithOptions

    launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white let rootViewController: UIViewController let creator: Creator = Creator() #if DEBUG if let viewController: UIViewController = Launcher(with: creator).launch() { rootViewController = viewController } else { rootViewController = creator.rootViewController() } #else rootViewController = creator.rootViewController() #endif window?.rootViewController = rootViewController window?.makeKeyAndVisible() } !15
  7. Lunchͷ࢖͍ํ(ςετଆ) import XCTest import LunchTest final class RootViewControllerTest: XCTestCase, ViewControllerTestable

    { var viewControllerName: String { return ViewControllerName.rootViewController.rawValue } func launch(with locale: String) -> XCUIApplication { let launcher = Launcher(targetViewController: self, locale: locale) return launcher.launch() } func testInitializeJP() { let _ = launch(with: "ja_JP") // TODO: something todo } } !16
  8. Lunchͷ࢖͍ํ(ςετଆ) import XCTest import LunchTest final class RootViewControllerTest: XCTestCase, ViewControllerTestable

    { var viewControllerName: String { return ViewControllerName.rootViewController.rawValue } func launch(with locale: String) -> XCUIApplication { let launcher = Launcher(targetViewController: self, locale: locale) return launcher.launch() } func testInitializeJP() { let _ = launch(with: "ja_JP") // TODO: something todo } } !17
  9. Lunchͷ࢖͍ํ(ςετଆ) import XCTest import LunchTest final class RootViewControllerTest: XCTestCase, ViewControllerTestable

    { var viewControllerName: String { return ViewControllerName.rootViewController.rawValue } func launch(with locale: String) -> XCUIApplication { let launcher = Launcher(targetViewController: self, locale: locale) return launcher.launch() } func testInitializeJP() { let _ = launch(with: "ja_JP") // TODO: something todo } } !18
  10. Lunchͷ࢖͍ํ(ςετଆ) import XCTest import LunchTest final class RootViewControllerTest: XCTestCase, ViewControllerTestable

    { var viewControllerName: String { return ViewControllerName.rootViewController.rawValue } func launch(with locale: String) -> XCUIApplication { let launcher = Launcher(targetViewController: self, locale: locale) return launcher.launch() } func testInitializeJP() { let _ = launch(with: "ja_JP") // TODO: something todo } } !19
  11. εΫγϣΛࡱΔϝιουΛੜ΍͢ import XCTest extension XCTestCase { func screenshot(_ named: String)

    { XCTContext.runActivity(named: named, block: { activity in let screenshot = XCUIScreen.main.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.lifetime = .keepAlways activity.add(attachment) }) } } !22
  12. εΫγϣΛࡱΔϝιουΛੜ΍͢ import XCTest extension XCTestCase { func screenshot(_ named: String)

    { XCTContext.runActivity(named: named, block: { activity in let screenshot = XCUIScreen.main.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.lifetime = .keepAlways activity.add(attachment) }) } } !23
  13. εΫγϣΛࡱΔϝιουΛੜ΍͢ import XCTest extension XCTestCase { func screenshot(_ named: String)

    { XCTContext.runActivity(named: named, block: { activity in let screenshot = XCUIScreen.main.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.lifetime = .keepAlways activity.add(attachment) }) } } !24
  14. εΫγϣΛࡱΔϝιουΛੜ΍͢ import XCTest extension XCTestCase { func screenshot(_ named: String)

    { XCTContext.runActivity(named: named, block: { activity in let screenshot = XCUIScreen.main.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.lifetime = .keepAlways activity.add(attachment) }) } } !25
  15. ৭Μͳը໘Λ։͍ͯͻͨ͢ΒεΫγϣ func testInitializeJP() { let app = launchOtherView(with: "ja_JP") screenshot(#function

    + " screenshot") } func testInitializeUS() { let app = launchOtherView(with: "en_US") screenshot(#function + " screenshot") } . . . !26
  16. UIςετͷ࣮ߦ • xcodebuild build-for-testing ϏϧυͷΈ࣮ߦ • xcodebuild test-without-building ςετͷΈ࣮ߦ •

    Φϓγϣϯ • -project .xcodeprojϑΝΠϧ • -workspace .xcworkspaceϑΝΠϧ • -scheme Ϗϧυ͢ΔεΩʔϚ • -configuration Debug΍ReleaseͳͲ • -sdk iphonesimulator΍iphoneosͳͲ • -destination “name=iPhone X,OS=11.4,platform=iOS Simulator” • -only-testing:HogeTests -skip-testing:FugaTests !27
  17. ShellscriptԽ͓ͯ͘͠ͱศར #!/bin/sh xcodeproj=./Type.xcodeproj xcworkspace=./Type.xcworkspace isWorkspace=true test="-only-testing:TypeUITests" scheme="Type" configuration="Debug" sdk="iphonesimulator" devices=("iPhone

    SE" "iPhone 7" "iPhone 7 Plus" "iPhone X") osList=("10.3.1" "11.4") if $isWorkspace; then command="xcodebuild -workspace $xcworkspace" else command="xcodebuild -project $xcodeproj" fi rm -rf ./result xcrun simctl shutdown xcrun simctl erase all open -a Simulator.app command="$command -scheme $scheme -sdk $sdk -configuration $configuration" $command clean $command build-for-testing for ((i = 0; i < ${#devices[@]}; i++)); do device=${devices[$i]} for os in ${osList[@]}; do destination="platform=iOS Simulator,name=${device},OS=${os}" result=./result/"${device}"_"${os}".bundle echo $result $command test-without-building -destination "$destination" $test -resultBundlePath "$result" done done https://gist.github.com/fromkk/95b7fd046a081da5bfac913e1c7b31a8 !28
  18. !31

  19. !32

  20. !33

  21. !34

  22. !35 1ճ໨ 2ճ໨ 3ճ໨ 4ճ໨ 5ճ໨ ฏۉ 1 Simulator 145.68s

    150.7s 146.76s 146.76s 153.15s 148.61s 2 Simulators 118.4s 125.44s 115.96s 115.57s 112.9s 117.65s 3 Simulators 124.72s 125.44s 115.09s 126.14s 111.81s 120.64s 4 Simulators 124.53s 134.79s 132.47s 144.06s 126.19s 132.41s # TypeͰUIςετ૸ΒͤͯΈͨ݁Ռ
  23. TestSummariesͷ࢖͍ํ test-summaries [--resultDirectory <resultDirectory>] | [--bundlePath <bundlePath>] --outputPath <outputPath> --outputType

    <outputType> [--imageScale <imageScale>] [--backgroundColor <backgroundColor>] [--textColor <textColor>] !40
  24. TestSummariesͷ࣮ߦ # ςετͷ࣮ߦ $ xcodebuild test -workspace Type.xcworkspace -scheme Type

    - configuration Debug -sdk iphonesimulator -destination “OS=11.4,name=iPhone X,platform=iOS Simulator" -resultBundlePath ./ uitests/iphonex.bundle . . . # ݁ՌΛ·ͱΊΔ $ test-summaries --resultDirectory ./uitests --outputPath ./ uitests/result.png —outputType PNG --imageScale 2 !41
  25. TestSummariesͷ࣮ߦ # ςετͷ࣮ߦ $ xcodebuild test -workspace Type.xcworkspace -scheme Type

    - configuration Debug -sdk iphonesimulator -destination “OS=11.4,name=iPhone X,platform=iOS Simulator" -resultBundlePath ./ uitests/iphonex.bundle . . . # ݁ՌΛ·ͱΊΔ $ test-summaries --resultDirectory ./uitests --outputPath ./ uitests/result.png —outputType PNG --imageScale 2 !42
  26. !44

  27. ྫ͑͹ func testHoge() { let app = launchOtherView(with: “ja_JP") let

    hogeLagel = app.staticTexts["hogeLagel"] XCTAssertTrue(hogeLagel.exists) XCTAssertEqual(hogeLagel.label, "hoge") screenshot(#function + " screenshot") } !50
  28. ྫ͑͹ func testHoge() { let launcher = Launcher(targetViewController: self) let

    app = launcher.launch() XCTAssertTrue(app.staticTexts[“hogeLabel"].exists) XCTAssertEqual(app.staticTexts["hogeLabel"].label, "hoge") screenshot(#function) } !51
  29. ྫ͑͹ func testHoge() { let launcher = Launcher(targetViewController: self, userInfo:

    [ "MOCK_JSON": "{\"hoge\": \"fuga\"}" ]) let app = launcher.launch() XCTAssertTrue(app.staticTexts["hogeLabel"].exists) XCTAssertEqual(app.staticTexts["hogeLabel"].label, "fuga") screenshot(#function) } !52