Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 ・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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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表現の考察

Slide 5

Slide 5 text

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名となっているものが多いので、テストコードからどの様 な処理を想定したテストになっているかが把握しやすいです。

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

最近はiOS(Android)に加えてRails側のコードを見る 実際にどんな感じでBackend側を処理しているかを調べる足掛かりがRSpec Backend側の大まかな仕様を知る際にもRspecを追っていく事が多かった(もちろんApp側も同様): Rspec側のドキュメントもQuick側のドキュメントと見比べてみればより解像度が上がるかもと思いました。 AppからのRequest 📲 そういえば、この画面で使っている サーバー側の処理ってどうだっけ? RESTful API or GraphQL Swift Ruby on Rails Quick + Nimble RSpec 開発以外の調査: 問題発生部分の切り分けをしたい時 現在の仕様を把握しておきたい時 機能開発で繋ぎ込み部分を調整する時

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

※ すなわち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で各々のケースで利用する。 ① 下準備 ② テスト

Slide 11

Slide 11 text

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を入れると変化

Slide 12

Slide 12 text

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と似た部分だと思います)

Slide 13

Slide 13 text

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内部の処理を共通化している

Slide 14

Slide 14 text

別解. 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クラスを実行する

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Thank you for listening !