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.'s job postings
10 Likes
10 Likes

iOS

Weekly ranking

Show other rankings
If this story triggered your interest, go ahead and visit them to learn more