niedziela, 17 lutego 2013

Porównanie tradycyjnych asercji, Hamcrest'a i FEST'a

Po tygodniowym szaleństwie w Alpach i niewielkim odmrożeniu, czas wrócić do pracy aby odpocząć ;) Temat może nie będzie zbytnio odkrywczy, ale akurat prezentowałem kolegom w projekcie jak działa FEST Asserts 2.X, więc niewielkim nakładem pracy można coś na blogu o tym skrobnąć. Jak ktoś już zna i używa, to nie musi dalej czytać. A kto nie zna jeszcze FEST’a to niech się przyjrzy przykładom przygotowanym przeze mnie i upiększy swoje nowe testy.

O wyższości FEST’a nad Hamcrest'em słyszałem po raz pierwszy na prezentacji Bartosza Bańkowskiego i Szczepana Fabera pt. Pokochaj swoje testy na Wroc JUG. I to już było wiadome w 2009 roku. W międzyczasie Hamcrest został wciągnięty do JUnit'a i Mockito, ale to z innych względów. A tym czasem FEST Asserts został trochę odświeżony i podbito jego numerek wersji. Dalej jest bardzo dobry i pozwala lepiej definiować fachowe asercje.

Czas na przykład. Jest on całkiem życiowy, aczkolwiek domena problemu została zmieniona i trochę uproszczona dla przejrzystości przykładu. Ja po zobaczeniu podobnego kodu u siebie w projekcie, podczas review, gdzie pewien proces zwracał sporo skomplikowanych wyników, stwierdziłem, że muszę natychmiast zastosować FEST’a. Trochę nawijki z architektem i dostałem zielone światło. Test w pierwszej, typowej JUnit’owej formie poniżej:

@Test
public void shouldGenerateInnerPlanets() {
 // given
 SolarSystem solarSystem = new SolarSystem();

 // when
 Set<Planet> innerPlanets = solarSystem.getInnerPlanets();

 // then
 assertEquals(4, innerPlanets.size());
 for (Planet innerPlanet : innerPlanets) {
  if (innerPlanet.getName().equals("Mercury")) {
   List<Gases> mercuryGases = asList(Gases.OXYGEN, Gases.SODIUM, Gases.HYDROGEN);
   assertPlanet(innerPlanet, RotationDirection.LEFT, 4_879_400, 87.96935, 3.701, mercuryGases);
  } else if (innerPlanet.getName().equals("Venus")) {
   List<Gases> venusGases = asList(Gases.CARBON_DIOXIDE, Gases.NITROGEN);
   assertPlanet(innerPlanet, RotationDirection.RIGHT, 12_103_700, 224.700_96, 8.87, venusGases);
  } else if (innerPlanet.getName().equals("Earth")) {
   List<Gases> earthGases = asList(Gases.NITROGEN, Gases.OXYGEN);
   assertPlanet(innerPlanet, RotationDirection.LEFT, 12_756_273, 365.256_363_004, 9.806_65, earthGases);
  } else if (innerPlanet.getName().equals("Mars")) {
   List<Gases> marsGases = asList(Gases.CARBON_DIOXIDE, Gases.NITROGEN);
   assertPlanet(innerPlanet, RotationDirection.LEFT, 6_804_900, 686.960_1, 3.69, marsGases);
  } else {
   throw new AssertionError("Undefined Planet Name: " + innerPlanet.getName()
     + " in result\n" + innerPlanet.toString());
  }
 }
}

private void assertPlanet(Planet planet, RotationDirection direction, long diameterInMeter,
        double yearInEarthDays, double acceleration, List<Gases> atmosphereGases) {
 assertEquals(direction, planet.getRotationDirection());
 assertEquals(diameterInMeter, planet.getDiameter());
 assertEquals(yearInEarthDays, planet.getSiderealYear().inEarthDays(), 0.01);
 assertEquals(acceleration, planet.getAcceleration(), 0.01);
 for (Gases gas : atmosphereGases) {
  assertTrue("Planet " + planet.getName() + " doesn't contains " + gas,
    planet.getAtmosphereGases().contains(gas));
 }
}  

Testujemy metodę generującą zbiór planet wewnętrznych, czyli 4ch pierwszych z naszego układu słonecznego. Pierwsza asercja w linii 10 sprawdza, czy metoda zwróciła odpowiednią ilość planet. Co się stanie, gdy w wyniku będzie nieprawidłowa ilość elementów? Będziemy o tym wiedzieć, ale co dokładnie jest, a czego nie ma, nie będzie wyszczególnione w komunikacie błędów zepsutego testu. Można do pracy wtedy za prząść debugera, ale ja jego nienawidzę.

Kolejny problem to konieczność odszukania właściwego obiektu. Nasza testowana metoda zwraca zbiór planet, przez co nie możemy być pewni, co do kolejności obiektów w wyniku. Dzięki temu dostaliśmy brzydki blok if-else. W takim każdym if’ie definiowana jest najpierw lista gazów, jakie ma zawierać dana planeta. Można by było to co prawda zrobić inline w wywołaniu assertPlanet(), ale wtedy w ogóle łańcuszek argumentów byłby nieczytelny.

Drugim elementem każdego if’a jest właśnie wywołanie assertPlanet() z jakimiś argumentami. Co one oznaczają? Trzeba skoczyć do ciała metody i sprawdzić. Jaka jest ich kolejność? Przy takiej ich liczbie na pewno jej nie zapamiętamy na dłużej niż 5 minut. Można ewentualnie zrezygnować z metody assertPlanet() i ją zinline’nować, ale wtedy mamy sporo duplikacji w kodzie, niskiej czytelności i słabej reużywalności.

Czas na wersję tego samego kodu z Hamcrestem.

@Test
public void shouldGenerateInnerPlanets() {
 // given
 SolarSystem solarSystem = new SolarSystem();

 // when
 Set<Planet> innerPlanets = solarSystem.getInnerPlanets();

 // then
 assertThat(innerPlanets, hasItem(withName("Mercury")));
 Planet mercury = findPlanetByName(innerPlanets, "Mercury");
 assertThat(mercury, is(rotation(RotationDirection.LEFT)));
 assertThat(mercury, is(diameterInMeter(4_879_400)));
 assertThat(mercury, is(yearLongInEarthDays(87.96935)));
 assertThat(mercury, is(acceleration(3.701)));
 assertThat(mercury, hasGas(Gases.OXYGEN));
 assertThat(mercury, hasGas(Gases.SODIUM));
 assertThat(mercury, hasGas(Gases.HYDROGEN));

 assertThat(innerPlanets, hasItem(withName("Venus")));
 Planet venus = findPlanetByName(innerPlanets, "Venus");
 assertThat(venus, is(rotation(RotationDirection.RIGHT)));
 assertThat(venus, is(diameterInMeter(12_103_700)));
 assertThat(venus, is(yearLongInEarthDays(224.700_96)));
 assertThat(venus, is(acceleration(8.87)));
 assertThat(venus, hasOnlyGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN));

 assertThat(innerPlanets, hasItem(withName("Earth")));
 Planet earth = findPlanetByName(innerPlanets, "Earth");
 assertThat(earth, is(rotation(RotationDirection.LEFT)));
 assertThat(earth, is(diameterInMeter(12_756_273)));
 assertThat(earth, is(yearLongInEarthDays(365.256_363_004)));
 assertThat(earth, is(acceleration(9.806_65)));
 assertThat(earth, hasGases(Gases.NITROGEN, Gases.OXYGEN));
 assertThat(earth, hasNotGases(Gases.SODIUM));

 assertThat(innerPlanets, hasItem(withName("Mars")));
 Planet mars = findPlanetByName(innerPlanets, "Mars");
 assertThat(mars, is(rotation(RotationDirection.LEFT)));
 assertThat(mars, is(diameterInMeter(6_804_900)));
 assertThat(mars, is(yearLongInEarthDays(686.960_1)));
 assertThat(mars, is(acceleration(3.69)));
 assertThat(mars, hasGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN));
 assertThat(mars, hasNotGases(Gases.OXYGEN));

 assertThat(innerPlanets, hasSize(4));
}


Kod wygląd już bardziej czytelnie, ale musiałem do tego dopisać 8 klas Matcher’ów. Właściwie na każdą asercję potrzeba oddzielnego Matcher’a. Mimo wszystko nie pozbyliśmy się problemu wyszukiwania potrzebnego obiektu, co widać w linii 11. Może jest jakieś lepsze obejście na to, ale ja nie znalazłem. Na koniec testu, warto jeszcze sprawdzić, czy testowany zbiór posiada żądaną ilość elementów. Jednak tutaj i tak ewentualny komunikat błędu będzie mało czytelny.

Dobra czas na gwiazdę wieczoru, czyli na test napisany z wykorzystaniem FEST’a.

@Test
public void shouldGenerateInnerPlanets() {
 // given
 SolarSystem service = new SolarSystem();

 // when
 Set<Planet> innerPlanets = service.getInnerPlanets();

 // then
 assertThat(innerPlanets)
   .containsPlanetWithName3("Mercury")
   .withRotation(RotationDirection.LEFT)
   .withDiameterInMeter(4_879_400)
   .withYearInEarthDays(87.96935)
   .withAcceleration(3.701)
   .withGas(Gases.OXYGEN)
   .withGas(Gases.SODIUM)
   .withGas(Gases.HYDROGEN);

 assertThat(innerPlanets)
   .containsPlanetWithName3("Venus")
   .withRotation(RotationDirection.RIGHT)
   .withDiameterInMeter(12_103_700)
   .withYearInEarthDays(224.700_96)
   .withAcceleration(8.87)
   .containsOnlyGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN);

 assertThat(innerPlanets)
   .containsPlanetWithName3("Earth")
   .withRotation(RotationDirection.LEFT)
   .withDiameterInMeter(12_756_273)
   .withYearInEarthDays(365.256_363_004)
   .withAcceleration(9.806_65)
   .containsGases(Gases.NITROGEN, Gases.OXYGEN)
   .doesNotContainGases(Gases.SODIUM);

 assertThat(innerPlanets)
   .containsPlanetWithName3("Mars")
   .withRotation(RotationDirection.LEFT)
   .withDiameterInMeter(6_804_900)
   .withYearInEarthDays(686.960_1)
   .withAcceleration(3.69)
   .containsGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN)
   .doesNotContainGases(Gases.OXYGEN);

 assertThat(innerPlanets)
   .containsOnlyPlanets("Mercury", "Venus", "Earth", "Mars");
} 

Początek testu jest taki jak w poprzednich przypadkach, ale od sekcji  //then  robi się ciekawie. Widać wyraźnie 4 logicznie oddzielone od siebie bloki, po jednym na sprawdzenie każdej z planet. Można też łatwo zauważyć łańcuch wywołań, który powinien nam być przynajmniej ze wzorca Bulder znany, a który można ogólniej zdefiniować, jako fluent interface. Hamcrest stara się również iść w tym kierunku, ale jak dla mnie te zapis jest bardziej zwięzły i przejrzysty. Mamy po prostu nazwę właściwości, którą weryfikujemy, konkretną wartość i zazwyczaj jakiś prefix w nazwie metody. Ja preferuję dla kolekcji prefix contains, a dla konkretnych właściwości with lub has. Ale jest to już kwestia umowna, co komu lepiej leży.

Jak więc tworzyć takie asercje?

public class PlanetSetAssert extends AbstractAssert<PlanetSetAssert, Set<Planet>> {

    private PlanetSetAssert(Set<Planet> actual) {
        super(actual, PlanetSetAssert.class);
    }

    public static PlanetSetAssert assertThat(Set<Planet> actual) {
        Assertions.assertThat(actual)
                .isNotNull();
        return new PlanetSetAssert(actual);
    }

    public PlanetSetAssert containsPlanetWithName(String expectedPlanetName) {
        for (Planet planet : actual) {
            if(planet.getName().equals(expectedPlanetName)) {
                return this;
            }
        }
        throw new AssertionError("Actual Set doesn't contains Planet with name: " + expectedPlanetName);
    } 

Aby stworzyć własną, fachową asercję, należy rozszerzyć klasę AbstractAssert. Ta przyjmuje za dwa typy generyczne kolejno samą siebie i typ który będziemy obsługiwać, w tym przypadku Set<Planet>. Później definiujemy konstruktor, który jako argument bierze badany obiekt. Konstruktora nie będziemy wywoływać poza obrębem tej klasy, więc można go uczynić prywatnym. Korzystać natomiast będziemy ze statycznej metody fabrykującej assertThat(), która to jako jedyna ze zdefiniowanego wcześniej konstruktora korzysta. Ja preferuję w tym miejscu od razu sprawdzić, czy przekazany obiekt nie jest null’em. Oficjalny tutoral (swoją drogą bardzo dobry, z licznymi komentarzami) preferuje wykonywać to sprawdzenie w każdej metodzie, ale jak dla mnie to jest niepotrzebna redundancja.

Na koniec jest przykładowa implementacja metody, sprawdzającej, czy w danym zbiorze znajduje się planeta o podanej nazwie. W tym przypadku metoda zwraca this, co pozwala wywoływać łańcuchowo dalsze asercje na zbiorze planet. Minusem takiego podejścia jest konieczność definiowania własnego komunikatu błędu, w przypadku niepowodzenia – linia 19. W przypadku gdy zależy nam na sprawdzeniu, czy dany zbiór zawiera szukany element, można tą samą funkcjonalność zaimplementować troszkę inaczej.

public PlanetSetAssert containsPlanetWithName2(String expectedPlanetName) {
 Planet expectedPlanet = new Planet(expectedPlanetName);

 Assertions.assertThat(actual)
   .usingElementComparator(new PlanetNameComparator())
   .contains(expectedPlanet);
 return this;
}

private class PlanetNameComparator implements Comparator<Planet> {
 @Override
 public int compare(Planet p1, Planet p2) {
  return p1.getName().compareTo(p2.getName());
 }
} 


W tym przypadku, za pomocą metody usingElementComparator(), podmieniamy sposób porównywania elementów w kolekcji. Bardzo użyteczny mechanizm. Nadpisujemy tylko typowego porównywacza (komparatora) i gotowe. I co fajniejsze, to dostajemy bardzo przejrzysty komunikat błędu:

java.lang.AssertionError: expecting:
<[Venus, Earth, Mars]>
 to contain:
<[Mercury]>
 but could not find:
<[Mercury]>
 according to 'PlanetNameComparator' comparator
 at com.blogspot.mstachniuk.hamcrestvsfest.fest.PlanetSetAssert.containsPlanetWithName2(PlanetSetAssert.java:34)
  at com.blogspot.mstachniuk.hamcrestvsfest.fest.SolarSystemFestTest.shouldGenerateInnerPlanets(SolarSystemFestTest.java:24)

Właśnie przejrzyste komunikaty błędów (zwłaszcza wśród kolekcji) to siła tego narzędzia.

Teraz czas na moją ulubioną implementację tego typu metody, która to pozwala nam budować jeszcze bardziej płynące interfejsy:

public PlanetAssert containsPlanetWithName3(String expectedPlanetName) {
 Planet expectedPlanet = new Planet(expectedPlanetName);

 PlanetNameComparator comparator = new PlanetNameComparator();
 Assertions.assertThat(actual)
   .usingElementComparator(comparator)
   .contains(expectedPlanet);

 for (Planet planet : actual) {
  if(comparator.compare(planet, expectedPlanet) == 0) {
   return PlanetAssert.assertThat(planet);
  }
 }
 return null;
} 

Początek metody jest analogiczny jak w poprzednim przykładzie. Natomiast zamiast zwracać obiekt this, wyszukuję żądany element i zwracam już asercję, dla tej znalezionej planety. Dzięki temu mogę już sprawdzić inne jej właściwości. Właściwie, mógłbym w pętli wywołać zwykłą metodę equals() do wyszukania elementu, ale skoro mam już napisanego komparatora…

Wrcając jeszcze to całego kodu z listingu 3, to warto na końcu metody testowej jeszcze sprawdzić, czy w zwracanym wyniku, są tylko te planety, których się spodziewamy (jeśli tak mamy w wymaganiach). Przykładowa implementacja, jak to można zrobić:

public PlanetSetAssert containsOnlyPlanets(String... planets) {
 List<Planet> expectedPlanets = new ArrayList<Planet>();
 for (String planet : planets) {
  Planet p = new Planet(planet);
  expectedPlanets.add(p);
 }
 Assertions.assertThat(actual)
   .usingElementComparator(new PlanetNameComparator())
   .containsOnly(expectedPlanets.toArray(new Planet[expectedPlanets.size()]));
 return this;
}

Co ogólnie jest jeszcze fajnego w takich asercjach? To to, że sprawdzamy dokładnie to co chcemy sprawdzić, zamiast sprawdzać zawsze wszystko, jak było to w pierwszej wersji testu. Również zdefiniowałem sobie różne metody odnośnie sprawdzania występujących na danej planecie gazów atmosferycznych: withGas(Gases.OXYGEN), containsGases(Gases.NITROGEN, Gases.OXYGEN), containsOnlyGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN), doesNotContainGases(Gases.SODIUM). Do wyboru do koloru / potrzeby. Po więcej szczegółów odsyłam do wiki projektu.

Z ciekawostek, warto jeszcze zaprezentować generator FEST asercji. Można sobie to samemu przetestować, ściągając mój projekt z github’a i odpalając assertion-generator\planet.bat. Skrypt automatycznie kopiuje wygenerowaną klasę do podkatalogu fest2 gdzieś w hierarchii testowej, gdzie jednocześnie znajduje się test niej korzystający. Wystarczy zmienić nazwę pakietu i doimportować potrzebne klasy, aby test z powrotem działał.

Tak wygląda kod, który korzysta z przykładowo wygenerowanych asercji:

@Test
public void shouldGenerateInnerPlanets() {
 // given
 SolarSystem service = new SolarSystem();

 // when
 Set<Planet> innerPlanets = service.getInnerPlanets();

 // then
 Planet mercury = findPlanetByName(innerPlanets, "Mercury");
 assertThat(mercury)
   .hasRotationDirection(RotationDirection.LEFT)
   .hasDiameter(4_879_400)
   .hasSiderealYear(new SiderealYear(87.96935))
   .hasAcceleration(3.701)
   .hasAtmosphereGases(Gases.OXYGEN, Gases.SODIUM, Gases.HYDROGEN);

 Planet venus = findPlanetByName(innerPlanets, "Venus");
 assertThat(venus)
   .hasRotationDirection(RotationDirection.RIGHT)
   .hasDiameter(12_103_700)
   .hasSiderealYear(new SiderealYear(224.700_96))
   .hasAcceleration(8.87)
   .hasAtmosphereGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN);

 Planet earth = findPlanetByName(innerPlanets, "Earth");
 assertThat(earth)
   .hasRotationDirection(RotationDirection.LEFT)
   .hasDiameter(12_756_273)
   .hasSiderealYear(new SiderealYear(365.256_363_004))
   .hasAcceleration(9.806_65)
   .hasAtmosphereGases(Gases.NITROGEN, Gases.OXYGEN);

 Planet mars = findPlanetByName(innerPlanets, "Mars");
 assertThat(mars)
   .hasRotationDirection(RotationDirection.LEFT)
   .hasDiameter(6_804_900)
   .hasSiderealYear(new SiderealYear(686.960_1))
   .hasAcceleration(3.69)
   .hasAtmosphereGases(Gases.CARBON_DIOXIDE, Gases.NITROGEN);
}


Niestety musimy sami sobie dopisać metodę szukającą odpowiednią planetę, albo asercję dla Set<Planet>. W wygenerowanym kodzie jest jednoznaczna konwencja, że wszystkie metody sprawdzające nasze własności, dostają prefix has. Można sobie oczywiście to i sporo innych rzeczy zmienić w szablonach generowanego kodu.

A jak komuś mało takie generowanie kodu, to może sobie ten kod generować podczas builda maven’owego, dzięki Maven plugin for fest-assertion-generator. Można to również w moim przykładowym projekcie przetestować, usuwając pliki *Assert.java z katalogu: com\blogspot\mstachniuk\hamcrestvsfest\. Wówczas po odpaleniu mvn install, lub mvn test lub mvn generate-test-sources zostaną wygenerowane na nowo pliki z asercjami. Wówczas test SolarSystemFest3Test, który z nich korzysta, będzie nadal działał.

Ostatnio pojawił się jeszcze plugin do Eclipse’a: FEST eclipse plugin generujący prosto z tegoż środowiska nasze asercje. Zapowiada się ciekawie, więc zachęcam do skorzystania.

Pomyślnych asercji!

wtorek, 15 stycznia 2013

Modyfikowanie niemodyfikowalnych kolekcji w Javie

Jakiś czas temu w pracy natrafiłem na coś, czego na pierwszy rzut oka nie zrozumiałem czemu nie działa. Mianowicie była tworzona niemodyfikowalna lista, zawierająca pewne elementy. Jednak po chwili lista stawała się pusta. Samo usuwanie z listy działa poprawnie (tzn. rzuca wyjątkiem), Kod poniżej:
public class ModifyUnmodifiableListTest {

    final List<String> exampleList = new ArrayList<String>();

    @Before
    public void setUp() {
        exampleList.add("text 1");
        exampleList.add("text 2");
        exampleList.add("text 3");
    }

    @Test(expected = UnsupportedOperationException.class)
    public void shouldThrowExceptionWhenModifyUnmodifiableList() {
        List<String> unmodifiableList = Collections
                .unmodifiableList(exampleList);
        unmodifiableList.clear();
    }

Operacja clear() wyrzuca UnsupportedOperationException zgodnie z oczekiwaniem. Poniżej kod który mnie zadziwił:

    @Test
    public void shouldModifyUnmodifiableList() {
        // given
        List<String> unmodifiableList = Collections
                .unmodifiableList(exampleList);

        // when
        exampleList.clear();

        // then
        assertEquals(0, unmodifiableList.size());
    }

Test przechodzi, czyli na nowoutworzonej liście nie ma elementów!

Co się stało? Otóż metoda jak można przeczytać w dokumentacji:
Returns an unmodifiable view of the specified list
Zwraca nam widok (projekcję) na oryginalną listę. Czyli źródłową kolekcje można dalej modyfikować!

Jak można więc stworzyć niezależną kopię kolekcji? A no znajdzie się kilka sposobów. Jednym z prostszych jest skonwertowanie kolekcji do tablicy:
    @Test
    public void shouldCloneUnmodifiableListToArray() {
        // given
        String[] tab = exampleList.toArray(
                new String[exampleList.size()]);

        // when
        exampleList.clear();

        // then
        assertEquals(3, tab.length);
        assertEquals("text 1", tab[0]);
    }

Jak komuś koniecznie potrzebna lista lub inna kolekcja, to można z powrotem z tablicy zrobić kolekcje.

Inną możliwością jest metoda na piechotę, czyli ręczne dodanie elementów kolekcji do nowej kolekcji.
    @Test
    public void shouldCloneUnmodifiableListInForLoop() {
        // given
        List<String> list = new ArrayList<String>();
        for (String s : exampleList) {
            list.add(s);
        }

        // when
        exampleList.clear();

        // then
        assertEquals(3, list.size());
        assertEquals("text 1", list.get(0));
    }

Można jeszcze próbować walczyć z Collections.copy(...) ale tutaj lista docelowa musi mieć co najmniej tyle samo elementów, co lista źródłowa.

Rozwiązanie jakie mi jeszcze wpadło do głowy to wykorzystanie Apache Commons a dokładniej SerializationUtils.clone(...).

Jest to już bardziej uniwersalna metoda robienia głębokiej kopii, która działa na wszystkim co jest serializowane.

A jakie jest rozwiązanie najprostsze? Po prostu skorzystać z konstruktora docelowej kolekcji:
    @Test
    public void shouldCreateNewListBasedOnList() {
        // given
        List<String> list = new ArrayList<String>(exampleList);

        // when
        exampleList.clear();

        // then
        assertEquals(3, list.size());
        assertEquals("text 1", list.get(0));
    }

To tyle. Cały kod to pobawienia się dostępny jest na githubie: ModifyUnmodifiableList.

wtorek, 11 grudnia 2012

Pierwszy Legacy Code Retreat


W sobotę miało miejsce niezwykle wydarzenie, mianowicie drugi Global Day of Code Retereat. Jako że niedawno było organizowane w Wolfsburgu to wydarzenie, postanowiono tym razem zrobić Legacy Code Retreat dla odmiany. Całość odbyła się z inicjatywy tych samych osób i w tym samym miejscu i sponsoringu.

Podczas Legacy Code Retreat pracuje się z już gotowym kodem, który można znaleźć na githubie: Trivia Game. Kod jest przygotowany w kilku językach i jest właściwie nie testowany. Osiągnięto to za pomocą metod zwracających void, wymieszaniu wyświetlania wyników z logiką, niejasne przeznaczenie zmiennych i boską klasę. Zadaniem uczestników jest ogarnięcie tego chaosu programując w parach.

Miałem dobre przeczucie podczas pierwszej sesji, aby początkowo stworzyć test testujący integracyjnie to co jest. Z założenia aktualny stan jest fachowo poprawny, więc nawet jak się widzi coś w kodzie co wydaje się nie prawdą, należy to zaakceptować. Taki całościowy test nazywa się Golden Master. Najlepiej stworzyć ich kilka i nie mogą one nigdy być czerwone, gdy będziemy modyfikować kod.

Jak już mamy kilka testów, możemy zacząć refaktoring. Najlepiej taki, do którego wsparcie daje nam IDE, gdyż wtedy nie powinniśmy nic zniszczyć. I tak powoli można pisać bardziej szczegółowe testy, konkretnych metod.

Podczas Legacy Code Retreat można ćwiczyć m.in. następujące rzeczy:
  • tworzenie Golden Master
  • pisanie testów dla nietestowanego kodu
  • testowanie poprzez partial mocking (nazywane również Subclass To Test)
  • refaktoring do kodu obiektów
  • refkatoring do kodu czysto funkcyjnego
  • refaktoring do ValueObjects

Miałem podczas warsztatów okazję przez dwie sesje pracować z kodem Scalowym. Jednak słabe wsparcie środowiska i mała przerwa w eksplorowaniu tegoż języka, sprawiły, że było bardzo ciężko przemieścić się do przodu. Inna sprawa to standardowe ułomności Eclipse’a, ale to już zasługuje na osobny post. Podczas kilku sesji korzystałem też z IntelliJ IDEA i tam już było trochę lepiej. Więcej nie opisuję, aby nie psuć wam ewentualnej zabawy z Legacy kodem, podczas warsztatów, które sami u siebie zorganizujecie.

Co do jakości i organizacji eventu nie ma się do czego przyczepić. Nie było problemów ze stołami, przedłużaczami, rzutnikami. Były naklejki z imionami, odliczanie czasu podczas każdej sesji, kanapki na śniadanie, kawa, herbata, soki, Club Mate i inne napoje. Obiad również był dobry, ciepły, wystarczył dla wszystkich i jednocześnie nie wiele się zmarnowało. Zabrakło mi jedynie videokonferencji z innymi miastami, jak to było rok temu. A to ja w sumie mogłem podsunąć pomysł organizatorom. Niestety jakoś mi to uciekło.

Osobiście wolę tworzyć nowy kod, niż grzebać w starym, tym bardziej w Legacy. Z tego powodu impreza podobała mi się trochę mniej niż typowy Code Retreat. Z drugiej strony, w naszej profesji umiejętność pracy z nieswoim kodem jest właściwie niezbędna i trzeba ją wykorzystywać od czasu do czasu. Oby taki sytuacji było jak najmniej, więc dbajmy o to, aby nie tworzyć Legacy Kodu.

poniedziałek, 10 grudnia 2012

Java Developers Day 2012


Długo się zbierałem do napisania tego postu, ale w końcu się udało, skoro to czytacie. Kurs podstaw programowania funkcyjnego w Scali dobiegł końca, zaliczony na 96.5 %, certyfikat ściągnięty, spadł śnieg, motywacja wzrosła, więc można się zająć blogiem.

Co do samej konferencji Java Developers Day, to się do niej zraziłem w 2010 roku. Tym razem jednak wygrałem darmową wejściówkę, więc głupio by było nie skorzystać. Pojechałem więc do Krakowa.

Pierwsza prezentacja na jakiej byłem: The dark art of performance tuning or how to become a performance hero without spending a penny on tools, Leonida Igolnika. Facet pracuje w Oracle jako “Vice President of Product Development” I gadał ponad 5 minut o sobie. Jak to mówią, ten co dużo mówi o sobie, ma mało do powiedzenia na temat prezentacji, więc miałem małe obawy co do wykładu. Jednak szybko się pozytywnie rozkręciło. Leonid przekonywał nas, że w przypadku problemów z wydajnością naszych aplikacji, nie powinniśmy patrzeć do kodu, a na narzędzia.

I tak na początek musimy poznać nasze środowisko (system operacyjny) jaki i Runtime Environment. Prelegent pokazał jak można to zrobić na Unixie. Chcąc sprawdzić parametry JVM na Windowsie, można odpalić (oczywiście z konsoli) jps, aby poznać PID naszej aplikacji / serwera aplikacji. Następnie za pomocą jinfo można sprawdzić, co tak naprawdę jest uruchomione (np. na produkcji) i z jakimi parametrami VM.

Następnie za pomocą jmap -heap PID można sprawdzić używaną implementację GC i statystyki pamięci. Narzędzie to posiada jeszcze kilka ciekawych opcji: zrzuty pamięci, histogram obiektów heap’u i inne statystyki. To na co należy szczególnie zwrócić uwagę, to ile zajmują Stringi w naszej pamięci, gdyż one siedzą w przestrzeni PermGen (permanent generation). Problem ten nie występuje w Javie 7, gdyż teksty nie są dłużej alokowane w tym obszarze pamięci, a na heap’ie. Rozwiązuje to kilka problemów. Więcej na ten temat można przeczytać na Java SE 7 Features and Enhancements.

Coś było jeszcze wspomniane o Kirku Pepperdine ekspercie od tuning’owania Javy i autorze słów: „dominating consumer of the CPU”. Prelegent polecał również to video: Everything I Ever Learned about JVM Performance Tuning @twitter. Można się z niego dowiedzieć, ile zajmują w pamięci instancje prostych obiektów, jak jest reprezentowana wartość null w polach klas i jak często się uruchamia full GC na serwerach Twittera.

Następnie było przedstawiane chyba najlepsze darmowe narzędzie do profilowania, czyli jVisualVM. Łączy ono przedstawione wcześniej aplikacje konsolowe i jest rozszerzalne przez pluginy. Prelegent pokazał, że jak mamy duże zużycie CPU i jest to głównie System Time, to może to być problem z przełączaniem kontekstu.

Później Leonid przedstawił plugin do jVisualVM o nazwie TDA - Thread Dump Analyzer. Pomaga  on nam analizować stacktrace’y. Był też przykład błędnego użycia WeakHashMap i synchronized. W końcu niemal każde użycie tego słowa kluczowego, jest informacją, że tu gdzieś się czai błąd. Było jeszcze o narzędziu GCViewer, do analizowania tego co Garbage Collector wypluwa, o opcji -XX:-HeapDumpOnOutOfMemoryError i o Memory Analyzer - plugin’ie do Eclipse, który pozwala przeanalizować referencje pomiędzy obiektami.

Po takim wykładzie aż chce się mieć problemy w projekcie i możliwość dostania tego często jakże niewdzięcznego zadania, tylko po to aby pobawić się tymi zabawkami, jakie przedstawiał prelegent. Jak dla mnie najlepsza prezentacja podczas całej konferencji, mimo że gościu jest z Oracle’a.

Następnie byłem na prezentacji Jarosława Pałki: The deconstruction of architecture in times of crisis. Prelegent mówił, że ostatnio za bardzo się skupiamy na frameworkach (technology masturbation), że one rozwiązują jedynie problemy ich twórców, zamiast bardziej skupić się na tym jak dostarczyć funkcjonalność naszemu klientowi. Była wyjaśniona zasada działania Tragedii wspólnego pastwiska i jak to może prowadzić do upadku projektu.

Z ważnych tematów, jakie wyniosłem z tej prezentacji, to gdy pewien zasób jest współdzielony przez wiele procesów, to procesy powinny mieć wyłączność do danego zasobu. Przykładowo procesy batch’owe, robiące coś na bazie danych, powinny mieć w czasie swojego działania współdzieloną bazę tylko na wyłączność. Rozwiązuje to wiele problemów. Ważne jest również monitorowanie metryk systemu. Ogółem prezentacja fajna sporo przykładów z życia, ale trochę mało konkretów.

Następnie byłem na prezentacji Rebecci Wirfs-Brock Why We Need Architects (and Architecture) on Agile Projects. Prelegentka mówiła, że małe, niekrytyczne projekty nie potrzebują wiele zajmowania się architekturą. Według niej, architekt powinien być odpowiedzialny za:

  • redukcję technicznego długu
  • integrację nowej wiedzy z kodem (czyli refactoring, redesign, sprzątanie kodu)
  • testy jednostkowe
  • standardy kodowania
  • zwięzłość (użycie API, logowanie, obsługa błędów)

Generalnie kiedy zespół jest większy niż 9 osób, to należało by się w jakiś sposób podzielić i skoordynować działania pomiędzy grupami. Przy dużych projektach, powinno się dodatkowo poświęcić iterację zerową na eksperymenty i prototypy, aby można było dobrać odpowiednią architekturę. Powinna również w projekcie znajdować się osobna tablica dla architektonicznych tematów.

Następnie udałem się na wykład Sławka Sobótki pt. Ewolucyjna Destylacja Architektury – myślenie wizualne na przykładzie Ports&Adapters. Dla tego prelegenta, architektura to segregacja kodu na kawałki. Bardzo ciekawe stwierdzenie, które przypadło mi do gustu. Sławek pytał uczestników, jak wyobrażamy sobie kod. W końcu piszemy go codziennie, więc jakąś postać w naszej pamięci powinien on mieć. Ja sam do dzisiaj nie mam jednoznacznej odpowiedzi na to pytanie. Z jednej strony widzę interakcję pomiędzy obiektami, a z drugiej myślę wymaganiu które aktualnie implementuję i formalizuję je do postaci zrozumiałej dla kompilatora. Kod wyobrażam sobie w tym przypadku jako transformację żądanej odpowiedzialności w tekst. Nie mam na pewno w głowie diagramów klas w stylu UMLa, gdyż raczej na co dzień uprawiam TDD. Będę musiał sobie w pracy przypomnieć tą kwestię, to może odpowiem sobie wtedy na to pytanie.

To o czym było mówione, można znaleźć na prezentacji: http://prezi.com/p0psif9qixgz/ports-adapters/ Z ważniejszych aspektów, to należy wymienić różne, możliwe architektury aplikacji:

  • Micro Kernel
  • Pipes & Filters
  • Layers
  • CQRS
  • oraz tytułowe Ports & Adapters.

Odnośnie ostatniej architektury była poświęcona prezentacja, wraz z wyjaśnieniami na przykładowym kodzie. Było również trochę o Sadze, oraz o możliwych problemach, jakie mogą wystąpić podczas stosowania Ports & Adapters.

Prezentacja była pełna żartów, ciekawa i trzymała wysoki poziom. Część rzeczy już widziałem na innych prezentacjach, ale prelegent wyjaśnij to na swoim blogu we wpisie: Materiały z konferencji Java Developers Day. Generalnie zbierając wiedzę rozsianą po różnych prezentacjach Sławka, kształtuje mi się coraz bardziej wyraźnie, rozwiązanie znane pod nazwą DDD, którym namiętnie od jakiegoś czasu zajmuje się prelegent. Pytanie tylko, czy ludzie będą chcieli zmienić swoje podejście i wyjść po za ramę modelu trójwarstwowego? I czy to podejście wejdzie do kanonu nauczania na studiach informatycznych?

Następnie udałem się na prezentację Pawła Badeńskiego The Catcher in the Code. Na tej prezentacji siadłem trochę za blisko i nie za bardzo widziałem slajdy (mównica zasłaniała). Wykład był bardzo ogólny, a jego przesłaniem było, że powinniśmy pisać czysty kod. Było o 4ch ważnych regułach:

  • pisaniu takiego kodu ze świadomością, jakby osoba która musi później z nim pracować, była seryjnym mordercą i wiedziała gdzie mieszkasz
  • nazwy metod i zmiennych należy tak samo dokładnie dobierać jak się wybiera imię dla swojego dziecka
  • write your code with your brain in mind
  • stop codding, start telling the stories (cieżko było mi to jakoś sensownie przetłumaczyć)

Ogółem nie lubię jak na konferencjach są takie luźne, niewiele uczące prezentacje. Temat czystego kodu przewija się już od kilku lat i myślę, że na konferencję przychodzą osoby, które już wiedzą o tym.

Następnie byłem na prezentacji Piotra Buckiego o XSS. Generalnie ataki Cross-site scripting dzieli się na persistent (czyli zapisany na serwerze) i non-persistent, czyli najczęściej zmodyfikowany URL. Jak się bronić przed tego typu atakami? Przede wszystkim filtrowanie danych wejściowych nazywane po angielsku Sanitization. Dzięki temu widzimy później w naszej bazie danych typowo HTMLowe krzaczki. Technika te jest ważna, ale zazwyczaj nie wystarczająca. Zalecanym rozwiązaniem jest konwersja HTML’a do DOM’a i stosowanie białej listy dozwolonych tagów.

Inną możliwością jest escape’owanie danych wyjściowych. Czyli jak komuś uda się zapisać złośliwy skrypt w bazie danych, to lepiej go już wyświetlić w postaci tekstowej na stronie zamiast pozwolić się wykonać. Należy jednak pamiętać, aby escape’ować do odpowiedniego kontekstu.

Było jeszcze przejście po frameworkach Javowych, z wyszczególnieniem jakich konstrukcji używać, a jakich nie i kilka ogólnych przykładów podatności. Ogółem wykład średni, jakoś nie porwał ani mnie, ani publiki (żadne pytanie nie padło). Czegoś mi ewidentnie w tej prelekcji brakowało. Może jakiś konkretny przykład, że XSS jest poważnym zagrożeniem by lepiej podziałał na wyobraźnię uczestników?

Następnie udałem się na wykład Hardy Ferentschik’ na temat Hibernate Search. Prelegent jest developerem tegoż mapera obiektowo relacyjnego. Hardy pokazał, jak można korzystać z Hibernate Search. Jest to projekt, który bazuje na Lucynce, zintegrowany oczywiście z Hibernatem. Wadą projektu Apache jest konieczność dobudowania indeksu, gdy nastąpiły jakieś zmiany w bazie. Hibernate Search wprowadził więc przyrostowe aktualizowanie indeksu. Czyli jak coś aktualizujemy / zapisujemy w bazie, to równocześnie jest aktualizowany indeks Lucene. Wszystko jest oczywiście konfigurowalne za pomocą adnotacji, czyli definiujemy, po których polach klasy będzie możliwe wyszukiwanie.

Chcąc wyszukać już cos konkretnego z naszego zbioru danych, można skorzystać z API dostarczonego przez Lucene, lub z Hibernate’owego Query DSL’a. Było jeszcze o wydajności projektu, projekcjach i paru innych możliwościach projektu.

Drugiego dnia konferencji udałem się na prelekcję Henri Kerola na temat Vaadina. Pamiętam, że na 33 Degree byłem zachwycony prezentacją tegoż frameworka. Tutaj jednak już po minucie, wiedziałem, że długo na wykładzie tego pana nie wysiedzę. Niestety ten pan się kompletnie nie nadaje do występów publicznych. Początkowo było trochę o frameworku, aż w pewnym momencie zaczęło się kodowanie na żywo. Henri pokazał, jak łatwo można spiąć bazę danych z tym co jest w warstwie widoku i dostają za darmo Lazy Loading, podczas przewijania listy użytkowników w dół. Jednak wklejenie SQL’a bezpośrednio w kodzie UI bardzo mi się nie spodobało. Jak już uczyć, to bez antypatternów. W porównaniu z tym co pokazał Joonas Lehtinen na 33 Degree było to smutne.

Następnie chciałem iść na BDD Rafała Jamróza, ale zagadałem się na korytarzu, a sala była wypełniona po brzegi, a nie chciało mi się stać. Udałem się więc na prelekcję Adama Biena pt. Java EE–Future Is Now, But It Is Not Evenly Distributed Yet. Adam pokazał projekt mavenowy, z którego usuwał zależności, które są zbędne przy korzystaniu z Javy EE 6. Tworząc projekt w korporacyjnej szóstce nie potrzeba nam już web.xml’a, ani innych biblotek, które standardowo wrzucamy do nowych projektów.

Prelegent wyśmiał kilka konwencji, które są namiętnie bez zrozumienia stosowane na co dzień w projektach. Przykładowo postfix w nazwach klasy Impl. Co się wtedy stanie, gdy przyjdzie nam napisać kolejną implementację tego samego interface’u? Nazwiemy ją Impl2, Impl3, itd. No i po co nam interface’y, skoro mamy jedną implementację? Bo pewnego dnia trzeba będzie napisać inną implementację... Później było o tym co można w Javie EE 6 robić. Co ciekawe, prelegent używał na prezentacji NetBeans’a. Było to wielkie zaskoczenie dla mnie, gdyż dawno nie widziałem tego środowiska w akcji.

Później byłem n a wykładzie Patrycji Węgrzynowicz na temat bezpieczeństwa open source’owych bibliotek Javowych, których używamy na co dzień. Na początku było omówione, jak można oceniać podatności w aplikacjach, jak są przyznawane za to punkty i jak można scharakteryzować owe luki w oprogramowaniu. Później było sporo wykresów, bazujących na NVD. Patrycja początkowo skupiła się na serwerach aplikacji i co ciekawe to najbardziej dziurawe są te komercyjne. Było również trochę o framework’ach tj. Struts 2, Seam, GWT i szkoda że tylko o tych. Brakowało mi czegoś w tym wykładzie, jakoś mnie nie zaciekawił zbytnio.

Na koniec byłem jeszcze na wykładzie Martina Gunnarssona i Pär Sikö o JavieFX. Trochę dziwnie się patrzy na wykład prowadzony przez 2 osoby. Bo gdy jeden mówi to drugi nie zawsze wie co ze sobą zrobić. Panowie mówili fajnym angielskim, kompletnie bez akcentu, jednak jeden mówił zdecydowanie za cicho. Prelegenci pokazywali ciekawe dema i stworzyli pewną kabaretową atmosferę. Mam na myśli śmieszne dialogi, jakie pomiędzy sobą prowadzili. Niestety na zawartości merytorycznej już się nie skupiłem.

Po obiedzie pokręciłem się jeszcze trochę po konferencji, porozmawiałem z ludźmi i udałem się w stronę Wrocławia.

Z konferencji nie jestem zadowolony, nie za wiele z niej wyniosłem pod względem merytorycznym. Od strony organizacyjnej nie było się do czego przyczepić: dobre obiady (choć był problem z miejscem do jedzenia), impreza, miejsce konferencji... Tylko coś organizatorzy nie trafiają z doborem wykładów. Jak dla mnie JDD ma szansę być dobrą konferencją, ale chyba zawsze będzie tylko miała tą szansę.

poniedziałek, 5 listopada 2012

Listy w Scali

Ostatnimi czasy biorę intensywnie udział w kursie programowania funkcyjnego w Scali na portalu Coursera.org. Generalnie kiedyś się zraziłem do tego języka, gdyż jakiś tutorial i aktualnie używana przeze mnie wersja środowiska, powodowała, że przekopiowane przykłady nie działały. Teraz jest już trochę lepiej, aczkolwiek IDE do Scali dalej kuleją.

Na sam kurs zapisałem się zachęcony przez kolegę z pracy – fana Scali. Pomyślałem, że skoro nie będę robił tego sam, a będzie wsparcie w razie czego, to warto się zaangażować. I rzeczywiście jeszcze kilka znajomych robi ten kurs. Ostatnio nawet organizuję cykliczne spotkania w pracy, podczas których prezentujemy i porównujemy nasz kod. Oczywiście omawiamy stare zadania, które są już po deadline, aby nie łamać Honor Code. Jeśli macie taką możliwość, to zachęcam do organizowania podobnych aktywności u siebie.

Czy uczestnictwo w takim kursie coś daje? Nawet jeśli Scala nie trafi do powszechnego użytku, to na pewno uczestnictwo w kursie pomaga w zrozumieniu paradygmatu funkcyjnego i wynikającego z niego konsekwencji. Horyzonty się poszerzają, co ostatnio pomogło mi w lepszym zrozumieniu Java Scripta na darmowym kursie we Wrocławiu. Więcej o tym szkoleniu w innym wpisie.

Dla mnie wielką satysfakcją jest moment, kiedy dostaję olśnienia i nagle całkiem odmiennie patrzę na coś, co używam na co dzień. Podczas tego kursu był już taki moment, podczas rozwiązywania zadań z drugiego tygodnia. Należało wówczas zaimplementować zbiór (Set). Zadanie niby proste, ale nie gdy w kodzie mamy coś takiego:
type Set = Int => Boolean 
Długo nie mogłem zrozumieć, o co chodzi. Czytałem wątki na forum kursu, szukałem w Necie... W końcu zrozumiałem, że w tym przypadku Set został zdefiniowany, jako funkcja przyjmująca wartość liczbową, a zwracająca informację, czy dana liczba należy do zbioru, czy nie. Całkiem odmienne spojrzenie, na coś co zawsze było pudełkiem przechowującym elementy. Jak widać, można inaczej. Co prawda sprawia to parę problemów (możliwy zakres elementów), ale wyjście po za tą ramę było dla mnie niesamowite.

Dobra, koniec tych przemyśleń, czas na konkrety. Listy towarzyszą uczestnikom kursu już od samego początku. Zostały one jednak dokładniej przedstawione, dopiero podczas piątego tygodnia.

Listy w Scali są niezmienne. Co prawda istnieje alternatywna implementacja: MutableList, ale chodzi o to aby używać niezmienników. Wiadomo dlaczego. Nie oznacza to jednak, że jak wrzucimy element do listy (a ściślej mówiąc stworzymy listę zawierającą dany element), to nie możemy zmienić stanu tegoż obiektu. Owszem możemy. W Javie było by to tak:
class Person {
    private final String name;
    public int variable = 0;

    public Person(String name, int variable) {
        this.name = name;
        this.variable = variable;
    }

    public void change() {
        variable = variable + 42;
    }
}

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Person("Jan", 0));
        List immutableList = Collections.unmodifiableList(list);
        System.out.println(immutableList.get(0).variable);
        immutableList.get(0).change();
        System.out.println(immutableList.get(0).variable);
    }
} 

W Scali można analogicznie:
class Person(name: String, var variable: Int) {
  def change() {
    variable = variable + 42
  }
}

object Main {

  def main(args: Array[String]) {

    val person = new Person("name", 0)
    val list = List(person)
    println(list(0).variable)
    list(0).change()
    println(list(0).variable)
  }
} 

Tworzenie list w Skali mocno różni się od list w Javie. Z racji ich nie zmienności, musimy ich zwartość zdefiniować w momencie tworzenia.
val person = new Person("name", 0)
val list = List(person) 

Warto przy tej okazji zobaczyć, do czego będzie w tym momencie ewaluowane wywołanie List(person). Otóż List w Scali to:
  • abstakcyjna klasa List
  • objekt List
Klasa i objekt jednocześnie? Prawie. W Scali słówko kluczowe object oznacza definicję singletona. Po prostu ten wzorzec został wbudowany w język. Z praktycznego punktu widzenia, oznacza to, że możemy mieć tylko jedną instancję tegoż obiektu. Nie trzeba więc używać słówka new w tym przypadku.

Z drugiej strony, możemy przecież wywołać naszego singletona wielokrotnie z różnymi argumentami.
val list = List(person)
val list2 = List(person)
Jakby podejrzeć adresy w pamięci, to się okaże, że nie wskazują one tego samego miejsca. Co się więc dzieje? Jeśli chcemy skorzystać z dobrodziejstwa niepisania new, należy w naszym object’cie zdefiniować metodę apply() zwracającą żądaną instancję. Oczywiście metoda ta może przyjmować argumenty i zwraca żądaną instancję. Pełni wiec to bardziej rolę Fabryki niż singletonu. Jak to jest zdefiniowane dla listy? a no tak:
override def apply[A](xs: A*): List[A] = xs.toList 
Czyli na naszych argumentach (typu WrappedArray) zostanie wywołana metoda toList() i zadnie będzie gdzieś tam daleko delegowane...

No dobra, a jakiego typu listę otrzymamy? Patrząc na List.scala to można zobaczyć, że są dwie klasy rozszerzające tą Listę:
  • case object Nil extends List[Nothing]
  • final case class ::[B](...) extends List[B]

Pierwsza konkretna implementacja to element końcowy listy, za którym nic już nie ma. Natomiast dwukropek dwukropek, to nazwa klasy! Trochę się zszokowałem, gdy to zobaczyłem – rozumiem nazwać tak metodę, ale klasę? Jak widać można. Klasa ta tak naprawdę skleja głowę z ogonem. Implementacja jest bardzo prosta:
final case class ::[B](private var hd: B, private[scala] var tl: List[B]) extends List[B] {
  override def head : B = hd
  override def tail : List[B] = tl
  override def isEmpty: Boolean = false 

A samego operatora używamy zazwyczaj w bardziej czytelnej formie
val person = new Person("name", 0)
val person2 = new Person("name2", 0)
val list = List(person2)
val list2 = person :: list 

A jak za pomocą :: utworzyć jednoelementową listę? Należy skorzystać ze wspomnianego już Nil’a:
val list3 = person :: Nil 
Analogicznie jak :: działa +: Ten drugi operator (a właściwie metoda) dodatkowo tworzy kopię bazowej listy. Możemy również dodawać elementy na końcu listy, ale raczej nie jest to optymalne. W tym celu używamy :+. Przykład:
scala> val text = List("Ala", "ma")
text: List[java.lang.String] = List(Ala, ma)

scala> val text2 = text :+ "kota"
text2: List[java.lang.String] = List(Ala, ma, kota) 

Można również sklejać listy. Do tego używamy potrójnego dwukropka ::: Przykład:
scala> val numbers = List(4, 6, -3, 1)
numbers: List[Int] = List(4, 6, -3, 1)

scala> val numbers2 = List(2, 3)
numbers2: List[Int] = List(2, 3)

scala> val numbers3 = numbers2 ::: numbers
numbers3: List[Int] = List(2, 3, 4, 6, -3, 1) 

Operator ten dokleja nowe elementy na początku istniejącej listy. Podobnie działa dwuplus ++, który dodatkowo tworzy kopię Listy:
scala> val numbers4 = numbers2 ++ numbers
numbers4: List[Int] = List(2, 3, 4, 6, -3, 1) 

Nie wiem jak by to można było na przykładzie przedstawić (a debugować mi się nie chce), po prostu musimy ufać dokumentacji.

Uff przebrnęliśmy szczęśliwie przez te ‘śmieszne’ operatory. Muszę przyznać, że dla początkującego adepta Scali, są te operatory trochę niejasne i mylące, dlatego warto poświęcić im chwilę. Istnieje jeszcze kilka metod produkujących listy:
xs.reverse - zwraca odwróconą listę
xs updated (n, x) - zwraca listę zawierającą to samo co xs, ale w miejscu o indeksie n wstawia x’a
xs.toList – metoda dostępna w wielu innych typach

Co jeszcze ciekawego mamy w listach w Scali?

Mamy 3 fundamentalne operacje:
head – zwraca pierwszy element
tail – zwraca wszystko poza pierwszym elementem
isEmpty – zwraca true dla :: i false dla Nil

Ponadto lista zawiera sporo innych już mniej wydajnych operacji (last, init, take…), jak i takie które znamy dobrze z Javy (contains, indexOf…).

Scala udostępnia nam trochę więcej operacji na listach, niż mamy to w czystej Javie. I tak na przykład przekształcenie listy w listę zawierającą inne elementy. W Javie zazwyczaj piszemy petlę:
List<String> names = new ArrayList<String>();
for (Person person : persons) {
    names.add(person.getName());
} 

W Scali można to wykonać prościej za pomocą map():
val names = persons map (p => p.getName) 

Czyli dla każdego elementu p z listy persons wywołaj getName i zwróć to wszystko opakowane w listę i zapisz w names. Czy jest to czytelniejsze? Jak się pamięta co robi metoda map() i jeśli jest się w stanie sobie wyobrazić co oznacza p, to tak. O ile jak czytam własny kod to jestem w stanie przeanalizować co się dzieje, ale jak bym miał to zrobić z czyimś kodem, to bym się pewnie musiał dłużej zastanawiać, o co kaman.

Tą, jakże krótką i zwięzłą definicję, można jeszcze zapisać na kilka sposobów:
val names = persons map (p => p getName)
val names = persons map (_.getName)
val names = persons map (_ getName) 

Następną przydatną operacją jest filtrowanie, czyli pobranie podzbioru listy. W Javie to by było jakoś tak:
List<Person> personsStartsWithJ = new ArrayList<Person>();
for (Person person : persons) {
    if(person.getName().startsWith("J")) {
        personsStartsWithJ.add(person);
    }
} 

W Scali można to zapisać na jeden ze sposobów:
val personsStartsWithJ = persons filter(p => p.getName.startsWith("J"))
val personsStartsWithJ = persons filter(_.getName.startsWith("J")) 

Dodatkowo mamy metodę filterNot(), która zwraca elementy niespełniające podanego warunku. Można również wykonać te dwie operacje (filter i filterNot) jednocześnie:
val personsPart = persons partition(_.getName.startsWith("J"))
println(personsPart._1)
println(personsPart._2) 

Przykładowy efekt działania:
List(Person(Jan))
List(Person(Stefan), Person(Emil), Person(Dominik)) 

Czyli bardzo sensowne użycie tuple’a, pomagające zwrócić dwa wyniki z metody. Nie kojarzę żadnych standarowych metod z Javy zwracających dwa wyniki. Tak wiem że SRP, ale jak akurat potrzebujemy obydwie listy, to dwukrotna iteracja może rzutować na performance. W Javie trzeba to na piechotę robić:
List<Person> personsStartsWithJ = new ArrayList<Person>();
List<Person> personsNotStartsWithJ = new ArrayList<Person>();
for (Person person : persons) {
    if(person.getName().startsWith("J")) {
        personsStartsWithJ.add(person);
    } else {
        personsNotStartsWithJ.add(person);
    }
} 

To na razie chyba tyle co chciałem przedstawić, zapisać, zapamiętać i podzielić się tym z Wami na temat Scali. Czas wrócić do oglądania kolejnych wykładów...

wtorek, 16 października 2012

Logowanie interakcji w Mockito

Ostatnio przyszedł do mnie mój architekt i zaczął rozmowę coś w stylu:
Marcin, ty nasz ekspercie od testowania jednostkowego, integracyjnego i Mockito…
Ucieszony miłymi słowami pochwały i podbudowany faktem, że stanowię ważną część zespołu słuchałem dalej, co ma mi do powiedzenia kolega z zespołu. Wtedy jeszcze nie wiedziałem, że to pierwsze zdanie to tak naprawdę prosta, skuteczna, magiczna sztuczka, dzięki której bardzo łatwo jest później przekonać odbiorcę tego komunikatu, do wykonania pewnego zdania. To już trochę zahacza o manipulację / wywoływanie (ang. elicitation) / urabianie / programowanie neurolingwistyczne, czy też inne tego typu techniki, a więc wykracza po za tematykę tego bloga.

Wracając do rozmowy:
No bo właśnie mamy taki problem, że zrobiliśmy refaktoring niedawno stworzonej klasy i generalnie wszystko działa, ale trzeba jeszcze testy dopasować. Ten i tamten już poszli do domu, więc pytam czy byś tego nie zrobił. Uważam, że Tobie, jako że jesteś najbardziej w testach zorientowany, pójdzie szybko i gładko...
No dobra, myślę sobie. Dzisiaj kończy się czas przeznaczony na kodowanie w tym Sprincie, trzeba więc cos zdeployować, aby testerzy mieli jutro co robić. No dobra, obowiązki wzywają.

Architekt pokazał mi i opisał jakie to zmiany zostały wykonane. Generalnie był to pewien proces, który kodowaliśmy przez cały sprint. Miał on wiele danych wejściowych i generował sporo na wyjściu. No i wcześniej była sobie taka jedna metoda, która to miała 6 argumentów, kilka mocków i produkowała coś pożytecznego. Zostało to opakowane w fabrykę, która podawała teraz cześć danych od dupy strony (wywołanie innych serwisów), jak i również sama metoda została przeorana. Czyli liczba argumentów zmalała, doszło kilka zależności, nowy sposób instancjowania, no i trzeba był do tego przerobić testy.

Cofnięcie zmian nie wchodziło w rachubę, gdyż była to sprawka w sumie 3ch osób, a wykorzystywany system kontroli wersji (dziękujemy IBM Synergy) zrobił by więcej złego niż dobrego. Olanie testów jedynie przesuwało problem w czasie, a na syndrom wybitego okna nie mogłem sobie pozwolić.

Zacząłem więc to po kolei przerabiać testy, ich czytelność malała, a definicje zachowania mocków stawały się niezrozumiałe. Najgorsze że pierwszy test, który przerabiałem, ciągle nie działał. Nie wiedziałem w końcu czy to wina domyślnych wartości zwracanych przez Mocki z Mockito, czy też trefnego refactoringu (a raczej redesignu).

No i w tym momencie postanowiłem sprawdzić interakcje z moimi mockami, czy czasem gdzieś przypadkiem Mockito nie zwraca np. pustej listy, która to powoduje brak wyników. Chwila szperania w dokumentacji i znalazłem taki ciekawy twór. Przykładowy kod poniżej.
@Test
public void shouldCreateAccount() {
    // given
    InvocationListener logger = new VerboseMockInvocationLogger();
    AccountService accountService = new AccountService();
    StrengthPasswordValidator validator = mock(
            StrengthPasswordValidator.class,
            withSettings().invocationListeners(logger));
    UserDao userDao = mock(UserDao.class,
            withSettings().invocationListeners(logger));
    UserFactory userFactory = mock(UserFactory.class,
            withSettings().invocationListeners(logger));

    when(userFactory.createUser(LOGIN, PASSWORD))
            .thenReturn(new User(LOGIN, PASSWORD));

    accountService.setPasswordValidator(validator);
    accountService.setUserDao(userDao);
    accountService.setUserFactory(userFactory);

    // when
    User user = accountService.createAccount(LOGIN, PASSWORD);

    // then
    assertEquals(LOGIN, user.getLogin());
    assertEquals(PASSWORD, user.getPassword());
} 
Testujemy zakładanie konta użytkownika. Życiowy przykład, wiele nie trzeba tłumaczyć.

Na początek w linii 4tej tworzymy instancję obiektu typu VerboseMockInvocationLogger. Jest to standardowa implementacja interfejsu InvocationListener, która wypisuje na standardowe wyjście zaistniałe interakcje z mockami. Aby przekazać ten obiekt do naszych mocków, musimy podczas ich tworzenia, ustawić ten obiekt. Jak? Widzimy to w linii 8smej. Powtarzamy to dla każdego mocka, który nas interesuje.

Dalej standardowo. Za pomocą when() konfigurujemy Mock’a, injectujemy zaleznosci (linie 17-19) wywołanie metody testowej i assercje. Dzięki takiemu Testu możemy otrzymać interesujący output:

########### Logging method invocation #1 on mock/spy ########
userFactory.createUser("login", "password");
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.mockinvocations.AccountServiceTest.shouldCreateAccount(AccountServiceTest.java:31)
   has returned: "null"

############ Logging method invocation #2 on mock/spy ########
strengthPasswordValidator.validate(
    "password"
);
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.createAccount(AccountService.java:19)
   has returned: "0" (java.lang.Integer)

############ Logging method invocation #3 on mock/spy ########
userDao.findUserByLogin("login");
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.existAccount(AccountService.java:43)
   has returned: "null"

############ Logging method invocation #4 on mock/spy ########
   stubbed: -> at com.blogspot.mstachniuk.unittestpatterns.service.mockinvocations.AccountServiceTest.shouldCreateAccount(AccountServiceTest.java:31)
userFactory.createUser("login", "password"); 
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.createAccount(AccountService.java:23)
   has returned: "User{login='login', password='password'}" (com.blogspot.mstachniuk.unittestpatterns.domain.User)
Pierwsze wywołanie, to te w teście:
when(userFactory.createUser(LOGIN, PASSWORD))
        .thenReturn(new User(LOGIN, PASSWORD));
Niestety (a może stety) nie można ukryć tego wywołania w standardowym outpucie, chyba że się napisze własna implementację InvocationListener’a.

W wywołaniu #2 widzimy, że wywołano metodę validate() z parametrem "password" i zwrócono zero. Co prawda metoda ta zwraca void, ale to pierdoła i nie należy się nią przejmować. Co fajniejsze, to po kliknięciu na AccountService.java:19 zostaniemy przeniesieni w miejsce wywołania tej metody. Bardzo użyteczny feature.

Trzecia interakcja jest analogiczna, ale za to czwarta jest ciekawa. Widzimy z jakimi parametrami wywołano mockowaną metodę, z którego miejsca w kodzie i gdzie zostało to zachowanie zdefiniowane! Od razu można przejrzeć konfigurację testu i się zorientować co skąd się bierze. Jak komuś za mało / za dużo, to zawsze można samemu sobie zaimplementować InvocationListener'a. Dzięki temu można trochę lepiej poznać bebechy tej biblioteki.

To tyle odnośnie tej logowania interakcji w Mockito. Obyście musieli jej używać jak najrzadziej, gdyż jak dla mnie świadczy to o skomplikowaniu i zagubieniu we własnych kodzie. Warto jednak o czymś takim zawczasu wiedzieć, może czasem uratować tyłek. Mi dzięki temu udało się dojść co było nie tak.

Cały kod wykorzystywany w tym wpisie można ściągnąć z githuba, a prezentowany kod testowy znajduje się w klasie: AccountServiceTestWithMockInvocations.

Morał tej historii:
Zwracaj uwagę na to co i jak mówią inni i nie daj się zmanipulować! Zwłaszcza w pracy :)

sobota, 22 września 2012

Kolejny Code Retreat

Dzisiaj wziąłem po raz kolejny udział w imprezie spod znaku Code Retreat. Tym razem odbył się on w Wolfsburgu – czyli mieście największego europejskiego koncernu samochodowego. Zorganizowany został on przez znajomych z pracy, a sponsorem była firma T-Systems on site.

Podczas początkowych sesji mogłem podzielić się swoją wiedzą z zakresu programowania w parach, TDD, baby steps, pisania dobrych testów i znajomości IDE. Nie ma się tu co rozpisywać, bo ciekawsze działo się później.

Postanowiłem wykorzystać obecność fanatyków Scali i trochę się poduczyć tegoż języka. Zdobyta wiedza przyda się przy realizacji kursu: Functional Programming Principles in Scala. Kto się jeszcze nie zapisał, ma jeszcze szansę. Więcej o kursie można poczytać na scala.net.pl. Podczas mojej pierwszej scalowej sesji, udało mi się sporo samemu napisać i odświeżyć jakieś stare wiadomości na temat tego języka. Część wymagań nawet działała. Szkoda jednak, że Eclipse dalej kuleje w tym temacie. Mam nadzieję, że z Ideą będzie lepiej.

Podczas mojej drugiej sesji skalowej programowałem razem z jeszcze większym wyjadaczem (fanatykiem) tego języka. Było więc co chłonąć. Podczas tej sesji (w ogólności) ćwiczyliśmy pisanie bez pętli, co w Scali nie jest jakimś utrudnieniem. Udało nam się całą logikę zakodować wraz z testami i bez pętli, w czasie krótszym niż 45 minut i z wyjaśnieniem mi, jak działa ta cała magia! Co chwilę wlepiałem swoje ślepia w ekran, dziwiąc się, co ‘to’ lub ‘tamto’ robi, oraz próbując zrozumieć, czy to aby na pewno pokrywa wszystkie wymagania. Jestem pod bardzo dużym wrażeniem tej sesji. Teraz trzeba na spokojnie (albo pod wpływem ;) ) poznać wykorzystane mechanizmy tegoż języka, aby móc je później efektywnie stosować.

Wyniesione doświadczenie z tej sesji, pozwoliło mi jeszcze podczas ostatniego sparowania, ponownie zakodować wszystkie reguły gry. Czasem lubię się droczyć, z moimi partnerami podczas Code Retreat. Zwłaszcza przy ping pongu staram się tak odbić piłeczkę, aby się później dużo nie narobić, a jedynie obserwować, jak ktoś się męczy. Tym razem coś poszło nie tak, gdyż sam musiałem się napocić. O dziwo udało się po raz drugi dojść do końca, bez większych problemów, że coś nie działa i nie wiadomo dlaczego. Dodatkowo nie można było rozmawiać podczas tej sesji, co stanowiło dodatkowe utrudnienie. Spotkałem się z nim po raz pierwszy i przekonałem się, ile rzeczy można przekazać niewerbalnie.

Podsumowując wypadło super. Był to mój 6ty CodeRetreat (licząc te oficjalne jak i te zamknięte). Obiad był na wypasie, ciepły z dużymi możliwościami wyboru dania i z deserem. Również było sporo owoców (zamiast ciasteczek) i możliwość zjedzenia śniadania (jak ktoś nie zdążył rano). Ważnym punktem (który był strzałem w dziesiątkę) była Club Mate. Pomogła ona zebrać siły na ten jakże wyczerpujący wysiłek intelektualny, jakim jest Code Retreat. Nieźle się naYerbałem, a w ogólnym rozrachunku, to cała skrzynka poszła. Uważam, że napój ten, powinien wejść do formuły Code Retreat ;)

Teraz pozostaje już tylko czekać, na drugi Global Day of Coderetreat, który w tym roku przypada 8 grudnia. Rok temu 94 miasta wzięły w tym udział, a cel na ten rok to 200 miast. Zobaczymy czy się uda i jak spędzę ten dzień.