LoginSignup
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-19

この記事はAndroidその2 Advent Calendar 2016 - Qiita 17日目の記事です。

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

前提

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

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;
  } 
}
UserRepository.java
// モデルを使ったクラス
class UserRepository {

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

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

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

パターンを用いないテスト方法

このRepositoryのテストを書こうとしたとき、大体は以下のような感じになると思います。

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パターンと呼んでいます。

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

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クラスを利用して、先程のテストコードを修正すると以下のようになります。

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パターンを適用してみてはいかがでしょうか。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11