$30 off During Our Annual Pro Sale. View Details »

iOSDC_2019_DeviceFarm.pdf

 iOSDC_2019_DeviceFarm.pdf

Fumihiko Shiroyama

September 06, 2019
Tweet

More Decks by Fumihiko Shiroyama

Other Decks in Technology

Transcript

  1. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Application Prototyping Solutions Architect
    Fumihiko Shiroyama
    September 6, 2019
    実機の管理とおさらば!
    AWS Device FarmでiOSのテストをしよう!
    iOSDC Japan 2019

    View Slide

  2. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    スライドURL
    https://bit.ly/2m2Gfww

    View Slide

  3. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ⾃⼰紹介
    名前:⽩⼭ ⽂彦(しろやま ふみひこ)
    所属:アマゾン ウェブ サービス ジャパン株式会社
    アプリケーションプロトタイピングソリューションアーキテクト
    経歴:インフラエンジニア、バックエンド開発者
    モバイルアプリ開発者、クラウドアーキテクト
    趣味:⼦育て!、懸垂、⾃動テスト#

    View Slide

  4. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ⾃動テスト⼤好きです!
    • Androidテスト全書という本を出しました
    • ⾃動テストの種類やカバーする範囲、モック
    (テストダブル)の考え⽅、CI/CDとの統合
    など、iOSにも応⽤できるトピックをカバー
    しています

    View Slide

  5. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    このセッションの対象者と⽬的
    対象者
    • iOSのテスト初⼼者
    • テストの⾃動実⾏にチャレンジしたい⽅
    • デバイスの管理から開放されたい⽅
    ゴール
    • iOSの⾃動テストを速習する
    • AWS Device Farmで⾃動テスト実⾏とリモートアクセス
    • AWS Device FarmとCI/CDツールとの統合

    View Slide

  6. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    対象となるアプリ

    View Slide

  7. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    じゃんけんゲーム
    プレーヤーは
    「⼿」を選ぶ

    View Slide

  8. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    デモ

    View Slide

  9. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストとはなんだろうか

    View Slide

  10. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストの種類
    • ユーザが⼿動で⾏うテスト
    • ⾃動テスト (Automated Tests)
    • ユニットテスト (Unit Test)
    • 統合テスト (Integration Test)
    • UIテスト
    統合テストの定義は会社や個⼈によって広範にわたる傾向にある

    View Slide

  11. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストピラミッド
    • The Test Pyramid
    • テストのバランスを⽰す指針
    • 上から下にかけて数が増える
    • 上に⾏くほど⼿間がかかる
    引⽤:https://martinfowler.com/articles/practical-test-pyramid.html

    View Slide

  12. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストピラミッド
    • The Test Pyramid
    • テストのバランスを⽰す指針
    • 上から下にかけて数が増える
    • 上に⾏くほど⼿間がかかる
    引⽤:https://martinfowler.com/articles/practical-test-pyramid.html
    今⽇はユニットテストとUIテストについてかいつまんで解説します%

    View Slide

  13. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    iOSにおける⾃動テスト

    View Slide

  14. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest
    • https://developer.apple.com/documentation/xctest
    • Appleが開発・提供するテストフレームワーク
    • ユニットテスト、パフォーマンステスト、UIテストを提供
    • Xcode 5.0+ から利⽤可能
    ☝XCTest以外にも⾊々あります

    View Slide

  15. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTestのはじめ⽅

    View Slide

  16. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTestのはじめ⽅
    後からプロジェクトに追加する場合

    View Slide

  17. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ユニットテスト

    View Slide

  18. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ユニットテストの基礎の基礎
    • 部品(ユニット)を作る
    • テストクラスを作る
    • テストメソッドを実装
    • 機能が正しいことをアサーション
    • ⾃動テストを実⾏する

    View Slide

  19. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    テストクラス

    View Slide

  20. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    XCTestを利⽤

    View Slide

  21. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    テスト対象のモジュールに
    アクセス可能にする

    View Slide

  22. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    XCTestCaseを継承

    View Slide

  23. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    テストメソッド
    testから始まる命名規則

    View Slide

  24. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    アサーション
    意図した結果だと
    主張(アサーション)

    View Slide

  25. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    import XCTest
    @testable import SampleTest
    class SampleUnitTest: XCTestCase {
    func testSample1() {
    XCTAssertTrue(1 + 1 == 2)
    }
    func testSample2() {
    XCTAssertEqual("foo" + "bar", "foobar")
    }
    }
    全体像

    View Slide

  26. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    実⾏
    Pass! ✅
    個別実⾏
    全体実⾏

    View Slide

  27. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    class SampleUnitTest: XCTestCase {
    override func setUp() {
    }
    override func tearDown() {
    }
    }
    初期化・後処理
    テストケース毎に実⾏される

    View Slide

  28. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    じゃんけんゲームのユニットテスト

    View Slide

  29. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    じゃんけんゲームのユニットテスト
    • 「じゃんけん」機能を考える
    • 「⼿」をランダム抽選し、相⼿の「⼿」と⽐較
    • 勝敗を判定して「結果」を返す

    View Slide

  30. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Hands.swift
    enum Hands: Int, CaseIterable {
    case paper
    case rock
    case scissors
    }
    じゃんけんの「⼿」

    View Slide

  31. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Result.swift
    じゃんけんの「結果」
    enum Result {
    case win
    case lose
    case draw
    }

    View Slide

  32. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    HandGenerator.swift
    「⼿」を抽選するクラス
    class HandGenerator {
    func generate() -> Hands {
    let i = Int.random(in: 0..<3)
    let hand = Hands(rawValue: i)!
    return hand
    }
    }

    View Slide

  33. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    HandGenerator.swift
    class HandGenerator {
    func generate() -> Hands {
    let i = Int.random(in: 0..<3)
    let hand = Hands(rawValue: i)!
    return hand
    }
    }
    「⼿」を返す

    View Slide

  34. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    HandGenerator.swift
    class HandGenerator {
    func generate() -> Hands {
    let i = Int.random(in: 0..<3)
    let hand = Hands(rawValue: i)!
    return hand
    }
    } 乱数で抽選

    View Slide

  35. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator = HandGenerator()
    func play(hand: Hands) -> Result {
    // 後述
    }
    }
    じゃんけんクラス

    View Slide

  36. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator = HandGenerator()
    func play(hand: Hands) -> Result {
    // 後述
    }
    }
    抽選オブジェクト

    View Slide

  37. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator = HandGenerator()
    func play(hand: Hands) -> Result {
    // 後述
    }
    }
    結果を返す

    View Slide

  38. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    func play(hand: Hands) -> Result {
    }

    View Slide

  39. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    func play(hand: Hands) -> Result {
    let generated = generator.generate()
    }
    抽選

    View Slide

  40. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    func play(hand: Hands) -> Result {
    let generated = generator.generate()
    if (generated == hand) {
    return Result.draw
    } else if (generated == Hands.paper && hand == Hands.scissors) {
    return Result.win
    } else if (generated == Hands.rock && hand == Hands.paper) {
    return Result.win
    } else if (generated == Hands.scissors && hand == Hands.rock) {
    return Result.win
    } else {
    return Result.lose
    }
    }
    結果を場合分け

    View Slide

  41. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    func play(hand: Hands) -> Result {
    let generated = generator.generate()
    if (generated == hand) {
    return Result.draw
    } else if (generated == Hands.paper && hand == Hands.scissors) {
    return Result.win
    } else if (generated == Hands.rock && hand == Hands.paper) {
    return Result.win
    } else if (generated == Hands.scissors && hand == Hands.rock) {
    return Result.win
    } else {
    return Result.lose
    }
    }
    play()メソッドをどうテストする?

    View Slide

  42. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストしにくい理由を考える
    • 実はPaperRockScissors#play(hand:)はテストしにくい
    • 「⼿」がランダム抽選されるので結果が不定
    • 依存しているランダム抽選オブジェクトが変更不能なプロパティ
    • 依存オブジェクトを外から渡すことでコントロール可能に

    View Slide

  43. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Generator.swift
    protocol Generator {
    func generate() -> Hands
    }
    プロトコル定義で抽象化

    View Slide

  44. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    HandGenerator.swift
    class HandGenerator {
    func generate() -> Hands {
    let i = Int.random(in: 0..<3)
    let hand = Hands(rawValue: i)!
    return hand
    }
    }

    View Slide

  45. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    HandGenerator.swift
    class HandGenerator: Generator {
    func generate() -> Hands {
    let i = Int.random(in: 0..<3)
    let hand = Hands(rawValue: i)!
    return hand
    }
    }

    View Slide

  46. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator = HandGenerator()
    func play(hand: Hands) -> (Result, Hands) {
    }
    }

    View Slide

  47. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator: Generator
    func play(hand: Hands) -> (Result, Hands) {
    }
    }

    View Slide

  48. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator: Generator
    init(generator: Generator = HandGenerator()) {
    self.generator = generator
    }
    func play(hand: Hands) -> (Result, Hands) {
    }
    }

    View Slide

  49. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissors.swift
    class PaperRockScissors {
    let generator: Generator
    init(generator: Generator = HandGenerator()) {
    self.generator = generator
    }
    func play(hand: Hands) -> (Result, Hands) {
    }
    }
    初期値を与えつつ
    差し替え可能に

    View Slide

  50. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    テストコード

    View Slide

  51. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    class PaperRockScissorsTest: XCTestCase {
    }

    View Slide

  52. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    class PaperRockScissorsTest: XCTestCase {
    class MockGenerator: Generator {
    var hand = Hands.paper
    func generate() -> Hands {
    return hand
    }
    }
    } テスト⽤のGeneratorクラス

    View Slide

  53. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    class PaperRockScissorsTest: XCTestCase {
    class MockGenerator: Generator {
    var hand = Hands.paper
    func generate() -> Hands {
    return hand
    }
    }
    }
    「⼿」は差し替え可能

    View Slide

  54. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    let generator = MockGenerator()
    var paperRockScissors: PaperRockScissors!
    override func setUp() {
    paperRockScissors = PaperRockScissors(generator: generator)
    }
    コントロール可能な
    依存オブジェクトを渡す

    View Slide

  55. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    func testPlay() {
    let myHand = Hands.paper
    generator.hand = Hands.rock
    let result = paperRockScissors.play(hand: myHand)
    XCTAssertEqual(result, Result.win)
    }
    テストケース

    View Slide

  56. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    func testPlay() {
    let myHand = Hands.paper
    generator.hand = Hands.rock
    let result = paperRockScissors.play(hand: myHand)
    XCTAssertEqual(result, Result.win)
    }
    ⾃分の「⼿」はパー

    View Slide

  57. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    func testPlay() {
    let myHand = Hands.paper
    generator.hand = Hands.rock
    let result = paperRockScissors.play(hand: myHand)
    XCTAssertEqual(result, Result.win)
    }
    相⼿の「⼿」はグー

    View Slide

  58. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    func testPlay() {
    let myHand = Hands.paper
    generator.hand = Hands.rock
    let result = paperRockScissors.play(hand: myHand)
    XCTAssertEqual(result, Result.win)
    }
    結果は勝利!

    View Slide

  59. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    PaperRockScissorsTest.swift
    func testPlay() {
    let myHand = Hands.paper
    generator.hand = Hands.rock
    let result = paperRockScissors.play(hand: myHand)
    XCTAssertEqual(result, Result.win)
    }

    View Slide

  60. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ユニットテストまとめ
    • ユニットテストは⽂字通りユニット(部品)単位で書く
    • テストしづらい場合は粒度をより細かく
    • 依存オブジェクトを外から渡すとテストしやすくなる
    • 継続的に実⾏する⼿順はこのあと学びます

    View Slide

  61. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UIテスト

    View Slide

  62. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    • 画⾯を操作し、その結果を検証するテスト
    • XCTestはUIテストをサポートしており、便宜上XCTest UIと呼称する
    • UI要素へのアクセス以外はアサーション含めユニットテストとほとんど同じ
    • テストピラミッドの最上位に位置し、作成もメンテも最も⼿間がかかるので、⽤法⽤
    量を守って付き合う
    UIテスト

    View Slide

  63. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UIの基礎
    import XCTest
    class SampleTestUITests: XCTestCase {
    var app: XCUIApplication!
    override func setUp() {
    continueAfterFailure = false
    app = XCUIApplication()
    app.launch()
    }
    override func tearDown() {
    }

    View Slide

  64. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UIの基礎
    import XCTest
    class SampleTestUITests: XCTestCase {
    var app: XCUIApplication!
    override func setUp() {
    continueAfterFailure = false
    app = XCUIApplication()
    app.launch()
    }
    override func tearDown() {
    }

    View Slide

  65. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UIの基礎
    import XCTest
    class SampleTestUITests: XCTestCase {
    var app: XCUIApplication!
    override func setUp() {
    continueAfterFailure = false
    app = XCUIApplication()
    app.launch()
    }
    override func tearDown() {
    }
    アプリの起動

    View Slide

  66. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の特定
    let topLabel = app.staticTexts["top_label"]
    XCTAssertEqual(topLabel.label, "じゃんけん")
    Accessibility Identifier

    View Slide

  67. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の特定
    let topLabel = app.staticTexts["top_label"]
    XCTAssertEqual(topLabel.label, "じゃんけん")
    Accessibility Identifier
    ラベルテキストの検証

    View Slide

  68. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の特定
    Accessibility Identifier
    let opponentHand = app.images["opponent_hand"]
    XCTAssertTrue(opponentHand.exists)

    View Slide

  69. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の特定
    Accessibility Identifier
    let opponentHand = app.images["opponent_hand"]
    XCTAssertTrue(opponentHand.exists)
    要素の存在検証

    View Slide

  70. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の操作
    同様に「⼿」も特定
    let rock = app.images["my_hand_rock"]
    let paper = app.images["my_hand_paper"]
    let scissors = app.images["my_hand_scissors"]
    for hand in [rock, paper, scissors] {
    hand.tap()
    }
    それぞれの「⼿」をタップ

    View Slide

  71. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    初期画像が
    これに変わってることを
    テストしたい
    どうする?

    View Slide

  72. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ViewController.swift
    switch opponentHand {
    case .paper:
    self.boyHand.image = UIImage(named: "boy_paper")
    self.boyHand.accessibilityLabel = "boy_paper"
    case .rock:
    self.boyHand.image = UIImage(named: "boy_rock")
    self.boyHand.accessibilityLabel = "boy_rock"
    case .scissors:
    self.boyHand.image = UIImage(named: "boy_scissors")
    self.boyHand.accessibilityLabel = "boy_scissors"
    }
    相⼿の「⼿」画像を更新するロジック

    View Slide

  73. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ViewController.swift
    switch opponentHand {
    case .paper:
    self.boyHand.image = UIImage(named: "boy_paper")
    self.boyHand.accessibilityLabel = "boy_paper"
    case .rock:
    self.boyHand.image = UIImage(named: "boy_rock")
    self.boyHand.accessibilityLabel = "boy_rock"
    case .scissors:
    self.boyHand.image = UIImage(named: "boy_scissors")
    self.boyHand.accessibilityLabel = "boy_scissors"
    }
    accessibilityLabelに値をセット

    View Slide

  74. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    let opponentHand = app.images["opponent_hand"]
    let accessibilityLabel = opponentHand.label
    let imageChanged = accessibilityLabel == "boy_paper" ||
    accessibilityLabel == "boy_rock" ||
    accessibilityLabel == "boy_scissors"
    XCTAssertTrue(imageChanged)
    再びUIテストコード

    View Slide

  75. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    let opponentHand = app.images["opponent_hand"]
    let accessibilityLabel = opponentHand.label
    let imageChanged = accessibilityLabel == "boy_paper" ||
    accessibilityLabel == "boy_rock" ||
    accessibilityLabel == "boy_scissors"
    XCTAssertTrue(imageChanged)

    View Slide

  76. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    let opponentHand = app.images["opponent_hand"]
    let accessibilityLabel = opponentHand.label
    let imageChanged = accessibilityLabel == "boy_paper" ||
    accessibilityLabel == "boy_rock" ||
    accessibilityLabel == "boy_scissors"
    XCTAssertTrue(imageChanged)
    相⼿が初期状態から
    グーチョキパーのいずれかに
    変わったことを検知

    View Slide

  77. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    let opponentHand = app.images["opponent_hand"]
    let accessibilityLabel = opponentHand.label
    let imageChanged = accessibilityLabel == "boy_paper" ||
    accessibilityLabel == "boy_rock" ||
    accessibilityLabel == "boy_scissors"
    XCTAssertTrue(imageChanged)
    Pass!!

    View Slide

  78. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UI要素の変更を検知
    let opponentHand = app.images["opponent_hand"]
    let accessibilityLabel = opponentHand.label
    let imageChanged = accessibilityLabel == "boy_paper" ||
    accessibilityLabel == "boy_rock" ||
    accessibilityLabel == "boy_scissors"
    XCTAssertTrue(imageChanged)
    ※あくまで⼀例であり、ViewController側でaccessibilityLabelをセットすることを強く推奨するものではありません

    View Slide

  79. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    UIテストデモ

    View Slide

  80. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.

    View Slide

  81. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    バグの原因を突き⽌める

    View Slide

  82. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    リリースしたが…

    なんか私のiPhone(iOS 10)
    でクラッシュするんですが…
    弱ったな…
    こちらで確認できないぞ

    View Slide

  83. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    バグの事実と原因を確かめたい
    • ユーザからiOS 10系で動かないという報告があった
    • 実はiOS 11.2未満でクラッシュする深刻なバグがある
    • 報告を確かめたいが古い端末は⼿元にない
    • AWS Device Farmを使ってみよう%

    View Slide

  84. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    AWS Device Farm

    View Slide

  85. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    近年のモバイル開発が抱える課題
    デバイスとOSの
    多様化
    複数のテストフレー
    ムワーク対応の困難
    デバイスメンテ
    の⼿間
    テストレポートの
    可視化に対する要望

    View Slide

  86. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    AWS Device Farm
    ⾃動テスト リモートアクセス リモートデバッグ
    ⾃動テストを好きな時に
    好きな数だけクラウド上
    で並列実⾏
    クラウド上の好きな端末の
    任意のバージョンですぐに
    リモートで動作確認
    クラウド上のiOS端末や
    Android端末をローカルの
    IDEから接続してデバッグ
    ※プライベートデバイスのみ

    View Slide

  87. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    様々なテストパターンに対応
    アプリ
    UI Automation
    UI Automator
    XCTest
    各種テストフレームワーク AWS Device Farm 詳細なレポート

    View Slide

  88. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    デバイスタイプ
    • パブリックデバイス
    • 0.17USD/デバイス分の従量課⾦(無料利⽤期間あり)
    • 250USD/⽉の定額プランで無制限利⽤
    • 端末はテスト毎にデータ消去して再起動
    • プライベートデバイス
    • 200USD/⽉から利⽤できる専有デバイス※1※2
    • デバイスは専有で決して他者と共有されず、契約終了後に廃棄
    • リモートデバッグ機能が利⽤可能 ※1 端末の取得価格によって変動
    ※2 最低6ヶ⽉の利⽤など諸条件あり

    View Slide

  89. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    早速使ってみよう

    View Slide

  90. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Built-in: Fuzzテスト
    • いわゆるモンキーテスト
    • テストコードがなくてもUIをランダムに操作して落ちないか確認
    • 複数のデバイスやOSを並列実⾏できる

    View Slide

  91. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ipaをビルド

    View Slide

  92. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ipaをビルド

    View Slide

  93. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    AWS Device Farm
    プロジェクトの作成

    View Slide

  94. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Automated Tests
    ipaのアップロード
    ⾃動テストを選択

    View Slide

  95. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Automated Tests
    Device poolの選択
    テストするデバイス群
    Fuzzテスト

    View Slide

  96. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Fuzzテスト
    テスト結果
    やはり古いバージョンで
    エラーが起こっているようだ

    View Slide

  97. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    エラーの録画を確認する

    View Slide

  98. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.

    View Slide

  99. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Screenshots
    やはりここで落ちている

    View Slide

  100. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Logs
    クラッシュ理由とソース
    コードの対応⾏を発⾒!

    View Slide

  101. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    リモートアクセス

    View Slide

  102. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    リモートアクセス
    ⽬当てのバージョンを選択
    リモートアクセス

    View Slide

  103. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    リモートアクセス
    ブラウザから操作

    View Slide

  104. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    リモートアクセス
    やはりここでクラッシュ!
    このバージョンに間違いない

    View Slide

  105. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    その他のテスト

    View Slide

  106. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    その他のテストをAWS Device Farmで実⾏する
    • AWS Device FarmはFuzzテスト以外にも多数のテストをサポートしている
    • XCTestを選ぶことでユニットテストが実⾏できる
    • XCTest UIを選ぶことでUIテストが実⾏できる
    • 他にもAppiumやEspressoなど多数のフレームワークをサポートしている

    View Slide

  107. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTestをAWS Device Farmで利⽤する
    • https://docs.aws.amazon.com/ja_jp/devicefarm/latest/developerguide/test-
    types-ios-xctest.html
    1. ⼿順を参考にアプリ本体のipaをビルドする
    2. ⼿順を参考にXCTestパッケージをアーカイブする
    3. AWS Device Farmにアップロードして実⾏

    View Slide

  108. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest ユニットテスト
    XCTestパッケージをアップロード

    View Slide

  109. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest
    ⽤途に応じて独⾃のDevice Poolも定義できる
    Deviceの台数や組み合わせも任意

    View Slide

  110. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest
    追加のオプションや
    タイムアウトを設定して実⾏

    View Slide

  111. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UIをAWS Device Farmで利⽤する
    • https://docs.aws.amazon.com/ja_jp/devicefarm/latest/developerguide/test-
    types-ios-xctest-ui.html
    1. ⼿順を参考にアプリ本体のipaをビルドする
    2. ⼿順を参考にXCTest UI Runnerバンドルをアーカイブする
    3. AWS Device Farmにアップロードして実⾏

    View Slide

  112. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UI
    UIテスト
    XCTest UI⽤Runnerバンドルをアップロード

    View Slide

  113. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    XCTest UI
    ユニットテスト同様
    オプションを設定して実⾏

    View Slide

  114. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CI/CDとの統合

    View Slide

  115. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CI/CDとの統合
    • AWS Device Farmは便利だが⼿動でアップロードは⼤変
    • CI/CDツールと連携することで⽇々のテストを⾃動化できる
    • ここではCircleCIを使ってDevice Farmのテストを⾃動化する
    • https://circleci.com/

    View Slide

  116. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIとの連携
    macOSでビルドするには有料プランが必要(無料トライアルあり)

    View Slide

  117. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIの設定
    • プロジェクト直下に.circleci/config.ymlという設定ファイルを設置
    • yamlファイルを編集しビルドとテストの実⾏を設定(後述)
    • Gitレポジトリへのプッシュを契機にビルドとテストが開始される
    • 設定例は後述する

    View Slide

  118. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    fastlaneの組み込み
    • fastlaneはビルド、テスト、リリースなどのタスクを⾃動化するツール
    • https://github.com/fastlane/fastlane
    • ⼀連のタスクをlaneという単位にまとめて記述、管理できる
    • iOSの証明書やプロビジョニングプロファイルの管理を簡単にできる
    • AWS Device Farmと連携するプラグインが提供されている

    View Slide

  119. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    fastlaneの組み込み
    % gem install bundler
    % bundle init
    % vim Gemfile
    gem "fastlane"
    % bundle install
    % bundle exec fastlane --version
    プロジェクト直下で
    次のコマンドを実⾏

    View Slide

  120. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    matchで証明書管理
    • matchはfastlaneで利⽤できるアクションで、証明書やプロビジョニング
    ファイルをプライベートレポジトリに⼀元管理できるツール
    • はじめにcertificatesなどの名前でプライベートレポジトリを作成しておく
    • コマンドを実⾏すると秘匿情報が証明書にプッシュされる
    • 詳細は公式ドキュメントを参照してください
    • https://docs.fastlane.tools/actions/match/

    View Slide

  121. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    matchを設定
    git_url("[email protected]:your_account/certificates.git")
    storage_mode("git")
    type("development") # The default type
    app_identifier(["us.shiroyama.SampleTest",
    "us.shiroyama.SampleTestUITests"])
    username("Your Apple ID")
    % bundle exec fastlane match init
    vim fastlane/Matchfile
    UIテスト⽤のIdentifierを
    忘れないこと
    % bundle exec fastlane match development
    ここで設定したパスワードをCircleCIの
    環境変数MATCH_PASSWORDに設定

    View Slide

  122. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    fastlaneでビルドとテストまで設定
    default_platform(:ios)
    before_all do |lane, options|
    setup_circle_ci
    end
    platform :ios do
    desc "Build and Test"
    lane :build_and_test do
    match(
    type: "development",
    readonly: true,
    )
    scan()
    end
    CircleCIでmatchを使う設

    ビルドとテストが実⾏できる状態

    View Slide

  123. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    fastlaneにaws_device_farmプラグインを追加
    Device Farmにテストを
    スケジュールする設定
    % bundle exec fastlane add_plugin aws_device_farm
    % vim fastlane/Fastfile
    desc "AWS Device Farm"
    lane :device_farm do |options|
    test_type = "XCTEST_UI"
    derived_data_path = options[:derived_data_path]
    configuration = options[:configuration]
    aws_device_farm_package(
    derrived_data_path: derived_data_path,
    configuration: configuration,
    )
    aws_device_farm(
    name: "My1stProject",
    device_pool: "Top Devices",
    test_type: test_type,
    run_name: "#{test_type} - #{Time.now}",
    )
    end
    Device Farm⽤のlaneを定義

    View Slide

  124. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    定義したlaneを利⽤してテストをスケジュール
    derived_data_path = "aws"
    scan(
    build_for_testing: true,
    destination: "generic/platform=iOS",
    derived_data_path: derived_data_path,
    )
    device_farm(
    derived_data_path: derived_data_path,
    configuration: "Debug",
    )
    Device Farmのために
    Generic iOSでビルド

    View Slide

  125. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIへの組み込み
    作ったlaneの呼び出し

    View Slide

  126. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIの環境変数設定
    AWSのキー情報
    matchのパスフレーズ

    View Slide

  127. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    証明書取得設定
    Permissions > Checkout SSH Keys > Add user key > Authorize with GittHub
    証明書レポジトリに
    アクセスできるよう設定

    View Slide

  128. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    満を持してプッシュ!

    View Slide

  129. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIコンソール
    プッシュをトリガに
    ビルドが成功

    View Slide

  130. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    AWS Device Farmコンソール
    Device Farm側でも
    ビルドの成功を確認!

    View Slide

  131. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    処理の流れおさらい
    1. Gitレポジトリへのプッシュを契機にCircleCIでビルドが始まる
    2. CircleCIがfastlaneを使ってソースをビルド
    3. fastlane awc_device_farmプラグインがビルドアーティファクトを元に
    Device Farmで利⽤できる形式に変換
    4. プラグインからAWS APIが呼び出されDevice Farmにアップロード&テスト
    が実⾏される
    5. fastlaneはテスト結果を受け取るまで待ってCircleCIに通知する

    View Slide

  132. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CI/CDとの統合まとめ
    • CI/CDパイプラインに組み込むことで、ビルドからテストまでロー
    カルに⼀切依存しない環境を構築できた
    • CircleCIのワークフロー機能を利⽤すると、特定のブランチへの
    プッシュのみを契機にしたテスト等も可能
    • プラグインはその他多くのCI/CD⽤に提供されており、⾃分にあっ
    たものを選ぶことができる
    コスト削減

    View Slide

  133. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Yay!!!

    View Slide

  134. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    ご清聴ありがとございました,

    View Slide

  135. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    質疑応答

    View Slide

  136. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    Appendix

    View Slide

  137. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    参考⽂献
    • 平⽥敏之, 細沼祐介(2019)『iOSアプリ開発⾃動テストの教科書
    〜XCTestによる単体テスト・UIテストから、CI/CD、デバッグ技術
    まで』技術評論社.
    • ⽩⼭⽂彦, 外⼭純⽣, 平⽥敏之, 菊池紘, 堀江亮介(2018)『Android
    テスト全書』PEAKS.

    View Slide

  138. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    AWS Device Farm テストデバイス⼀覧
    • Q. AWS Device Farmではどのようなデバイスが利⽤できますか
    • A. モバイルアプリケーションのテストに使⽤できる iOS、
    Android、および Fire OS の実機は増え続けています。デバイス群
    は、市場のデータとお客様からのフィードバックをもとに、今後も
    引き続き更新していく予定です。⼀覧は次のリストをご覧くださ
    い。
    • http://awsdevicefarm.info/

    View Slide

  139. © 2019, Amazon Web Services, Inc. or its Affiliates. All rights reserved.
    CircleCIのWorkflow
    workflows:
    version: 2
    build_and_test:
    jobs:
    - build
    - test:
    requires:
    - build
    filters:
    branches:
    only:
    - master
    - release
    Master, release
    のみDevice Farmを動かす

    View Slide