sobota, 5 stycznia 2013

JUnit rule i mockowanie new Date() cz.2

Chciałbym rozwinąć trochę temat poruszony w 1 części dotyczący mockowania new Date(). Często w testach tworzonych jest po kilka obiektów, dla których oczekujemy, że będą miały np. różne daty utworzenia (pole prywatne w klasie). Nie mając nad tym władzy zdajemy się na JVM i raz te daty faktycznie będą się od siebie różniły, a raz nie. Bazując na poprzednich klasach, test z użyciem Clock'a mógłby wyglądać tak:
@Rule
public Clock clock = Clock.standard();
@Test
public void should() {

 // given
 DateTime firstDate = new DateTime();
 DateTime secondDate = firstDate.plusDays(1);

 clock.setFixedTime(firstDate);
 Order firstOrder = new Order();

 clock.setFixedTime(secondDate);
 Order secondOrder = new Order();

 // when
 // then
}
W rezultacie mamy 2 zamówienia z różnymi datami ich utworzenia. Jeśli nie zależny nam na pełnej kontroli tych dat, to można to zrobić trochę bardziej fancy, np.
clock.afterDay();
Order secondOrder = new Order();
Można również wykorzystać MillisProvider'a i zaimplementować go tak aby każde kolejne wywołanie zwracało "datę" (tj. longa) przesuniętą względem poprzedniej np. o 1 milisekundę. Symulujemy tym samym upływ czasu i mamy pewności, że wywołanie new DateTime() zawsze zwróci nam późniejsza datę.
public class Clock extends ExternalResource {

 private static DateTime currentTime;
 private MillisProvider millisProvider;


 public static final MillisProvider ONE_MILLIS_INTERVAL = new MillisProvider() {

  @Override
  public long getMillis() {
   currentTime = currentTime.plusMillis(1);
   return currentTime.getMillis();
  }
 };

 private Clock() {}

 public Clock(MillisProvider millisProvider) {
  this.millisProvider = millisProvider;
 }

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

 public static Clock alwaysNewTime() {
  return new Clock(ONE_MILLIS_INTERVAL);
 }

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

 private void setProviderIfExists() {
  if (millisProvider != null) {
   DateTimeUtils.setCurrentMillisProvider(millisProvider);
  }
 }

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

 /**
  * set date and stop the clock
  */
 public void setFixedTime(DateTime dateTime) {
  setFixedTime(dateTime.toDate());
 }

 public DateTime getCurrentDateTime() {
  return currentTime;
 }

 public Date getCurrentDate() {
  return currentTime.toDate();
 }
}
I sam test:
@Rule
public Clock clock = Clock.alwaysNewTime();
@Test
public void should() {

 // given
 Order firstOrder = new Order();
 Order secondOrder = new Order();
 // when
 // then
 assertThat(firstOrder.getCreateDate().before(secondOrder.getCreateDate()).isTrue()
}
Kiedy to się może przydać? Na pewno jeśli gdzieś w teście zamówienia lądują w HashMapie, której kluczem jest data utworzenia zamówienia.

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.