[iOS] BDDフレームワークのQuick について概要をまとめる

環境

  • Xcode 12.4
  • Swift 5.3.2

Quick とは

Quick is a behavior-driven development framework for Swift and Objective-C. Inspired by RSpec, Specta, and Ginkgo.

https://github.com/Quick/Quick#readme
振る舞い駆動開発のためのSwift や Objective-C 用のテストフレームワークです。
振る舞い駆動開発とは何ぞや、ということについては上のリンクの他、
本ブログの以下ポストなどを参照していただければと思います。
Web-y.dev: 振る舞い駆動開発(Behavior-Driven Development)とは

インストール方法

公式ドキュメントがあります。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/InstallingQuick.md

SwiftPM でもインストール可能でしたが、動作が不安定だった
(失敗したテストを再度回しても何のテストも実行されないなど)ので、
個人的には、SwiftPM以外の方法でインストールするのがいいかなと思います。

使い方のイメージ

Web-y.dev: 一つ一つの振る舞いテストのお作法 に書いた通り、
一つ一つの振る舞いテストは以下の3ステップをひとまとまりとして記述していきます。

  1. Given: システムがこの状態の時(前提条件)
  2. When: この動作をすると
  3. Then: このような振る舞いをする

Quick はこの3ステップを、人が読みやすい形で記述することをサポートしてくれる
フレームワークとなっています。

Quick でテストを書くときのお作法として Arrange, Act, Assert の手順が挙げられていますが、
上記の Given, When, Then と同義かなと思います。
https://github.com/Quick/Quick/blob/main/Documentation/ja/ArrangeActAssert.md#effective-tests-using-xctest-arrange-act-and-assert

具体的な使い方

Git に公式ドキュメントがあります。
https://github.com/Quick/Quick/blob/main/Documentation/ja/QuickExamplesAndGroups.md

そちらを見てもらうのが早そうですが、イメージだけ書いておきます。

import Quick
import Nimble
@testable import HogeApp

class HogeAppSpec: QuickSpec {
    override func spec() {
        describe("メイン画面") {
            var vc: MainVC!
            beforeEach {
                vc = MainVC()
                vc.load()
            }
            it("メインコンテンツが表示される") {
                expect(vc.collectionView.cellForItem(at: indexPath) as? ContentCell)
                    .toEventuallyNot(beNil()) // ここは Nimble で書ける
            }
        }
        describe("アカウント画面") {
            beforeEach {
                // 下2つのcontext の事前準備の共通処理をここに書ける
            }
            context("ログインしている場合") {
                it("アカウント情報が表示される") {
                    
                }
            }
            context("ログインしていない場合") {
                it("ログイン画面が表示される") {
                    
                }
            }
        }
    }
}

spec() メソッドをオーバーライドし、その中でクロージャをネストさせてテストを書いていきます。

describe もしくは context 内のクロージャで、
「Given: システムがこの状態の時(前提条件)」と「When: この動作をすると」まで記述します。
(describe と context に違いはなく、ただのエイリアスです)

it 内のクロージャで「Then: このような振る舞いをする」を記述します。

beforeEach や afterEach で共通の事前準備、後始末を書けます。

実行すると

上記のテストコードを実行すると、以下の3つのテストケースが順番に実行されます。

  1. アカウント画面__ログインしていない場合__ログイン画面が表示される
  2. アカウント画面__ログインしている場合__アカウント画面が表示される
  3. メイン画面__メインコンテンツが表示される

テストファイルの上から実行されるのでは無く、テストケース名の辞書順に実行されます。

メリットとデメリット

僕の思うメリットとデメリットは以下です。

メリット

  1. describe や context で処理を共通化するため、ボイラーテンプレート的な事前準備などのコードを複数書かなくて済む
  2. describe, context, it の組み合わせにより仕様書として読みやすい形になる
  3. 構造化することで、特に条件で出し分ける箇所の仕様が読み取りやすくなる

デメリット

  1. うまく仕様を分解して構造化できなかった場合、テストコード自体がスパゲッティコードになる
  2. 行数が多くなると、いまどの describe 内のコードなんだっけ?と混乱しがち
  3. func にテストを書かないため、ファイル内のテストケース一覧が参照できない

注意しないとデメリットにあるようにけっこうスパゲッティコードになりがちかなと思います。

そのような場合、テストコードの設計見直しや、
describe や context やテストファイルの分割などを検討するとよいのかなと思います。

銀の弾丸ではないので、本格導入する前に一度試してメリデメを把握するのがよいかと思います!

便利な機能

特定のテストケースを無効にする

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/QuickExamplesAndGroups.md#temporarily-disabling-examples-or-groups

xdescribe、xcontext、xit のように先頭に x を付けると、
そのクロージャ内のテストを無効にできます。

特定のテストケースのみ有効にする

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/QuickExamplesAndGroups.md#temporarily-running-a-subset-of-focused-examples

fdescribe、fcontext、fit のように先頭に f を付けると、
そのクロージャ内のテストのみを有効にできます。

テストを共通化する

it の内容がボイラーテンプレート的に同じような内容になる場合、
その部分も共通化することができます。

公式の説明はこちら。
https://github.com/Quick/Quick/blob/main/Documentation/en-us/SharedExamples.md

SharedExample という機構を使います。

以下のようなイメージで使います。

class HogeAppSpec: QuickSpec {
    override func spec() {
        describe("アカウント画面") {
            context("ログインしている場合") {
                itBehavesLike(HogeSharedExamplesConfiguration.accountViewExample) {[
                    HogeSharedExamplesConfiguration.faqKey: "ログインのFAQを表示"
                ]}
            }
            context("ログインしていない場合") {
                itBehavesLike(HogeSharedExamplesConfiguration.accountViewExample) {[
                    HogeSharedExamplesConfiguration.faqKey: "ログインのFAQを表示"
                ]}
            }
        }
    }
}

class HogeSharedExamplesConfiguration: QuickConfiguration {
    static let accountViewExample = "アカウント画面"
    
    static let faqKey = "faq"
    
    override class func configure(_ configuration: Configuration) {
        var faq: String!
        sharedExamples(accountViewExample) { (sharedExampleContext: @escaping SharedExampleContext) in
            it("情報が正しい") {
                faq = sharedExampleContext()[faqKey] as? String
                expect(faq).to(equal("ログインのFAQを表示"))
            }
        }
    }
}

QuickConfiguration を継承したクラスを作成し、共通する it の処理を書いていきます。

で、テストする側は it で呼び出すのでは無く、代わりに itBehavesLike を使います。
テスト対象の値などを itBehavesLike のクロージャ内に辞書形式で書き、SharedExample に渡します。

サンプルではString を渡していますが、それ以外の型ももちろん利用可能です。

SharedExample 側は sharedExampleContext()[キー名] のような形で値を取り出し、
あとはそれを元にテストしていきます。

ちなみに、SharedExample 内には、以下のように it 以外も記述可能です。

class HogeSharedExamplesConfiguration: QuickConfiguration {
        sharedExamples("hogeExample") { (sharedExampleContext: @escaping SharedExampleContext) in
            beforeEach {   
            }
            context("hogeContext") {
                it("hogeIt") {
                }
            }
        }
    }
}

けっこうコストがかかる書き方のため、必要に応じて使うとよさそうですね!

最後に

Quick についてつらつらっと書いてみました。

本文に記した通り、銀の弾丸ではないと思うので、本格導入の前に一度試すのがよいのかなと思います。

他にもこんな便利な機能があるよ、というのがあればコメントに書いていただければと思います!

以上です。
お疲れ様でした!

コメントを残す