Test Data Builderパターン 〜 テストコードの保守性を上げる

この記事は[Androidその２ Advent Calendar 2016 - Qiita](http://qiita.com/advent-calendar/2016/android_second) 17日目の記事です。

今日はテストコードを保守していく際に役立つパターンの1つとして、
**Test Data Builderパターン**を紹介したいと思います。

# 前提
実際の開発時に、
モデル(値オブジェクトなど)を使ったクラスのテストコードを書くことが多々あると思います。
今回はそのようなケースを想定して、値オブジェクトを`User`、それを使ったクラスを`UserRepository`
をサンプルとして話を進めます。

```java:User.java
// モデル
final class User {
  private final long id;
  private final String name;
  private final int age;
  private final Gender gender;
  private final String avatarUrl;
  private final Email email;

  User(long id, String name, int age, Gender gender,
      String avatarUrl, Email email) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.avatarUrl = avatarUrl;
    this.email = email;
  } 
}
```

```java:UserRepository.java
// モデルを使ったクラス
class UserRepository {

  User findBy(long id) {
    return null; // TODO
  }

  List<User> findAll() {
    return null; // TODO
  }

  void store(User user) {
    // TODO
  }
}
```

# パターンを用いないテスト方法
この`Repository`のテストを書こうとしたとき、大体は以下のような感じになると思います。

```java:UserRepository.java
public class UserRepositoryTest {
  private UserRepository repo;

  @Before public void setUp() {
    repo = new UserRepository();
  }

  @Test public void store() {
    long id = 100;
    User expected = new User(id, "", 1, Gender.MALE, "", new Email(""));

    repo.store(expected);

    User actual = repo.findBy(id);
    assertThat(actual, is(equalTo(expected)));
  }

  @Test public void findAll() {
    repo.store(new User(1, "", 1, Gender.MALE, "", new Email("")));
    repo.store(new User(2, "", 1, Gender.MALE, "", new Email("")));

    List<User> actual = repo.findAll();

    assertThat(actual.size(), is(2));
  }
}
```

### 問題点
このテストコードにおいての問題点は、**`User`クラスのインスタンス化の部分**だと私は考えています。
以下がその理由です。

- `User`クラスのコンストラクタの引数が多く、コードが見づらい
    - `1`や`""`のような意味を持たない記述が多い
- `User`クラスのどの変数に着目したテストなのか、他人から見るとわかりづらい
    - 重要なのは`id`？それともすべて？
- `User`クラスのコンストラクタの変更に脆い
    - コンストラクタを使用しているテストコードすべてを修正しないとテストが実行できない

いずれもテストコードを長期間運用していくには負債となる可能性があります。

# パターンを用いたテスト方法
上記の問題点に対処するため、テストのための`User`クラスのデータを作ることに特化したクラスを用意します。
これを**Test Data Builder**パターンと呼んでいます。

ポイントは**このクラスのメンバ変数にデフォルト値がセットされている** ことです。

```java:UserDataBuilder.java
class UserDataBuilder {
  private long id = 1;
  private String name = "name";
  private int age = 20;
  private Gender gender = Gender.MALE;
  private String avatarUrl = "avatarUrl";
  private Email email = new EmailDataBuilder().build();

  public User build() {
    return new User(id, name, age, gender, avatarUrl, email);
  }

  public UserDataBuilder withId(long id) {
    this.id = id;
    return this;
  }

  public UserDataBuilder withName(String name) {
    this.name = name;
    return this;
  }

  ... 省略 ...

  public UserDataBuilder withEmail(Email email) {
    this.email = email;
    return this;
  }
}
```
※ このコードはテスト環境のみ参照可能で、実際のプロダクション環境には含まれません。


この`UserDataBuilder`クラスを利用して、先程のテストコードを修正すると以下のようになります。

```java:UserRepositoryTest.java
public class UserRepositoryTest {
  private UserRepository repo;

  @Before public void setUp() {
    repo = new UserRepository();
  }

  @Test public void store() {
    long id = 100;
    User expected = new UserDataBuilder().withId(id).build();

    repo.store(expected);

    User actual = repo.findBy(id);
    assertThat(actual, is(equalTo(expected)));
  }

  @Test public void findAll() {
    UserDataBuilder user = new UserDataBuilder();
    repo.store(user.withId(1).build());
    repo.store(user.withId(2).build());

    List<User> actual = repo.findAll();

    assertThat(actual.size(), is(2));
  }
}
```

### パターンを適用した結果
いかがでしょうか。
パターンを用いないテストコードに比べると、こちらのコードは

* `User`クラスの冗長なコンストラクタが消え、見やすい
* `id`という変数に着目したテストであることは、他人が見ても明らか
* `User`クラスのコンストラクタに強い
 * `UserDataBuilder`クラスの変更だけで済む

というようなメリットがあると思います。

# まとめ
今回の例で使った`User`クラスは、比較的インスタンス化が簡単なオブジェクトでした。
しかし実際の開発では、依存しているクラスが多く、テストデータの用意が難しいクラスを用意するケースが多々あります。
そういった時に、

* 自分も他人も読みやすく
* 後々の変更にも耐えやすい

というテストコードを書くために、
この**Test Data Builderパターン**を適用してみてはいかがでしょうか。

# 参考文献
* [実践テスト駆動開発](https://www.amazon.co.jp/%E5%AE%9F%E8%B7%B5%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA-Object-Oriented-SELECTION-Freeman/dp/4798124583)