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を参考にしながら解説したものになります。

Avatar for Fumiya Sakai

Fumiya Sakai

June 20, 2023
Tweet

More Decks by Fumiya Sakai

Other Decks in Technology

Transcript

  1. 自己紹介 ・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
  2. 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名となっているものが多いので、テストコードからどの様 な処理を想定したテストになっているかが把握しやすいです。
  3. ※ すなわち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で各々のケースで利用する。 ① 下準備 ② テスト
  4. 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を入れると変化
  5. 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と似た部分だと思います)
  6. 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内部の処理を共通化している
  7. 別解. sharedExamplesの部分をBehaviorクラスにする例 Behaviorクラスを定義してitBehavesLikeで呼び出す形にする例 振る舞いを別クラスへ切り出しておく様なイメージ: // イルカが食べて幸せになるBehaviorクラス final class DolphinEdible: Behavior<Edible>

    { 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クラスを実行する
  8. まとめ 自分でも改めて「こういう事ができるのか!」という気付きがありました。 意外にも普段使っている物でまだまだ知らない事が多くって日々是勉強だと改めて思いました。 1. RSpec等インスパイアを受けたものから理解のヒントを得る: 最初はドキュメントを読んでも正直イメージが掴めていませんでしたが、RSpecでの解説を読み比べながら進めていくと、徐々に イメージが掴めていき、改めてこう利用するんだという事が理解できた気がします。 2. 共通化をうまく活用する事でテストコードを更に読みやすくできる: 過度な共通化でないならば、今回紹介した「sharedExamples

    / itBehavesLike / Behavior Class」を上手に活用する事で、テス トコードをより読み易くできる余地があるかもと改めて感じました。 3. これまで触れた事がない物だったのでちょっと悔しいお気持ち: これまでQuick+Nimbleを利用したテストコードを書いた経験があったものの、恥ずかしながら私もこのメソッドをこれまで利用 した事はありませんでした。共通化をうまくする部分で苦戦した経験もあったので、知った時は少し複雑でした…。