1
/
5

iOS アプリのテストで簡単にEntityのFixtureを作れる Fixturable について


 こんにちは!iOS エンジニアの永田 (@ngtknt)です。今日は iOS アプリのテストにおける Fixture についてお話ししたいと思います。

Fixture とは

 前もってテストデータを用意しておき、様々な Example で使い回すことでよりテストを簡単に書くことができます。このテストデータのことを Fixture と呼びます。もちろんテストデータは画像などのバイナリデータもありますし、何かしらのレスポンスデータかもしれません。

何を解決する?

 今回は、Entity の Fixture について考えたいと思います。 ( Entity の定義については省略しますが、ドメインオブジェクトと考えてください) Entity の Fixture を定義すること自体はそこまで難しくないと思います。Entity を初期化して定数として定義するだけです。しかし、同時に一部のプロパティーだけ値を指定したいケースもありますよね。そういったケースにも対応したいとなると少し考える必要があります。そこで今回は以下の3点を解決する簡単な仕組みを考えたのでご紹介します。

  1. Entity の Fixture を提供するための Interface を揃える
  2. 使う側がプロパティーの値を設定せずに、適当なインスタンスを作ることができる
  3. 使う側がプロパティーの値を一部だけ設定しても、適当なインスタンスを作ることができる

Fixturable Protocol

 まずは、一貫した Interface を作るために Protocol を作ります。Fixturable Protocol は Self 型の static 変数 fixture を持ちます。

protocol Fixturable {
    static var fixture: Self { get }
}

 例えば、以下のような User という Entity があったとするとします。

struct User {
    let id: Int
    let name: String
    let email: String
    let profile: Profile
}

 User Entity に Fixturable を準拠させます。ここでポイントは、すでにProfile Entity も Fixturable に準拠しているので .fixture と書くだけで済む点です。

extension User: Fixturable {
    static let fixture = User(
        id: 1,
        name: "John Smith",
        email: "john@example.com",
        profile: .fixture
    )
}

 ここまでで 1. 「Entity の Fixture を提供するための Interface を揃える」と 2. 「使う側がプロパティーの値を設定せずに、適当なインスタンスを作ることができる」が達成できました。

fixtureメソッドを自動生成

 さて、続いて 3. の「使う側がプロパティーの値を一部だけ設定しても、適当なインスタンスを作ることができる」を解決したいと思います。これも実はそこまで難しくないです。先ほど作成したfixture
変数を使ってメソッドの引数のデフォルト値を設定していきます。これによって自分で指定したいプロパティーのみ指定することができ、それ以外は適当なデフォルト値で初期化されます。

extension User: Fixturable {
    static func fixture(
        id: Int = fixture.id,
        name: String = fixture.name,
        email: String = fixture.email,
        profile: Profile = fixture.profile
    ) {
        return User(
            id: id,
            name: name,
            email: email,
            profile: profile
        )
    }
}

以下のような呼び出し、または結果が得られます。

User()
// ▿ User
//   - id : 1
//   - name: "John Smith"
//   - email: "john@example.com"
//   ▿ profile: ...

User(name: "Kento Nagata")
// ▿ User
//   - id : 1
//   - name: "Kento Nagata"
//   - email: "john@example.com"
//   ▿ profile: ...

User(id: 360748, email: "nagata@example.com")
// ▿ User
//   - id : 360748
//   - name: "John Smith"
//   - email: "nagata@example.com"
//   ▿ profile: ...

 さて、ここでお気づきでしょうが、この定義自体は機械的に行うことができます。なので、コード生成ツールである Sourcery を利用します。Sourcery は、SourceKit が生成するコードの抽象定義を利用し、テンプレートからコードを生成します。Sourcery の詳細やできることに関してはSourceryのドキュメントをご覧ください。
 以下のテンプレート(swifttemplate)を書くことによって、上記のコードを自動生成できます。types.structs.filter({ $0.implements[“Fixturable”] != nil })とあるようにFixturableを実装した型のみを対象とします。

// fixturable.swifttemplate

// Import required module here

<%_ for type in types.structs.filter({ $0.implements[“Fixturable”] != nil }) { -%>

// MARK: - <%= type.name %> Fixturable

extension <%= type.name %> {
    static func fixture(
    <%_ for (index, variable) in type.storedVariables.enumerated() { -%>
        <%= variable.name %>: <%= variable.typeName.name %> = fixture.<%= variable.name %><%= index == type.storedVariables.count - 1 ? “” : “,” %>
    <%_ } -%>
    ) -> <%= type.name %> {
      return <%= type.name %>(
      <%_ for (index, variable) in type.storedVariables.enumerated() { -%>
          <%= variable.name %>: <%= variable.name %><%= index == type.storedVariables.count - 1 ? “” : “,” %>
      <%_ } -%>
      )
    }
}
<%_ } -%>

まとめ

さて、最後に簡単にまとめます。

  • Fixturable Protocolを作ることによって Entity の Fixture に関するInterfaceを揃える
  • fixture メソッドの引数のデフォルト値として fixture 変数を利用することによって、一部プロパティー値を設定可能な Fixture を取得できるようになる
  • fixture メソッドは、Sourceryを利用して自動生成可能

もし同じようなユースケースでお困りの方がいればご活用ください。

Wantedly, Inc.からお誘い
この話題に共感したら、メンバーと話してみませんか?
Wantedly, Inc.では一緒に働く仲間を募集しています
9 いいね!
9 いいね!

同じタグの記事

今週のランキング

永田 健人さんにいいねを伝えよう
永田 健人さんや会社があなたに興味を持つかも