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

Quickを利用したUnitTestにおける細かなTips

 Quickを利用したUnitTestにおける細かなTips

potatotips #82 iOS/Android開発Tips共有会での登壇資料になります。

今回はビヘイビア駆動開発(BDD)のためのテスティングフレームワークでもあるQuickに関して、テストケースにおける共通化をするための処理に関する部分をRuby on Railsでも利用されているRSpecを参考にしながら解説したものになります。

Fumiya Sakai

June 20, 2023
Tweet

More Decks by Fumiya Sakai

Other Decks in Technology

Transcript

  1. Quickを利用したUnitTestにおける細かなTips
    potatotips #82 @ AppBrew様
    2023/06/20
    Fumiya Sakai

    View Slide

  2. 自己紹介
    ・Fumiya Sakai
    ・Mobile Application Engineer
    アカウント:
    ・Twitter: https://twitter.com/fumiyasac

    ・Facebook: https://www.facebook.com/fumiya.sakai.37

    ・Github: https://github.com/fumiyasac

    ・Qiita: https://qiita.com/fumiyasac@github
    発表者:
    ・Born on September 21, 1984
    これまでの歩み:
    Web Designer
    2008 ~ 2010
    Web Engineer
    2012 ~ 2016
    App Engineer
    2017 ~ Now
    iOS / Android / sometimes Flutter

    View Slide

  3. iOSのUI実装本を執筆しています!
    書籍に掲載したサンプルのバージョンアップや続編等に現在着手中です。
    少しの工夫で実現できるTIPS集やライブラリ表現の活用集をはじめとした、iOSア
    プリ開発の中でも特にUI実装やUIKitを利用した画面の中で特徴を与える様な表現
    という題材に焦点を当てた書籍となっております。
    現在は電子書籍版のみとなります。 こちらは全て¥1,000となっております。
    https://just1factory.booth.pm/
    概要:
    https://book-tech.com/
    価格:
    📖 Booth
    📖 Book Tech

    View Slide

  4. UI実装であると嬉しいレシピブックの最新情報
    UI実装であると嬉しいレシピブックVol.3として昨年10月に商業化しました!
    Still

    WIP
    これまでの同人誌として頒布したものに加えて、Vol.1及びVol.2に頒布したものの
    中で書籍に載せきれなかったものや表現や動きが特徴的でユーザーにもほんの少し
    遊び心を与える様なUI実装を紹介したものをVol.3としています。
    概要:
    これからの構想:
    こちらで購入可能です:
    Amazon / Google Play / Apple Books / KINOKUNIYA / Rakuten BOOKS etc..
    🏊 iOS: SwiftUIを利用したUI実装や動画関連の実装
    🏊 Android: Jetpack Composeの基本やその他気になるUI表現の考察

    View Slide

  5. Quick + Nimbleを利用したUnitTestに関する基本事項
    ビヘイビア駆動開発(BDD)のためのテスティングフレームワーク
    XCTestも勿論とても使い易いですが、プロジェクトによってはQuick+Nimbleを利用する場合も多いと思います。
    1. Ruby on RailsのRSpecに似た構造を取っている:
    その他の開発言語でも類似した特徴を持つテスティングフレームワークがありますが、それらの構造にとてもよく似た構造や記
    述方法を持っています。(よく比較事例として挙げられるのがRspecかと思います。)
    2. 振る舞いを記載するステップがわかりやすい:
    ①Arrange(環境構築) / ②Act(実行) / ③Assert(動作確認)のパターンに従ってユニットテストを書く事になるので、読みやす
    い記述をサポートしています。(※Given-When-Then構文の記法と同様です。)
    3. Nimbleで用意されているMacherも相まって記述がわかりやすい:
    Nimbleで提供されているMatcherは、英語の並び順を意識したMatcher名となっているものが多いので、テストコードからどの様
    な処理を想定したテストになっているかが把握しやすいです。

    View Slide

  6. UnitTestを記載していく際にこの様な経験ありますか?
    テストケースを書いていく中で同じ様な記述をついしてしまっていないか?
    Mustでは必ずしもないんだけれども、うまくDryに書けるのであれば取り入れたい所でした。
    1. (例) Form内バリデーション処理:
    例えば文字列のバリデーション処理については、しきい値は変化するけど判定した処理結果はBoolだったりする。
    2. (例) API非同期通信処理:
    検索画面で様々なパラメータでのテストを試す時に、成功時・失敗時での期待するケースを使いまわしたい。
    🤔
    SharedExamples / itBehavesLike を活用してみては?

    View Slide

  7. 最近はiOS(Android)に加えてRails側のコードを見る
    実際にどんな感じでBackend側を処理しているかを調べる足掛かりがRSpec
    Backend側の大まかな仕様を知る際にもRspecを追っていく事が多かった(もちろんApp側も同様):
    Rspec側のドキュメントもQuick側のドキュメントと見比べてみればより解像度が上がるかもと思いました。
    AppからのRequest
    📲
    そういえば、この画面で使っている

    サーバー側の処理ってどうだっけ?
    RESTful API or
    GraphQL
    Swift Ruby on Rails
    Quick + Nimble RSpec
    開発以外の調査:
    問題発生部分の切り分けをしたい時
    現在の仕様を把握しておきたい時
    機能開発で繋ぎ込み部分を調整する時

    View Slide

  8. RSpecのドキュメントを見てみる
    ドキュメント内のコードを見てみると期待する結果を共通化する事例があります
    Rspecでのshared_examplesとit_behaves_likeの例で良くあるのは、バリデーション処理の事例でした。
    引用: https://rspec.info/features/3-12/rspec-core/example-groups/shared-examples/

    View Slide

  9. shared_examplesやit_behaves_likeについて
    更に余談となりますが、RSpecではこんな感じで処理のイメージになります
    同様なテストケースを多く書かなければならない際は(適度に)この様に共通化すると良い場合もありそうです。
    1. shared_examples:
    itの部分ないしはcontext〜itの部分を共通化して名前をつけて管理する。
    2. it_behaves_like:
    shared_examplesで共通化したテストコードを呼び出す。該当呼び出したいshared_examplesにつけた名前を付与する。
    RSpec.shared_examples 'start_wonderful_event' do

    it '何かすごいイベントが始まる' do

    # expect ... で期待する結果を記載する

    end

    end
    describe 'Wonderful Event' do

    let(:event) { create(:event) }

    # 個別にitを書くのではなく定義したshared_examplesを呼び出す

    it_behaves_like 'start_wonderful_event'

    end

    View Slide

  10. ※ すなわちMackerel Class & Cod Classの2つは、Edibleプロトコルに準拠したクラスでないといけない。
    では次にQuickのドキュメントを見てみる
    もちろんRSpecの影響を受けているQuickにも同様な機能は備わっています
    Quick側のドキュメントに掲載されていたコードをここから解説を補足しながら説明していきます。
    引用: https://github.com/Quick/Quick/blob/main/Documentation/ja/SharedExamples.md
    assertの共有でボイラープレートコードをなくしましょう。 複数のオブジェクトに対象して同じ内容のテストを行う場合
    概要の文章を読み解いてみる:
    食べられる(Edible)というprotocolがあるとします。
    サバ(Mackerel)とタラ(Cod)は食べられるものです。
    イルカが食べられるものを食べるとイルカが幸せになります。
    ※ イルカが幸せになる部分をsharedExamplesで共通化をして、itBehavesLikeで各々のケースで利用する。
    ① 下準備
    ② テスト

    View Slide

  11. Quickを使ったテストコードでit部分を共通化する例(1)
    この部分はまず対象Protocol準拠のクラスとイルカのクラスを作成する
    最終的にはDolphinクラスのインスタンス内のisHappyがtrueになることを確かめるのが今回の趣旨です。
    ① 下準備の部分を実際のコードに落とし込んでみる:
    // イルカ🐬 さんクラス

    class Dolphin {

    // イルカが幸せかどうかを判定する

    private(set) var isHappy: Bool

    // テスト時に初期値にはfalseをセットする事にする

    init(happy: Bool) {

    self.isHappy = happy

    }

    // Edibleプロトコルに準拠したものを渡せばtrueになります。

    func eat(_ edible: Edible) {

    isHappy = true

    }

    }
    // こちらは「食べられる」ことを表現するprotocol

    protocol Edible {}

    // 鯖🐟 さんクラス

    class Mackerel: Edible {}

    // 鱈🐟 さんクラス

    class Cod: Edible {}
    MackerelとCodを入れると変化

    View Slide

  12. Quickを使ったテストコードでit部分を共通化する例(2)
    実際のテストコードでitに該当する部分をitBehavesLikeで共通化してみる
    context ~ itの流れとなる部分をitBehavesLikeに置き換えています。
    ② テストの部分を実際のコードに落とし込んでみる:
    final class DolphinSpec: QuickSpec {

    override func spec() {

    describe("イルカ🐬 さんが食べて幸せになるものを調べるテスト") {

    // sharedExamplesを利用してitの部分を共通化する

    // 👉 Closureでテスト対象のものを引き渡した後に期待する答えか否かを確かめる

    // テスト対象のcontext部分を組み立てる

    }

    }

    }
    context("鯖🐟 さんを食べる場合") {

    itBehavesLike("イルカが食べるもの") { ["edible": Mackerel()] }

    }

    context("鱈🐟 さんを食べる場合") {

    itBehavesLike("イルカが食べるもの") { ["edible": Cod()] }

    }
    itBehavesLikeを利用
    📝 itBehavesLikeの下線部分に関して
    sharedExampleの名前を指定すると実行されます。

    (※この部分はRSpecと似た部分だと思います)

    View Slide

  13. Quickを使ったテストコードでit部分を共通化する例(3)
    sharedExampleを利用してイルカが食べて幸せになるケースを切り出し共通化
    今回は「処理イメージの解説まで」ですが、共通化を上手に活用すればテストケースをより綺麗にできるかも。
    ② sharedExample部分の抜粋:
    sharedExamples("イルカが食べるもの") { (sharedExampleContext: @escaping SharedExampleContext) in

    it("イルカが食べて幸せになること") {

    let dolphin = Dolphin(happy: false)

    // SharedExampleContextのClosureを使う場合は[String : Any]型となる点に注意

    let edible = sharedExampleContext()["edible"]

    if let food = edible as? Edible {

    dolphin.eat(food)

    expect(dolphin.isHappy).to(beTruthy())

    } else {

    assertionFailure()

    }

    }

    }
    📝 sharedExampleの下線部分に関して
    itBehavesLikeでこの名前を利用します。

    (※この部分はRSpecと似た部分だと思います)
    it内部の処理を共通化している

    View Slide

  14. 別解. sharedExamplesの部分をBehaviorクラスにする例
    Behaviorクラスを定義してitBehavesLikeで呼び出す形にする例
    振る舞いを別クラスへ切り出しておく様なイメージ:
    // イルカが食べて幸せになるBehaviorクラス

    final class DolphinEdible: Behavior {

    override class func spec(_ context: @escaping () -> Edible) {

    // Closureから渡された食べ物を反映する

    var edible: Edible!

    beforeEach {

    edible = context()

    }

    // 期待した動作になる事を確認する部分を共通化している

    it("イルカが食べて幸せになること") {

    let dolphin = Dolphin(happy: false)

    dolphin.eat(edible)

    expect(dolphin.isHappy).to(beTruthy())

    }

    }

    }
    // 実際のテストを実行するクラス

    final class DolphinBehaviorSpec: QuickSpec {

    override func spec() {

    describe("イルカ🐬 さんが食べて幸せになるものを調べるテスト") {

    context("鯖🐟 さんを食べる場合") {

    itBehavesLike(DolphinEdible.self, context: { Mackerel() })

    }

    context("鱈🐟 さんを食べる場合") {

    itBehavesLike(DolphinEdible.self, context: { Cod() })

    }

    }

    }

    }
    itBehavesLikeにてDolphinEdibleクラスを実行する

    View Slide

  15. まとめ
    自分でも改めて「こういう事ができるのか!」という気付きがありました。
    意外にも普段使っている物でまだまだ知らない事が多くって日々是勉強だと改めて思いました。
    1. RSpec等インスパイアを受けたものから理解のヒントを得る:
    最初はドキュメントを読んでも正直イメージが掴めていませんでしたが、RSpecでの解説を読み比べながら進めていくと、徐々に
    イメージが掴めていき、改めてこう利用するんだという事が理解できた気がします。
    2. 共通化をうまく活用する事でテストコードを更に読みやすくできる:
    過度な共通化でないならば、今回紹介した「sharedExamples / itBehavesLike / Behavior Class」を上手に活用する事で、テス
    トコードをより読み易くできる余地があるかもと改めて感じました。
    3. これまで触れた事がない物だったのでちょっと悔しいお気持ち:
    これまでQuick+Nimbleを利用したテストコードを書いた経験があったものの、恥ずかしながら私もこのメソッドをこれまで利用
    した事はありませんでした。共通化をうまくする部分で苦戦した経験もあったので、知った時は少し複雑でした…。

    View Slide

  16. Thank you for listening !

    View Slide