#5 Wzorce projektowe: Test Data Builder

Dzisiaj na tapete bierzemy wzorzec Test Data Builder. Nazywany jest również Fluent Builder’em lub Appender’em. Z jego pomocą jesteśmy w stanie budować obiekty w prosty i przejrzysty sposób. Zetknęliście się kiedyś z kodem, w którym tworzenie obiektu z dużą ilością pól odbywało się przez konstruktor, a wy kilka razy musieliście sprawdzać w jakiej kolejności podawać następne parametry? Jest na to proste rozwiązanie – Test Data Builder.

Implementacja

Wzorzec ten ma kilka implementacji, ale każdy z nich sprowadza się do stworzenia fluent API. Jednym ze sposobów jest stworzenie klasy buildera posiadającej metodę do każdego pola, która ustawia wartość oraz zwraca obiekt buildera. Dzięki temu możemy ustawiać kolejne pola poprzez łańcuch wywołań metod. Klasa posiada również metodę build(), która wywoływana jest na końcu łańcucha. Tworzy ona nowy obiekt z ustawionymi polami i zwraca go:

class ArticleBuilder {

    private String title;

    public ArticleBuilder title(String title) {
        this.title = title;
        return this;
    }

    public Article build() {
        Article article = new Article();
        article.setTitle(title);
        return article;
    }
}

Kolejną możliwością jest stworzenie buildera jako wewnętrzną klasę statyczną, która podobnie jak przykład wyżej posiada do każdego pola, metody ustawiające wartość oraz zwracające obiekt buildera. Różnicą jest implementacja metody build(), która przekazuje do prywatnego konstruktora klasy budowanego obiektu (Author), obiekt buildera. Dzięki takiemu rozwiązaniu nie potrzebujemy żadnych setterów.

class Author {
    private String firstName;
    private String lastName;

    private Author(final AuthorBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
    }

    static class AuthorBuilder {
        private final String firstName;
        private String lastName;

        public AuthorBuilder (String firstName) {
            this.firstName = firstName;
        }

        public AuthorBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Author build() {
            return new Author(this);
        }
    }
}

Biblioteka Lombok dostarcza nam gotowy mechanizm Test Data Buildera. Adnotacja nad daną klasą pozwala nam korzystać z buildera poprzez wywołanie metody builder() na początku łańcucha oraz metody build() na jej końcu. Klasa posiada tylko pola, które chcemy ustawiać, a budując obiekt nie używamy słowa kluczowego new. Więcej informacji na temat lombokowego buildera znajdziecie TUTAJ.

@Builder
class Category {
    private String name;
}

Wywołanie wszystkich powyższych implementacji widzimy poniżej:

Author author =  new Author.AuthorBuilder("Agnieszka")
        .lastName("Pieszczek")
        .build();
Article article = new ArticleBuilder()
        .title("#5 Wzorce projektowe: Test Data Builder")
        .author(author)
        .creationDate(LocalDateTime.now())
        .format(Format.NORMAL_ARTICLE)
        .category(Category.builder().name("Design Patterns").build())
        .build();

GitHub

Całość znajdzie na GitHubie.

  • Lukasz Lenart

    Uprościłbym AuthorBuilder, przedrostek Author jest duplikacją, zostawiłbym samo Builder i wtedy mamy new Author.Builder(). Przekazywanie Builder do konstruktora tworzy niepotrzebną zależność. Można bawić się dalej i “schować” Builder za metodą statyczną, tzw. Factory Method, wtedy w miejscach gdzie używamy Builder nie musimy go importować 😉

  • Agata Pysz

    hej, czy ten wzorzec powinien byc wykorzystywany tylko w kodzie testowym ? Uwazaz ze moze byc uzywany takze w kodzie produkcyjnym? Jaka jest Twoja opinia na ten temat?:)

    • CodeCouple.pl

      Witaj, to fakt, jego nazwa nie jest zbytnio fortunna, ale jak najbardziej powinniśmy stosować go w kodzie produkcyjny, najlepiej korzystając z Lomboka. Ja osobiście korzystam z tego rodzaju buildera na produkcji i Tobie to też polecam 😉

      • Agata Pysz

        dzieki za odpowiedz. Spotkalam sie z opinia, ze nie powinno sie stosowac builderow w kodzie prdukcyjnym.
        Sytuacja: dodajemy argument do konstruktora. Jesli nie korzystamy z builderow, kod w ktorym nie inicjalizujemy poprawnie nowego argumetnu sie nie skompliuje i bedziemy zmuszeni poprawnie zainicjalizowac klase.
        Jesli korzystamy z buildera, fakt, ze zle inicjalizujemy klase wyjdzie w testach, lub na produkcji..
        Ja osobiscie lubie buildery, ale od tamtego czasu zawsze sie zastanawiam jak powinno sie ich uzywac:)