Test Data Builder pattern ~ Improve maintainability of test code

This article is the 17th day of Android 2 Advent Calendar 2016 --Qiita.

Today, as one of the useful patterns for maintaining test code, I would like to introduce the ** Test Data Builder pattern **.

Premise

During actual development I think you often write test code for classes that use models (such as value objects). This time, assuming such a case, the value object is ʻUser, and the class using it is ʻUserRepository. Let's proceed with the story as a sample.

User.java


//model
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 using a model
class UserRepository {

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

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

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

Test method without pattern

When I try to write this Repository test, I think it looks like this:

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));
  }
}

problem

I think the problem with this test code is the ** ʻUser` class instantiation part **. Here's why.

--ʻ The code is hard to see because there are many arguments in the constructor of the Userclass. --Many descriptions have no meaning such as1 and "" ―― It is difficult for others to understand which variable in the User class is the focus of the test. ――What is important is ʻid? Or is it all? --ʻ ʻUser class constructor is vulnerable to change --The test cannot be executed without modifying all the test code that uses the constructor.

Both can be a liability for long-term operation of the test code.

Test method using patterns

To address the above issues, we will prepare a class that specializes in creating data for the ʻUser` class for testing. This is called the ** Test Data Builder ** pattern.

The point is that ** the member variables of this class have default values set **.

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;
  }

  ...abridgement...

  public UserDataBuilder withEmail(Email email) {
    this.email = email;
    return this;
  }
}

Using this ʻUserDataBuilder` class, if you modify the test code above, it will be as follows.

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));
  }
}

The result of applying the pattern

What do you think. Compared to the test code that does not use patterns, this code is

I think there is such a merit.

Summary

The ʻUser` class used in this example was a relatively easy object to instantiate. However, in actual development, there are many dependent classes, and there are many cases where it is difficult to prepare test data. At that time

To write the test code Why not apply this ** Test Data Builder pattern **?

References

Recommended Posts

Test Data Builder pattern ~ Improve maintainability of test code
Implementation of unit test code
Test Data Builder pattern ~ Improve maintainability of test code
Builder pattern
Builder Pattern
Design pattern ~ Builder ~
Design pattern (2): Builder
String and stringbuffer and string builder
Builder pattern (Effective Java)
Introduction to Effective java by practicing and learning (Builder pattern)
Builder pattern
Builder Pattern
[Java] Explanation of Strategy pattern (with sample code)
RSpec-Results of reviewing the test code for'users validation'
Design pattern ~ Builder ~
Design pattern (2): Builder
Efficient test code
Builder pattern that forces a set of required properties
Define data source in code in spring-boot instead of property