Pokazywanie postów oznaczonych etykietą TDD. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą TDD. Pokaż wszystkie posty

czwartek, 26 września 2013

Fluent builder do generowania fluent builderów cz. 3

Opisany w części 2 generator buidlerów został gruntownie zrefaktoryzowany i ewoluował o poziom wyżej, jego użycie staje się jeszcze łatwiejsze. Korzystając z Annotation Processing Tool, mechanizm generowania klas został przeniesiony do procesora adnotacji, którego możemy na różne sposoby podłączyć do projektu.

Nie nudząc technicznymi szczegółami przejdźmy do przykładu. Klasę np. Order oznaczamy adnotacją @GenerateBuilder, która informuje procesor, że dla niej ma zostać wygenerowany builder:
@GenerateBuilder
public class Order {
 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;

 public boolean isRealized() {
  return realized;
 }
 public void orderRealized() {
  // very complex implementation
  realized = true;
 }
}
Żeby w jakiś sposób automatycznie aktualizować istniejące buildery o nowe pola, które doszły w klasie Order (lub odpowiednio usuwać metody inicjujące nieistniejące pola), builder został rozbity na 2 klasy. Jedna z nich jest aktualizowana zawsze przy uruchomieniu procesora, druga generowana tylko raz, dzięki czemu możemy do niej dodawać własne metody budujące. Dla klasy Order powstaną następujące klasy:
public abstract class AbstractOrderBuilder<B> extends AbstractBuilder<Order, B> {
 public abstract B withItems(List<OrderItem> items);
 public abstract B withCreateDate(Date createDate);
 public abstract B withRealized(boolean realized);
 public B withItems(OrderItem... items){
  return withItems(new ArrayList<OrderItem>(Arrays.asList(items)));
 }
}
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder anOrder(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Samo użycie może wyglądać następująco:
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().withRealized(true).build();
 // then
 assertTrue(order.isRealized());
}
Klasę OrderBuilder można rozszerzać, wywołując rzeczywiste metody domenowe, np:
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
 public OrderBuilder realized() {
  Order order = targetObject();
  // invoking real domain method
  order.orderRealized();
  // other methods
  return builder();
 }
}
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().realized().build();
 // then
 assertTrue(order.isRealized());
}
Do podłączenia procesora do projektu może zostać wykorzystany ant, maven, eclipse. Jest również możliwość użycia generatora w starym stylu, czyli wygenerowania ciała klasy buildera do konsoli.

Procesor może również poszukiwać klas z adnotacjami JPA (@Entity, @Ebeddable, @MappedSuperclass).

Do poznania szczegółów odsyłam do wiki projektu. Zachęcam do forkowania i dzielenia się uwagami.

sobota, 5 stycznia 2013

JUnit rule i mockowanie new Date() cz.1

Mam wrażenie, że temat został już dawno rozpracowany, ale niektórzy nadal zapominają, że pisząc taki test:
@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 //given
 Order order = new Order();

 //when
 Date createDate = order.getCreateDate();

 //then
 assertThat(createDate).isEqualTo(new Date());
}
dla takiej klasy:
public class Order {
 private Date createDate;

 public Order() {
  this.createDate = new Date();
 }
 public Date getCreateDate() {
  return createDate;
 }
}
będzie on bardzo niedeterministyczny i raz zadziała a raz nie. Dotychczas, w celu pozbycie się problemu, zamiast new Date(), wywoływałem statyczną metodę z JodaTime (this.createDate = DateTime.now().toDate();), którą następnie mockowałem za pomocą PowerMockito. I test mógł wyglądać tak:
@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 //given
 DateTime currentDate = new DateTime();
 PowerMockito.mockStatic(DateTime.class);
 PowerMockito.when(DateTime.now()).thenReturn(currentDate);
 Order order = new Order();

 //when
 Date createDate = order.getCreateDate();

 //then
 assertThat(createDate).isEqualTo(currentDate.toDate());
}
Minusem takiego podejścia jest fakt, że rezerwujemy sobie runnera na PowerMocka i już innego nie będziemy mogli użyć, np. dla testów w kontekście springa. Żeby nie wiało nudą, zainspirowany ostatnim szkoleniem TDD z Rafełem Jamrózem, proponuje inne podejście, a konkretnie wykorzystanie junitowych @Rule. Mechanizm ten jest alternatywą dla @Before i @After, czyli poprzez implementację odpowiedniego interfejsu pozwala na zrobienie czegoś przed i po metodzie testowej. Dlaczego jest to lepsze od @Before i @After - ponieważ raz zaimplementowane, może być używane potem w wielu testach bez dziedziczenia i bez copy-paste. Korzystając z JodaTime mamy do dyspozycji utilsa do sterowania źródłem aktualnego czasu. Implementacja klasy sterującej czasem w testach może wyglądać następująco:
public class Clock extends ExternalResource {

 private static DateTime currentTime;


 private Clock() {}

 public static Clock standard() {
  return new Clock();
 }

 @Override
 protected void before() throws Throwable {
  currentTime = new DateTime();
 }

 @Override
 protected void after() {
  DateTimeUtils.setCurrentMillisSystem();
 }

 /**
  * set date and stop the clock
  */
 public void setFixedTime(Date date) {
  currentTime = new DateTime(date);
  DateTimeUtils.setCurrentMillisFixed(currentTime.getMillis());
 }

 public DateTime getCurrentDateTime() {
  return currentTime;
 }

 public Date getCurrentDate() {
  return currentTime.toDate();
 }
}
A test z wykorzystaniem rula:
@Rule
public Clock clock = Clock.standard();

@Test
public void shouldCreateOrderWithCurrentCreateDate() {

 // given
 Date date = new Date();
 clock.setFixedTime(date);
 Order order = new Order();

 // when
 Date createDate = order.getCreateDate();

 // then
 assertThat(createDate).isEqualTo(date);
}
Należy tylko pamiętać, że do tworzenia daty w zamówieniu używamy JodaTime. Implementacja Clocka nie wpływa na inne metody testowe (patrz. metoda after()), więc możemy w zależności od potrzeb korzystać z jego funkcjonalności lub nie.

Fluent builder do generowania fluent builderów cz.2

Tak jak wspominałem w części 1. postaram się przedstawić, jak można tworzyć buildery do klas, które nie korzystają z setterów i nie są w ciele klasy.

Może od razu na konkretnym przykładnie dla klasy:
public class Order {

 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;
 
 public void orderRealized(){
  //very complex implementation
  realized = true;
 }
}
Wykorzystując trochę przerobiony mechanizm generowanie builderów:
AbstractBuilderGenerator.forClass(Order.class)
    .withStaticCreate(true)
    .printBuilder();
Wynikiem jest interfejs dla naszego przyszłego buildera, w postaci abstrakcyjnej klasy i abstrakcyjnych metod:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder>{

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);
 public static OrderBuilder create(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Kod z sysout'a należy skopiować do pliku i mamy klasę buildera, którą możemy umieścić sobie w dowolnym miejscu, najlepiej gdzieś w folderze od testów.
OrderBuilder dodatkowo został wygenerowany z metodą create, która wywołuje fabrykę tworzącą instancję buildera na podstawie jego abstrakcyjnego interfejsu. To jest miejsce gdzie się dzieje trochę czarnej magii, tj. refleksje, dynamiczne proxy, generics. Z drugiej strony, który framework na tym w dzisiejszych czasach nie bazuje? Fabryka wykorzystuje domyślny konstruktor do utworzenia obiektu docelowego (może być prywatny), ale jeśli klasa nie ma takiego konstruktora, to można użyć przeładowanej metody createImplementation i przekazać obiekt docelowy utworzony przez jakikolwiek konstruktor.

Samo wykorzystanie:
Order realizedOrder = OrderBuilder.create().withRealized(true).build();
Dzięki temu, że interfejs buildera nie jest tak de facto nie interfejsem a klasa abstrakcyjną mamy pełną swobodę w tworzeniu dodatkowych metod buildera, które mogą korzystać z metod domenowych obiektu.

Zmodyfikowany interfejs:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder> {

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);

 public OrderBuilder realized() {
  Order order = targetObject();
  order.orderRealized();
  // other methods
  return builder();
 }

 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Korzystając z metod targetObject i builder (zwracające odpowiednio referencje na obiekt docelowy i samego buildera), możemy konstruować dodatkowe metody tak, aby nie przerywały mechanizmu fluent. Wykorzystanie:
Order realizedOrder = OrderBuilder.create()
    .realized()
    .withCreateDate(new Date())
    .build();

Zapraszam do forkowania i komentowania co jeszcze można by tu było usprawnić, biblioteczka jest właściwie w powijakach. Najwygodniej byłoby gdyby generowanie nie odbywało się do sysouta, tylko od razu do pliku. Niestety nie mam żadnego doświadczenia w tworzeniu pluginów eclipsowych...

piątek, 4 stycznia 2013

Fluent builder do generowania fluent builderów cz.1

Na samym początku dla wszystkich, którzy przykładają duża wagę do definicji, nie będzie tutaj mowa o wzorcu builder w pełnej swojej krasie, a raczej o jego uproszczonej wersji, która jest jednak częściej spotykana.

Stosując TDD, prędzej czy później dojdziemy do sytuacji, w której potrzebujemy spreparować sobie jakiś obiekt (niech to będzie np. encja domenowa) do testów. Jeśli obiekt ma dobrą enkapsulację, to nie mamy bezpośredniego dostępu do wszystkich pól, co za tym idzie niektóre z nich zostaną poprawnie zainicjowane dopiero po wywołaniu szeregu metod domenowych. Przykładowo jeśli potrzebujemy encje Order, ale jako zrealizowane zamówienie (pole realized = true), tylko i wyłączenie na potrzebuje testów, to dla mnie stratą czasu jest wywoływanie wielu metod (które nie rzadko są dość skomplikowane) tylko po to żeby ta flaga miała odpowiednią wartość.

Drogą na skróty jest tutaj użycie buildera, który znajduje się wewnątrz danej klasy, dzięki temu nie musimy sztucznie dodawać metod "set" do obiektu. Tworzenie takich builderów jest dość nudnym zajęciem dlatego proponuje generator builderów, który zrobi to za nas.

Dla klasy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
}
Wykorzystując generator, który domyślnie drukuje do konsoli:
SimpleBuilderGenerator.forClass(Sample.class)
    .withMethodPrefix("with")
    .printBuilder();
Po wklejeniu tego co zostało wygenrowane do głownej klasy otrzymujemy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
 
 /** 
  * Fluent builder for Sample
  * @formatter:off
  */
 public static Builder builder() {
  return new Builder();
 }

 public static class Builder {

  private final Sample sample = new Sample();

  public Builder withId(int id) { sample.id = id; return this; }
  public Builder withName(String name) { sample.name = name; return this; }
  public Builder withMap(Map<String, List<Integer>> map) { sample.map = map; return this; }

  public Sample build() { return sample;}
 }
 /** @formatter:on */
}
I samo wykorzystanie:
Sample sample = Sample.builder().withName("name").build();
Wszystko fajnie pięknie, ale taki builder ma jedną zasadniczą wadę - jego kod znajduje się w klasie produkcyjnej. Wymaga to od programisty przestrzegania zasady, żeby nie używać tego mechanizmu w kodzie produkcyjnym.

Właściwie cały ten post to preludium do właściwej treści, czyli tworzenie builderów, które są poza klasami produkcyjnymi i nie wymagają setterów, ale o tym w części 2.

Kod źródłowy generatora można znaleźć tutaj.