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

sobota, 14 grudnia 2013

I znów Global Day Of Code Retreat

Kolejny rok mija i kolejny Global Day of Code Retreat już za nami. W tym roku planowałem początkowo uczestniczyć w tym evencie w Wolfsburgu, ale ostatecznie wybrałem Berlin, aby się trochę zorientować, jak wygląda tamtejsza sytuacja projektowa i mieszkaniowa. A CR to zawsze dobra okazja do tego. To już kolejny raz (6ty, jeśli nie licząc jakiś mniejszych, wewnętrznych inicjatyw), kiedy biorę udział w tego typie wydarzeniu, więc widzę jak się ono zmienia w czasie. Rok temu prawie 200 miast na całym świecie organizowało CR, a w tym było 165 lokalizacji. Tendencja więc spadkowa.

Jako że Berlin uchodzi za bardzo Europejskie miasto(o czym wspominałem już kiedyś w poprzedniej relacji), spotkanie było prowadzone po angielsku. Jednak w międzyczasie można było usłyszeć sporo języka niemieckiego, jaki i polskiego:) Zaczęliśmy więc od sortowania uczestników bąbelkowo, aby zorientować się kto jest mocny w TDD, pair programingu, a kto jest kompletnie świeży. No i w ten sposób najlepsi zasiedli do wspólnego stanowiska pracy z kompletnymi świeżakami. Nie ma to jak nauczanie innych:)

W pierwszej sesji dostałem więc niemieckiego studenta, który w javie pisze od święta, studiuje coś tam powiązane z medycyną, a na co dzień klepie w php. Używa do tego Vim’a. Jest on do niego tak przyzwyczajony, że korzysta z ichniejszych skrótów klawiszowych bezpośrednio w Eclipsie. Byłem pod wrażeniem, choć wątpię, aby była to optymalna droga do wykorzystania w pełni możliwości Eclipse’a.

Drugą sesję pokodowałem z przyjacielem Szymonem, z którym wspólnie przyjechałem do Berlina. Pobawiliśmy się trochę Scalą, ale nie szło nam jakoś najlepiej. Na pewno jeszcze sporo czasu trzeba zainwestować w poznawanie tego języka, aby móc z niego swobodnie korzystać. Niestety kiepska pogoda w ostatnim czasie, brak śniegu i gorączka przedświąteczna, nie jest najlepszym czasem do nauki nowych rzeczy. Dlatego kurs na Courserze sobie troszkę odpuściłem:(

Trzecią sesję miałem sposobność spędzić z pewnym Syryjczykiem, który na co dzień jest adminem, ale chciałby robić jakieś przyjemniejsze rzeczy. Ta sesja była dla mnie bardzo wymagająca pod względem komunikacji, ze względu na mój nienajlepszy angielski, ciężki w zrozumieniu angielski partnera i jego trochę słabą wiedzę w zakresie Javy. Myślę, że ziomek się sporo ode mnie nauczył.

Po trzeciej sesji była mała retrospekcja. W grupach 3 osobowych, mieliśmy napisać na kartkach, co się nauczyliśmy i co chcielibyśmy się nauczyć. Kartki przykleiliśmy na szybę i chwilę o tym dyskutowaliśmy.

Później była przerwa obiadowa. Były kanapki, a nie porządny ciepły, pyszny obiad jak to jest w formule tego wydarzenia zapisane. No ale trudno, nie to jest przecież najważniejsze.

Na 4tej sesji znów próbowałem pisać w Scali z innym nowopoznanym kolegą z Wrocławia. Tutaj trochę ciężko było nam dojść do porozumienia, gdyż zmierzaliśmy w innych kierunkach: ja kładłem nacisk na TDD, a kolega na implementację i lukier składniowy, jak to można by lepiej i ładnie zrobić w Scali.

Po czwartej sesji musieliśmy jednak opuścić warsztaty. Podziękowania dla globalnych i kontynentalnych sponsorów wydarzenia, a także dla sponsora w Berlinie: ImmobilienScout24, gdzie odbywał się event. Podziękowania również dla Martina Klose (@martinklose), który jak zwykle stanął na wysokości zadania i dobrze poprowadził kolejny raz to wydarzenie.

A co się zmienia, jeśli chodzi o Code Retreat? W Berlińskiej edycji zastosowano fajny pomysł przy rejestracji. Należało zapłacić 15 Euro, które było później zwracane, gdy uczestnik się pojawił na spotkaniu. Motywowało to ludzi do przychodzenia, gdy już byli zapisani, a gdyby naprawdę coś komuś nagłego wypadło, to strata 15 E jest do przełknięcia. Uważam to za super pomysł.

Ponadto zauważyłem, że osoby bardziej doświadczone w TDD i w formule Code Retreat już nie tak licznie w nim uczestniczą. Może to wynika z braku chęci nauczania innych, lub są już zmęczenie problemem gry w życie? Albo stwierdzają, że już nic nowego nie mogą się nauczyć i wybierają inne, ciekawsze aktywności w tym czasie. Ciężko mi to oceniać, ale wydaje mi się, jakby młodsi niedoświadczeni uczestnicy byli bardziej widoczni na tego typu wydarzeniach. To oznacza, że nadchodzą nowi, pełni zapału i żądzy wiedzy programiści, a więc tym bardziej nie można zostawać z tyłu…

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!

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ń.

poniedziałek, 4 czerwca 2012

Code Retreat w Berlinie

W Niedzielę 3ciego czerwca wziąłem udział w warsztatach Code Retreat w Berlinie. Był to mój nie pierwszy udział w tego typu „imprezie”, ale pierwszy raz było to za granicą i w otwartej formie jednoczenie (wcześniej zorganizowaliśmy sobie CR w pracy). Co mnie bardzo zaskoczyło to wielka różnorodność kulturowa. Spodziewałem się, że event będzie prowadzony w języku niemieckim, ale jak się okazało w Berlinie mieszka sporo ludzi, którzy nie mówią w tym języku. Z drugiej strony, jak się okazało, łatwiej jest mówić o typowo technicznych sprawach w języku angielskim.

Pierwszą sesję kodowałem z Carlosem z Portugali. Przykładowo on mimo prawie rocznego już tutaj pobytu nie używał Niemieckiego, a w pracy oficjalnym językiem jest angielski, ze względu na sporą różnorodność kulturowa. Kodowaliśmy w Rubym, tzn. miałem okazję zobaczyć jak to można robić i poznać podstawy podstaw, ale ogółem nic konkretnego. Generalnie w Portugalii jest podobna sytuacja jak w Polsce, tzn. projekty są, praca jest, ale lepiej być gdzieś indziej.

Druga sesję miałem okazję po kodować z Grzegorzem Dziemidowiczem. W końcu była możliwość się poznania i sprawdzenia się przy klawiaturze. Była to moja najbardziej produktywna sesja podczas tego dnia jak i chyba podczas wszystkich Code Retreat’ów w jakich brałem udział. Obaj dobrze się poruszaliśmy po IntelliJ IDEA, była super komunikacja, zbliżone pomysły na design (czyli nie wiele konfliktów) i szybka ewolucja designu (ekstrakcja osobnych klas dla żywych i umarłych komórek, przeniesienie do nowych klas odpowiednich implementacji).

Udało mi się sprzedać fajnego hotkey’a do Idei. Mianowicie zamiast pisać assercję, można napisać warunek, jaki chcemy sprawdzić (tak jak by to był zwykły if), zaznaczyć tekst, wcisnąć Alt + Enter i wybrać opcję „Create JUnit Assertion”, aby zamienić porównanie w wywołanie odpowiedniej metody. Screen poniżej.


Podczas kolejnej sesji (tym razem z Łukaszem Balamutem) jak drugi raz pokazywałem ten skrót, to odkryliśmy, że nie zawsze on działa. Teraz jak to sprawdzałem, to doszedłem do tego, że jak zaznaczymy tekst od lewej do prawej, to popup pokazuje się bez tej (i innych opcji). Na szczęście ktoś już zgłosił ten błąd: IDEA-86707.

Z Łukaszem ćwiczyliśmy pisanie w Javie, przy czym na napisanie testu mieliśmy tylko dwie minuty. Jak stoper dochodził do końca, a my nie zdążyliśmy skończyć testu, to trzeba było usunąć kod i zacząć od nowa. Później analogicznie robiliśmy z implementacją. Ćwiczenie to miało na celu tworzenie małych prostych testów i prostej implementacji. Mi się kilka razy nie udało skończyć testu, gdyż nie była to moja klawiatura i bardzo ciężko mi się tworzyło test „na szybko”. Dla mnie pisanie testu, to czas w którym intensywnie myślę, co chcę testować, jak, jaką nazwę dobrać i czy test jest poprawny. Dwie minuty to troszkę za mało, nawet jak test jest niewielki.

Później trochę się posprzeczaliśmy na temat używania słówka should na początku nazwy każdej metody testowej. Łukasz twierdzi, że kiedyś było test (bo musiało być), a teraz ten „noise” zamieniliśmy na should i jak czytamy nazwy niedziałających testów, to i tak pomijamy ten prefiks.

Później wyskoczyliśmy całą grupą na obiad. Poszliśmy na jakieś burgery w pobliskim bistro. Panie z za lady bardzo się zaskoczyły nagłym zmasowanym atakiem żądnych mięsa programistów w Niedzielę, gdyż wcześniej nikogo w tym barze nie było. Co prawda nie była to pizza, ale taki lepszy fast food, smaczny i sponsorowany przez Nokię. Była w tym czasie możliwość pogadania z innymi uczestnikami.

Po obiedzie miałem jeszcze okazję pokodować z Janem. Tym razem rozmawialiśmy po Niemiecku.


Ostanie dwie sesje były bardzo szalone. Postanowiliśmy zrobić coś, z czym spotkałem się pierwszy raz. Mianowicie 6 par postanowiło zacząć kodować w Javie i wykonywać rotację jednej osoby co 10 minut. Do tego nie skasowaliśmy kodu pomiędzy sesjami. Bardzo smutne było, gdy trafiałem co chwila na tą samą trefną implementację jednego z uczestników, jednak ciężko było mi przeforsować jemu inny sposób spojrzenia na problem i odpuściłem. Ciężko, zwłaszcza podczas drugiej sesji, przebiegało wdrożenie w to, co się dzieje w projekcie, gdyż przez 10 minut ktoś musiał wytłumaczyć / pokazać w którym miejscu jesteśmy, jak to implementujemy i coś dopisać. W momencie gdy udało się już dopchać do klawiatury, to przychodziła już nowa osoba na sąsiednie stanowisko i role się odwracały.

O dziwo prowadzący stwierdził, że pierwszy raz widział, że ludzie podczas ostatniej sesji są tak mocno ożywieni. I rzeczywiście tak było. Te częste rotacje powodowały, ze trzeba było dawać z siebie wszystko, a stres z tym związany na pewno powodował wydzielanie się motywująco – pobudzających substancji w naszym ciele.

Na koniec oczywiście retrospekcja z całego dnia, podziękowania, wymiana kontaktami i wypad na piwko. Tam to dopiero całość spływa z człowieka i można jeszcze pogadać w luźniejszej atmosferze. No i jeszcze było sporo dzielenia się wrażeniami z Marcinem Saneckim i Rafałem w drodze powrotnej.

Podziękowania dla sponsorów @nokia@crealytics, @klosebrothers. Miejsce odbywania się imprezy było ciekawe, gdyż znajdowało się w niezbyt uroczej dzielnicy, a blok wyglądał jak była fabryka. Za to firma crealytics udostępniła małe przytulne biuro i Club Mate, które trzymało mnie cały dzień na nogach. Powinni wprowadzić ten napój w pracy obok / zamiast kawy. Ogółem organizacja była bardzo dobra, dojście oznaczone, a na ścianach wisiały ciągle reguły gry.


Podsumowując, poznałem ciekawą technikę wymiany rotacyjnej uczestników co 10 minut, z którą nie spotkałem się wcześniej. Podpatrzyłem kilka innych konwencji nazywania metod testowych i zorientowałem się jak wygląda praca w Berlinie (miasto start-up’ów), gdzie dominującym językiem jest angielski.

piątek, 30 marca 2012

Nowa ksiazka o testowaniu jednostkowym


Dnia 28 marca 2012 pojawiła sie oficjalnie książka: Practical Unit Testing with TestNG and Mockito napisana przez Tomka Kaczanowskiego. Wspominałem w jednym z poprzednich wpisów (na temat testowania wyjatków), że ta książka nadchodzi. Już oficjalnie jest dostępna do kupienia na practicalunittesting.com do czego zachecam.

Dlaczego o tym pisze?

Powodów jest kilka.

Po pierwsze jest to książka polskiego autora (co prawda po angielsku, ale spolszczenie jest w przygotowaniu), a tych należy wspierać. Łatwiej (podobno) jest napisać książkę techniczną po angielsku i później ją przetłumaczyć na nasze, niż pisać po polsku i później się męczyć.

Po drugie jest to kompletny, uporządkowany i aktualny zbiór technik z przykładami, jak pisać dobre testy. Książka wyjaśnia wszelkie wątpliwości związane z nazewnictwem, konwencjami i dobrymi praktykami, jak i daje wiele cennych wskazówek.

Dla kogo jest ta książka?

Książka jest bardzo dobra dla osób, które chcą właśnie zacząć pisać testy jednostkowe do swojego kodu i nie wiedzą jak wystartować. Książka wprowadza krok po korku do lepszego świata kodu pokrytego testami. Również dla startych wyjadaczy znajdzie się sporo cennych informacji. Dalsze rozdziały książki omawiają zaawansowane zagadnienia ze świata testowania. Tomek wskazuje również dobre praktyki jak pisać testy, a także prezentuje narzędzia powiązane z nimi(jak catch-exception czy testy mutacyjne).

Dlaczego więc się wypowiadam o tej książce, skoro dopiero co się ukazała?

Dobra, dość tej krypto reklamy, czas na konkrety ;)

Powodem dla którego piszę na blogu o tej książce, jest fakt, że brałem udział w jej powstawaniu. Mianowicie robiłem korektę (review) książki, za co dostałem podziękowania uwiecznione na jednej ze stron w książce:
In various different ways a number of people have helped me with writing this book – some by giving feedback, others by comforting me in times of doubt.
Marcin Stachniuk was the first person to offer to review the book in the early stages of its being written, and at the same time the most persistent of my reviewers. He read every part of the book and gallantly overcame the obstacles I frequently put in his way: frequent releases, constant juggling of the contents, minor piecemeal adjustments, etc.
Nadszedł teraz czas podzielenia się moimi wrażeniami ze wspólnej pracy z autorem.

Wszystko zaczęło się na konferencji GeeCON 2011 w Krakowie, Podczas (za)ostatniego dnia konferencji (tzw. Community Day) Tomek miał swoją prezentację pt.: Who watches the watchmen? - on quality of tests. Na prezentacji trochę się wynudziłem, gdyż otarłem się już o większość omawianych tam zagadnień. Pod koniec jednak prelegent pochwalił się, że właśnie pisze książkę o testach. Wówczas jeden z uczestników (nazwisko do wiadomości redakcji) zaproponował, że chętnie by zrobił review jego książki. Bardzo zaciekawiła mnie ta inicjatywa, i mimo iż wszyscy już wychodzili z sali, zostałem chwilę dłużej, aby przysłuchać się rozmowie na temat recenzowania książki.

Tomek zaproponował aby chętni się do niego zgłosili mail’owo, aby ustalić szczegóły współpracy.  I tak to się zaczęło. Stwierdziliśmy, że najlepszą formą wymiany informacji będzie założona na Google prywatna grupa dyskusyjna, poprzez którą Tomek będzie podsyłać nowe wersje książki. Tym kanałem można również podyskutować i wyjaśnić ewentualne kwestie sporne. Rozwiązanie to sprawdziło się.

Preferowanym sposobem zgłaszania uwag, było odsyłanie aktualnego pdf’a ze wstawionymi komentarzami w teksie, w formie żółtych karteczek. Funkcjonalność taką daje nam Foxit Reader. Dzięki temu Tomek widział dokładnie, którego miejsca dotyczy uwaga. Było to również wygodne dla mnie, gdyż fragmenty czytałem na ekranie komputera. Niektórzy zgłaszali swoje uwagi w postaci wiadomości na grupie, ale dla mnie żółte karteczki i tak były lepsze, gdyż dokładniej wskazywały miejsce, gdzie jest coś nie tak.

Na podstawie e-maili naliczyłem jakieś 20 „release’ów” książki, tzn. fragmentów, które Tomek podsyłał do korekty. Początkowo przychodziły pojedyncze rozdziały do czytania, ale lepszym wyjściem, jak się później okazało, było podsyłanie całości. Dzięki temu nie było rozdziałów wyrwanych z kontekstu i było widać ogólnego zarys całości książki, w którą stronę ona dąży.

Koleje wersje pojawiały się z różną częstotliwością. Czasem było kilka w tygodniu, a czasem była cisza przez dłuższy czas. Ale to już była kwestia Tomka jak sobie organizuje pracę nad książką. Ze swojej strony mogę powiedzieć, że często nowe wersje się pojawiały, jak akurat mocno byłem zajęty innymi sprawami i niemiałem możliwości aby usiąść od razu do lektury. Kilka razy dogoniła mnie kolejna wersja i wtedy już trzeba było się zmobilizować i przeczytać to co tam Tomek naskrobał :)

Co do zgłaszanych uwag, to Tomek zazwyczaj je akceptował. Jak nie widziałem poprawy w następnej wersji, to początkowo się czepiałem, że moje sugestie są nieuwzględniane, ale w późniejszym czasie doszedłem do wniosku, że nie powinienem się tym przejmować. W końcu to książka Tomka i Jego koncepcja, a nie moja. Jedynie sprawy edytorsko/estetyczne (zła czcionka, rozjeżdżające się tabelki) były dla mnie bardzo irytujące, ale to chyba przez spędzenie dawniej sporej ilości czasu z LaTeX’em. Tomek postanowił zostawić to sobie na sam koniec, przez co ciągle widziałem te niedoskonałości. To była chyba jedyna pierdółka, która mi troszkę przeszkadzała w naszej współpracy.

Moje zaangażowanie zostało wynagrodzone cytowanymi wyżej podziękowaniami w książce (Dzięki Tomek!). Właściwie to nie nawet nie ustaliliśmy szczegółów współpracy, tzn. co ewentualnie będę z tego miał. Do samego końca nie wiedziałem nawet, czy pojawią się te podziękowania w książce (po cichu na nie liczyłem). W końcu w jednej z ostatnich wersji książki zobaczyłem, że są :) Świadczy to o tym, że moje zaangażowanie było kompletnie non-profit.

Podsumowując współpracę cieszę się, że wziąłem udział w tym przedsięwzięciu. Było to bardzo ciekawe doświadczenie i mogłem przez to poznać proces powstawania książki. Robienie review sprawia wielka frajdę, czego zwieńczeniem jest oczywiście ukazanie się książki „na produkcji”. Szczerze polecam każdemu zaangażowanie się w tego typu akcję.


Btw. Co do relacji z 33 degree (kilka osób już o nią pytało), to musi ona jeszcze poczekać kilka dni, z powodu natłoku innych zajęć (w tym korekty ostatecznej wersji książki Tomka), jak i obecnych sporych zmian życiowych.

sobota, 4 lutego 2012

Testowanie wyjątków

Testowanie wyjątków w testach jednostkowych od zawsze trochę mnie irytowało. Niby sprawa banalna, ale jak do tego dołączymy słynny szablon // given // when // then to nie bardzo wiadomo gdzie powinniśmy umieścić słówko // then, aby test był dalej przejrzysty.

Problem nie jest nowy. Zastanawiano się nad dobrym rozwiązaniem już podczas prezentacji Bartosza Bańkowskiego i Szczepana Fabera pt. Pokochaj swoje testy [czas - 18.04] na Wroc JUG, albo pewnie jeszcze wcześniej. Idąc za radą lepszych, zawsze korzystałem z try / catch’a, aby wiedzieć gdzie dokładnie spodziewam się wyjątku.

@Test
public void shouldThrowSomeException() throws Exception {
    // given
    SomeClass someClass = new SomeClass();

    try {
        // when
        someClass.doSomething();
        fail("This method should throw SomeException");
    } catch(SomeException e) {
        // then
        assertThat(e.getMessage()).isEqualTo("Some message");
    }

}


Blok instrukcji try / catch wymusza na nas pewien sposób formatowania kodu i przez to nie do końca widać gdzie jest // when i // then. Można oczywiście próbować umieszczać je w trochę innym miejscu, np. // when przed try, a // then przed fail().

@Test
public void shouldThrowSomeException() throws Exception {
    // given
    SomeClass someClass = new SomeClass();

    // when
    try {
        someClass.doSomething();
        // then
        fail("This method should throw SomeException");
    } catch(SomeException e) {
        assertThat(e.getMessage()).isEqualTo("Some message");
    }

}

Jednak rozwiązanie dalej jest nie do końca czytelne. Gdyby jeszcze nic nie umieszczać w bloku catch, to już w ogóle, trzeba się chwilę zastanowić, co my tu tak na prawdę chcemy przetestować. Całe szczęście narzędzia do statycznej analizy kodu dbają o to, aby nie zostawiać pustych bloków catch.

Alternatywnym rozwiązaniem dla testowania rzucanych wyjątków, jest stosowanie andotacji @Test z parametrem expected w JUnit’cie  :

@Test(expected = SomeException.class)
public void shouldThrowSomeException() throws Exception {
    // given
    SomeClass someClass = new SomeClass();

    // when
    someClass.doSomething();

    // then
    fail("This method should throw SomeException");
}

Test wygląda już lepiej, ale ma swoje wady. Jak mi jakiś test nie przechodzi, to pierwsze co robię, to czytam sekcję // then testu. W tym przypadku widzę wywołanie fail() co sugeruje mi, że test zawsze powinien nie przechodzić. Dopiero po chwili zauważam, że test został zadeklarowany jako @Test(expected = SomeException.class), czyli spodziewam się wyjątku typu SomeException. Jest tutaj jednak pewne niebezpieczeństwo. Jeżeli faza // given testu, czyli przygotowania środowiska testowego, będzie trochę dłuższa, to może się zdarzyć, że tam gdzieś poleci wyjątek. Test będzie dalej przechodził, a tak naprawdę nie będzie testowane to co chcieliśmy. Wspominał o tym już Szczepan Faber w cytowanym fragmencie video. Dodatkowo nie można, jeśli byśmy chcieli sprawdzić np. treść komunikatu wyjątku. Z tych względów nie stosowałem tej konstrukcji.

Sprawa wygląda trochę  lepiej w przypadku TestNG. Tutaj mamy analogiczna adnotację, mianowicie zamiast expected używamy expectedExceptions.

@Test(expectedExceptions = SomeException.class,
        expectedExceptionsMessageRegExp = "Some Message.*")
public void shouldThrowSomeException() throws Exception {
    // given
    SomeClass someClass = new SomeClass();

    // when
    someClass.doSomething();

    // then
    fail("This method should throw SomeException");
}

Tu jeszcze dochodzi fajna zabawka w postaci expectedExceptionsMessageRegExp, czyli możemy za pomocą wyrażeń regularnych sprawdzić, czy wyjątek posiada spodziewaną wiadomość. Dalej jednak istnieje ryzyko wyrzucenia tego wyjątku w sekcji // given.

Podobną zabawkę daje nam JUnit, ale w trochę innym wydaniu. Mianowicie od wersji 4.7 można zastosować ExpectedException:

public class SomeClassTest {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void shouldThrowSomeException() throws Exception {
        // given
        thrown.expect(SomeException.class);
        thrown.expectMessage("Some Message");
        SomeClass someClass = new SomeClass();

        // when
        someClass.doSomething();

        // then
        fail("This method should throw SomeException");
    }
}

Tutaj w klasie testowej musimy zdefiniować regułę (linie 3 i 4), która początkowo mówi, że nie spodziewamy się wyjątków. Natomiast już w naszych metodach testowych, redefiniujemy to zachowanie i mówimy, czego się spodziewamy w danym teście (linie 9 i 10). Możemy dzięki temu sprawdzić komunikat rzucanego wyjątku. Tutaj jednak podajemy fragment wiadomości, którą ma zawierać nasz wyjątek. Istnieje również przeciążona wersja tej metody, która jako argument przyjmuje Matcher’a Hamcrest’owego.

Do niedawna były to jedyne rozwiązania, jakie były dostępne w temacie testowania wyjątku. Jakiś czas temu jednak pojawiła się ciekawa biblioteka: catch-exception. Kod napisany za jej pomocą może wyglądać tak:

@Test
public void shouldThrowSomeException() throws Exception {
    // given
    SomeClass someClass = new SomeClass();

    // when
    caughtException(someClass).doSomething();

    // then
    assertThat(caughtException())
            .isInstanceOf(SomeException.class)
            .as("Some Message");

}


Czyli mamy metodę CatchException.catchException(), gdzie jako argument przekazujemy obiekt naszej klasy. Następnie wywołujemy metodę, którą chcemy przetestować. Na koniec w sekcji // then sprawdzamy czy otrzymaliśmy wyjątek, którego się spodziewaliśmy. Bezargumentowa wersja caughtException() zwraca wyjątek rzucony przez ostatnią klasę, którą przekazaliśmy do metody caughtException(). W naszym wypadku jest to ostatni wyjątek wygenerowany przez someClass.

I to rozwiązanie mi się podoba. W sekcji // when informuję, że będę łapał wyjątki, a w sekcji // then sprawdzam czy poleciał ten wyjątek, którego oczekiwałem. I do tego nie bruździ to przy formatowaniu kodu i użyciu // given // when // then. I mamy czytelny kod :)

Zafascynowany tą biblioteką postanowiłem zajrzeć do środka (kod jest dostępny na googlecode.com), aby zobaczyć jak zbudowano takie rozwiązanie.

public class CatchException {

    public static <T> T catchException(T obj) {
        return processException(obj, Exception.class, false);
    }

}

Czyli mamy delegację pracy do metody processException(), która zwraca obiekt tego samego typu, jaki został przekazany w argumencie. Dzięki temu możemy używać biblioteki w sposób jaki pokazałem powyżej. Zobaczmy co kryje się za tą metodą:

private static <T, E extends Exception> T processException(T obj,
        Class<E> exceptionClazz, boolean assertException) {

    if (obj == null) {
        throw new IllegalArgumentException("obj must not be null");
    }

    return new SubclassProxyFactory().<T> createProxy(obj.getClass(),
            new ExceptionProcessingInterceptor<E>(obj, exceptionClazz,
                    assertException));

}

Po upewnieniu się, że argument nie jest null’em tworzymy (jak można się domyśleć po nazwach) proxy dla naszej klasy. Brnąc dalej w las, jeżeli klasa nie jest ani prymitywem, ani finalna, to proxy tworzone jest  w ten sposób:

proxy = (T) ClassImposterizer.INSTANCE.imposterise(
        interceptor, targetClass);

czyli wykorzystywany jest ExceptionProcessingInterceptor z poprzedniegu listingu, wewnątrz którego znajduje się następująca metoda, gdzie już widać całą magię:

public Object intercept(Object obj, Method method, Object[] args,
        MethodProxy proxy) throws Throwable {

    beforeInvocation();

    try {
        Object retval = proxy.invoke(target, args);
        return afterInvocation(retval);
    } catch (Exception e) {
        return afterInvocationThrowsException(e, method);
    }

}

Metoda beforeInvocation() czyści ostatnio złapany wyjątek, np. z wywołania poprzedniej metody. Następnie w bloku try wywoływana jest nasza rzeczywista metoda (linia 5), a następie zwracana jest (w naszym sposobie wykorzystania biblioteki) wartość wygenerowana przez oryginalną metodę. Jak coś pójdzie nie tak, to w bloku catch jest zapamiętywany rzucony wyjątek (zajmuje się tym metoda afterInvocationThrowsException()). Bardzo sprytny sposób na łapanie wyjątków, a jaki banalny zarazem.

Z ciekawostek jeszcze, to biblioteka korzysta z Mockito, a dokładniej cglib. Nie działa ona z klasami finalnymi (gdyż dla nich nie można teoretycznie tworzyć proxy), no chyba że skorzystamy w PowerMock’a i odpowiednich adnotacji w deklaracji klasy testowej:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ SomeClass.class })
public class SomeClassFinalPowerTest {
    // ...
}


Wtedy zadziała :)



Na koniec wpisu, jeszcze informacja skąd się dowiedziałem o tej bibliotece. Mianowicie powstaje teraz ciekawa książka o testach: Practical Unit Testing with TestNG and Mockito. Pisana jest ona przez Tomka Kaczanowskiego i już niedługo ujrzy światło dzienne. Książka poszła już do recenzji do Szczepana Fabra, więc lipy nie będzie. Będzie można się z niej dowiedzieć m.in. o catch-exception, jak i o testach mutacyjnych, o których pisałem ostatnio. Na razie wyjdzie wersja angielska, ale będzie też robione tłumaczenie na nasz ojczysty język. Zachęcam więc do śledzenia informacji o książce jak i do jej zakupu.

Więcej informacji o tym jak powstawała książka w kolejnych wpisach.

niedziela, 8 stycznia 2012

Testowanie mutacyjne z PIT Mutation Testing

Przeglądając dzisiaj (a właściwie to wczoraj) blogosferę natrafiłem na ciekawy post Tomka Kaczanowskiego First Glance At PIT Mutation Testing Tool na temat testowania mutacyjnego, za pomocą nowej biblioteki PIT Mutation Testing. Co to jest testowanie mutacyjne to można poczytać na Wikipedii: Testowanie mutacyjne (co ciekawe w tej chwili dostępny jest tylko artykuł w wersji angielskiej i polskiej).

PIT modyfikuje w locie nasz kod produkcyjny, puszcza testy i sprawdza, czy mutacje kodu są wykrywane przez nasze testy. Sprawdzamy dzięki temu jakość naszego kodu testowego, a dokładniej to, jak ściśle zdefiniowaliśmy zachowanie naszego kodu produkcyjnego za pomocą testów. Jest to trochę więcej niż pokrycie kodu testami wyrażone w procentach, gdyż raport ten podpowiada nam co jeszcze należało by przetestować.

Tomek stworzył przykładowy projekt i umieścił na github’ie. Ściągnąłem go, odpaliłem i przeglądnąłem raporty wygenerowane przez PIT’a. Jako że jest to prosty projekt na potrzeby testu tej biblioteki, postanowiłem zastosować go do swojego projektu MFCCChart. Co prawda nowej funkcjonalności do tego projektu już nie dodaję, ale się nim zabawiam testując jakieś ciekawe rozwiązania.

Patrząc na to co może PIT na stronie Mutation testing systems for Java compared w końcowej sekcji: Summary Of Mutation Testing Systems, to narzędzie to nie ma bezpośrednio wsparcia dla Ant’a, ale mamy interfejs linii komend. Przygotowałem więc komendę, dostosowaną do moich potrzeb:

java -cp out\test\MFCCChart;out\production\MFCCChart;testlib\junit-4.8.jar;testlib\mockito-all-1.8.5.jar;testlib\pitest-0.24.jar;lib\forms_rt.jar;lib\jcommon-1.0.16.jar;lib\jfreechart-1.0.13.jar org.pitest.mutationtest.MutationCoverageReport --outputFormats XML,HTML --reportDir reportspitests --targetClasses org.bitbucket.mstachniuk.mfccchart.* --targetTests org.bitbucket.mstachniuk.mfccchart.* --sourceDirs src --verbose --excludedMethods hasCode,equals --excludedClasses org.bitbucket.mstachniuk.mfccchart.view.*,org.bitbucket.mstachniuk.mfccchart.presenter.*

Najpierw mamy definicję naszego classpath’a. W katalogu out\test\MFCCChart mój ant’owy skrypt budujący wrzuca skompilowane testowe klasy, a do: out\production\MFCCChart klasy produkcyjne aplikacji. Następnie dołączyłem wszystkie wymagane do uruchomienia aplikacji jak i testów biblioteki. Klasa org.pitest.mutationtest.MutationCoverageReport jest klasą startową projektu PIT, a dalej mamy już argumenty dla tejże aplikacji. Ja zapragnąłem raportu w dwóch formatach (XML i HTML) w katalogu reportspitests. Następnie zapodałem klasy, które mają być mutowane, testowe klasy, kody źródłowe (aby można było zobaczyć gdzie wprowadzano mutacje). Jako że jest to biblioteka, której jeszcze nie znam to warto spojrzeć na szczegóły działania (opcja --verbose)  i wyłączyłem z testowania metody hasCode() i equals(), a także klasy z pakietów, które są odpowiedzialne za GUI i spinanie całości (do kupy).

Początkowo miałem problem z opcjami --outputFormats i --excludedClasses, gdyż nie chciały mi działać. Zgłosiłem nawet błąd na stronie projektu: Issue 23, ale szybko się okazało, że podczas kopiowania flag ze strony z Command Line Quick Start Notepad++ zamias zwykłego myślnika wstawił znak wyglądający niemal tak samo, ale o innym kodzie (najprawdopodobniej z po za zestawu ASCII).

Jak już się przekonałem, że generacja raportów działa, postanowiłem dorzucić tą analizę do skryptu budującego aplikację. Początkowo próbowałem to wykonać za pomocą taska exec ale nie chciało hulać. Po za tym task ten jest zależny od systemu operacyjnego i zarzuciłem go na rzecz taska java. I teraz poszło lepiej:

<target name="pitests">
    <java jvmargs="-Xmx600m" fork="true"
            classpath="${mfccchart.testoutput.dir};${mfccchart.output.dir};${basedir}/lib/jcommon-1.0.16.jar;${basedir}/lib/jfreechart-1.0.13.jar;${basedir}/lib/forms_rt.jar;${basedir}/testlib/junit-4.8.jar;${basedir}/testlib/mockito-all-1.8.5.jar;${basedir}/testlib/pitest-0.24.jar"
            classname="org.pitest.mutationtest.MutationCoverageReport"
            args="--reportDir reportspitests --targetClasses org.bitbucket.mstachniuk.mfccchart.* --targetTests org.bitbucket.mstachniuk.mfccchart.* --sourceDirs src --verbose --excludedMethods hasCode,equals --excludedClasses org.bitbucket.mstachniuk.mfccchart.view.*,org.bitbucket.mstachniuk.mfccchart.presenter.* --outputFormats XML,HTML">
    </java>
</target>


Bez fork'a nie chciało mi działać (zresztą pewnie jak większość tego typu rozszerzeń wywoływanych z Ant'a) Co mogłem to pozamieniałem na ścieżki zdefiniowane w pozostałej części skryptu. Jeszcze podaję sporą ilość ścieżek do konkretnych bibliotek co mi się nie podoba. Podejrzewam, że jest lepszy sposób na to ale mistrzem Ant’a nie jestem. Jak by ktoś wiedział, to proszę o komentarz.

Teraz czas na analizę raportu: „Detected 84 of 93 mutations”. Czyli na 9 różnych mutacji podatny jest mój kod. Patrząc na pokrycie klasowy, to chyba bardzo dobrze to wypadło:

Mutated classesLine coverageMutation coverage
OneMfccFileStatistic100%85%
MfccFileReader 100%97%
FrameInformation 100% 100% 
ChartSettings 100% 100% 
MfccFrame 100% 100% 
StatisticTableModel 100% 80% 

Ja jestem zadowolony z wyniku. Jak ktoś chce to niech sobie przejrzy mój przykładowy raport: MFCCChart_pit_reports_201201080158.zip

Teraz pozostaje tylko rozkminienie czego ode mnie chce ten raport (czyli co jeszcze mogę poprawić w swoich testach), jak i przyjrzenie się konkurencyjnym rozwiązaniom, których zestawienie można obejrzeć na wspominanej już stronie: Mutation testing systems for Java compared.

poniedziałek, 12 grudnia 2011

Agile Development Day

W sobotę 10 grudnia odbył się w Warszawie Agile Development Day. Była to impreza zorganizowana przez firmy Sages i Pragmatis. Event polegał na wspólnym tworzeniu aplikacji, ćwiczeniu TDD, programowania w parach i innych zwinnych praktykach. Był do tego Continous Integration (build był uruchamiany co git push) i Continous Delivery (deployment co udany build).

Aby dostać się na warsztaty trzeba było się wcześniej zapisać. Z ponad 60 osób chętnych, wybrano 22 w tym mnie :) Została przygotowana startowa baza kodu i udostępniona na github’ie. Trzeba było więc wcześniej ściągnąć kod i przygotować środowisko. Technologicznie był Spring Core, Spring MVC, JSP, Casandra, Maven. Jako że nie miałem wcześniej możliwości korzystać ze Spring’a w warunkach bojowych (po za jakimiś szkoleniami / prezentacjami), to miałem z czym się bawić. Zaprzyjaźniłem się więc z książkami na ten temat i archetypami kodu, aby porozkminiać te technologie i aby było na czym bazować.

Event zaczął się o 8 rano w Millennium Plaza. Najpierw było przywitanie i omówienie zasad, co będziemy robić w ciągu dnia. Następnie było o aplikacji jaką mamy stworzyć. Mianowicie za pomocą pisanego serwisu internetowego można się chwalić czego się nauczyliśmy, ile czasu na to poświeciliśmy i ile punktów zebraliśmy. Taki jakby twitter, tyle że piszemy co poznaliśmy, jaki był stopień trudności, system nalicza jakieś tam punkty i chwalimy się tym przed znajomymi. Plus jeszcze jakieś pierdoły do tego.

Po omówieniu wymagań aplikacji było wprowadzenie do Casandry, gdyż była to najbardziej „dziwna” i nieznana technologia z używanego technology stack. Później było szybciutko o Spring MVC i zaczęło się. Uczestnicy mieli wybrać sobie couch’a i osobę do pary (najlepiej aby ktoś znał Springa MVC). Jako że znałem Piotra (jednego z prowadzących) to się z nim dogadałem i wraz z Marcinem usiedliśmy do pierwszego user story.

Wybraliśmy sobie na tapetę formatowanie czasu, ile zajęła nam nauka danej technologii. Napisaliśmy kilka testów i zaczęła się zabawa. Postanowiliśmy dodać do projektu bibliotekę JUnitParams do pisania sparametryzowanych testów w JUnit’cie. Swoją drogą jest to biblioteka napisana przez Pawła Lipińskiego, który organizował, prowadził i kodował na warsztatach. Pomogło nam to pisać ładniejsze, parametryzowane testy w JUnicie, podczas pierwszej iteracji.

Wcześniej ustalono, że iteracje będą trwały po półtorej godziny i pomiędzy nimi będą zmiany par, a co kilka iteracji będzie retrospekcja. Czyli jakby jeden dzień ze sprintu został skompresowany do 1,5h a cały Sprint do 5ciu dni (czyli pięciu iteracji). Taka organizacja pracy bardzo motywowała do jak najszybszego kończenia zadań. Nie inaczej było w naszej parze, gdyż chcieliśmy jak najszybciej zobaczyć choćby częściowy efekt naszej pracy na „live”, mimo że nie wszystkie przypadki mieliśmy obsłużone. No i tu zaczęła się prawdziwa walka z technologiami.

Gdy jeszcze wczoraj Piotrek w projekcie dodał Tiles’y zaczęła się cała aplikacja wysypywać. Doszedł on do wniosku, że Casandra z Tiles’ami nie funkcjonuje ;) i wycofał zmiany. W naszej historyjce chcieliśmy (wręcz musieliśmy) skorzystać z JSTL’owego Taga c:forEach, ale też nie chciało działać. Był ten sam błąd co w przypadku Tiles’ów. Nasza koncepcja zamknięcia wyświetlania czasu we własny Tag JSTL’owy również nie zadziałała i trzeba było to jakoś inaczej obmyślić. To wszystko pewnie przez Casandrę ;)

Akurat nadszedł koniec pierwszej iteracji i nastąpiła rotacja par. Jako, ze pracowaliśmy na moim laptopie, to Marcin odszedł, a dołączył się Michał. Porozmawialiśmy chwilę, co zrobić z naszym problemem niemożliwości użycia JSTL’a i zaproponowałem, aby stworzyć Data Transfer Object (DTO) i za jego pomocą wyświetlać już odpowiednio sformatowany tekst. Coś tam niby się udało, ale potem było trochę walki z merge’owaniem zmian. Niestety szybko się okazało, że user story bardzo nachodzą na siebie, wręcz się powtarzają. Powodowało to masę konfliktów i trudności z push’owaniem zmian, gdyż po pull’u, odpaleniu testów, i przy próbie ponownego push’a okazywało się, że ktoś w między czasie zdążył wypushować swoje zmiany i proces trzeba było powtarzać ponownie.

Po drugiej iteracji postanowiono zrobić retrospekcję. Mieliśmy na małych, przylepnych karteczkach wypisać, co nam się podobało, a co nie i przykleić to na ścianę. Później trzeba było w ciszy to pogrupować (silent sorting) i ponazywać grupy. Po „uroczystym” przeczytaniu tego co jest fajne i niefajne, każda para wraz ze swoim mentorem miała się zastanowić, co zrobić, aby było lepiej. Super pomysłem było wprowadzenie stand-up meeting’ów - w trakcie trwania iteracji - [chyba] co 20 minut, aby poprawić komunikację w zespole i wiedzieć, kto co robi i na kiedy będzie. Innym ciekawym pomysłem było wpisywanie podczas commit'a / push’a jako autor numer implementowanego user story i nazwiska autorów. I rzeczywiście, początkowo brakło konwencji wypychania zmian do repozytorium, aby były one jednolite i czytelne.

Następnie był obiad i kolejna iteracja. Tym razem osoby, które przez dwie iteracje robiły jedną opowieść, miały się przesiąść. Może to trochę nieoptymalne posunięcie z punktu widzenia ukończenia zadania, ale podczas eventu chodziło o wspólną naukę. Trafiłem więc do zadania wyświetlania podpowiedzi. Mianowicie gdy wpisujemy technologię, której się właśnie nauczyliśmy, ma nam podpowiadać czy było to łatwe, średnie czy trudne. Wiązało się to z koniecznością napisania kawałka JavaScript’a, ale na szczęście to akurat poszło gładko. Problemem się jednak okazało, gdy coś co było zależne od naszego zadania (mianowicie dodawanie nowych umiejętności) czy jest już skończone i czy dane lądują tam, skąd je odczytujemy. Niestety ciężko w Casandrze cokolwiek podejrzeć, co wylądowało w bazie danych, gdyż zapisują się tam dane binarne. Nad tym zadaniem pracowałem przez dwie iteracje i później na ostatnią iterację, znów trzeba było się przesiąść.

Dosiadłem się więc do Bartka i tam nawet początkowo było fajnie (nie znaczy to wcale, że wcześniej było źle). Dostałem szybkie wprowadzenie co robimy, że właśnie jest test z nową funkcjonalnością i mam ją zaimplementować. Chwila ogarniania / czytania kodu i z pomocą Bartka udało się. Problem niemożliwości skorzystania z JSTL’a rozwiązano użyciem Skryptletów. Coś tam nawet się udało wyświetlić, ale nie można było wypchać zmian, gdyż najpierw trzeba było powalczyć  z kwestią, co wyświetlać, jak ktoś jest niezalogowany. Inny zespół pisał back-end do tej funkcjonalności i niestety czas warsztatów nie pozwolił już na połączenie naszych zmian.

Po piątej iteracji przejrzeliśmy backlog zapisywany na tinypm.com. Okazało się, że niewiele funkcjonalności udało się w pełni zaimplementować. Paweł pokazał jednak na żywym środowisku, że coś tam działa. Patrząc na statystyki na Jenkinsie, było widać, że w ciągu zmian udało się wypchnąć do repozytorium kod 54 razy z czego tylko 9 się wywaliło. Uważam to za niezły wynik, biorąc pod uwagę to, że dla uczestników był to świeży projekt, bez wypracowanych konwencji i nie każdy musiał znać wszystkie wykorzystywane technologie. Poniżej przedstawiam trend tesów.


Podczas warsztatów budowały się build’y od 88 do 142. Wcześniejsze build’y pochodziły z przygotowań do warsztatów. Widać wyraźnie że od 95 build’a liczba testów zaczęła znacząco rosnąć, z każdą nową zmianą.

Na koniec nadszedł czas na podsumowanie. Poprzestawialiśmy trochę stoły i ułożyliśmy krzesła w dwa kółka. W środku było 5 krzeseł, a  na zewnątrz reszta. Kto siedział w środku ten mógł mówić, pod warunkiem, że cztery wewnętrzne krzesła były zajęte. Jedno było wolne, aby kogoś mógł się dosiąść i oczywiście można było spokojnie opuścić wewnętrzny krąg. Osoby siedzące w środku mówiły o swoich odczuciach dotyczących warsztatów, co się nauczyły itd. Czyli taka runda feedback’owa dla innych uczestników i organizatorów.

Jako że ja nie miałem okazji zasiąść w środku, to swoją opinię wyrażę teraz. Ogółem event wyszedł bardzo fajnie. Organizacja w porządku: były kanapki rano, napoje i ciasteczka przez caly dzień, dobry obiad w trakcie (i nie była to pizza). Lokalizacja warsztatów bardzo dobra, mianowicie blisko centralnego – ważna kwestia dla osób przyjezdnych, a tych nie brakowało. Ja sam przyjechałem z Wrocławia, ale spotkałem również ludzi z Gdyni, Gdańska, Wrocławia, a nawet z Berlina. Organizacja pod względem technicznym również dobra – były dwa rzutniki, na jednym Jenkins, a na drugim live aplikacja. Bardzo dobrym pomysłem było przygotowanie zaczynu aplikacji i jej udostępnieniu w necie. Również Continous Integration i Delivery daje bardzo fajny efekt psychologiczny. Można było szybko zobaczyć swoje zmiany na Jenkinsie jak i na na środowisku „produkcyjnym”.

Niestety, jak to sami organizatorzy przyznali, rozpoczęcie przygotowywania tej aplikacji na tydzień przed warsztatami to było trochę za późno. Wynikały później z tego problemy z technologiami (niemożliwość użycia Tiles’ów i JSTL’a). Uważam również, że dobranie stosu technologicznego było nienajszczęśliwsze. Fajnie, że było cos egzotycznego (Casandra), ale mało kto wiedział, jak z tego odpowiednio korzystać. Do tego zastosowanie Springa MVC i JSP powodowało, że czasem trzeba było się męczyć z JavaScriptem (AJAX i jQuery). Te problemy technologiczne powodowały, ze ciężko było się skupić na czystym pisaniu kodu (nie wspominając już o TDD), a trzeba było się męczyć ze zgraniem technologii i merge’owaniem zmian.

Wcześniej wydawało mi się że w miarę ogarnąłem już Git’a, ale dopiero tutaj w warunkach bojowych zweryfikowałem swoje poglądy na ten temat. Problem scalania zmian występuje (pushowaliśmy do jednej gałęzi, aby Continous Server już dalej robił swoje), jak w każdym innym systemie kontroli wersji. Jakub Nabrdalik mówił, że jednym z możliwych sposobów jest zrobienie miecza z dwóch zwiniętych kartek papieru i ten kto aktualnie posiada miecz może push’ować. Jak ktoś innyc chce to robić, to musi uzyskać ten miecz. Wydaje mi się, że skorzystanie z Mercuriala mogło by trochę ułatwić sprawę z łączeniem zmian w kodzie, ale nie jestem ekspertem w tym temacie.

Innym poważnym minusem co do warsztatów, było przygotowanie user story’s. Bardzo często były one do siebie podobne, zachodziły na siebie i od siebie zależały. Jednak podczas wyboru user story nie posiadaliśmy tej wiedzy. Na przyszłość trzeba lepiej przygotować historyjki do zaimplementowania, wraz z grafem, co od czego zależy, co trzeba zrobić najpierw i z lepszym opisem. Większość opowiastek dotyczyła strony głównej, przez co wzajemnie sobie modyfikowaliśmy zawartość tych samych plików, co prowadziło do problematycznych konfliktów. Na przyszłość można kilka opowiastek udostępnić np. tydzień wcześniej, aby można było w domu coś na rozgrzewkę napisać. Zachęciło by to bardziej do zajrzenia w kod i do lepszego przygotowania do warsztatów. I najlepiej aby były one możliwie jak najbardziej rozłączne.

Również wybór Casandry na bazę danych nie było chyba najlepsze, gdyż z rozmów pomiędzy ludźmi słyszałem, że niektóre zespoły sporo się z nią męczyły. Fajnie poznać przy takiej okazji coś nowego, ale wybór chyba nie był najszęśliwszy. Jak bym proponował na następny raz jakąś bazę działającą w pamięci, inną NoSQL’ową (obiektową, key-value) lub jakąś lekką SQLową, którą wszyscy znają. Jako technologię widoku proponowałbym GWT (lub coś o podobnej koncepcji), gdyż tam nie trzeba się bawić z JavaScriptem, ani z JSP, a maksymalnie z niewielkimi kawałkami HTML’a.

Nie oznacza to, ze nic nie wyniosłem z warsztatów. Miałem w końcu motywację do lepszego poznania Springa, Git’a, JUnitParams… Zrozumiałem, że nie tylko testy jednostkowe są najważniejsze, ale obok nich musi się znajdować kilka integracyjnych i akceptacyjnych (już w początkowym stadium projektu). Przykładowo prosty test end-to-end uruchamiający przeglądarkę na stronie głównej i sprawdzający czy jakiś tam przycisk istnieje, pozwolił nam szybko wykryć, że Tagi w JSP nie chcą działać. Inna sprawa ze testy nie były podzielone na jednostokowe i inne (trwające dłużej), przez co ich uruchamianie było trochę przydługie.

Dowiedziałem się też o fajnej technice, podkreślającej współwłasność kod. Mianowicie po za wywaleniem z szablonów plików / klas i metod informacji o autorze, można dać wszystkim pracownikom z danego projektu, jedno konto i hasło do repozytorium. Dopiero wtedy powstaje wspólny kod i nikt się nie boi modyfikować nieswoich części. Poznałem również trochę bardziej w praktyce procesy zachodzące w procesie Agile'owym, jak i mogłem się dowiedzieć jak wygląda IT w innych miastach.

Zawsze z takich wydarzeń można wynieść ciekawe skróty klawiaturowe. I tak dla IDEI: Crtl + Shift + Enter - Statement Comlete, czyli dodanie średnika na końcu linii. Z analogicznych skrotów korzystałem w NetBeans’ie, ale tam to było to Ctrl + ; i Ctrl + Shift + ;. Innymi interesującymi skrótami w Idei są: Ctrl + Shift + T czyli przejście do klasy, która testuję aktualną klasę, w której jesteśmy i Ctrl + Shift + N (odpowiednik Eclipsowego Ctrl + Shift + R), czyli wyszukiwanie po nazwie pliku z projektu.

Ciekawe, nowopoznane skróty dla Eclipse’a to Ctrl + Shift + M - zaimportowanie statyczne danej metody na której znajduje się aktualnie focus. Przydatne zwłaszcza jak mockujemy z Mockito i chcemy samo when() zamiast Mockito.when(). Ponadto Ctrl + Shift + O czyli organizacja importów (w Idei można zrobić, aby automatycznie narzędzie przed commitem zmian, odpowiednio organizowało importy). I na koniec syso + Ctrl + Spacja, czyli skrót od System.out.println();. W Idei jest to sout + Tab co jest według mojego gustu fajniejsze.

Dobra to chyba tyle z podsumowania wydarzenia. W tym roku już się żadna konferencja / warsztaty nie szykują, więc trzeba będzie z tym poczekać do przyszłego roku. Mam nadzieję na kolejną edycję Agile Development Day lub podobny tego typu event.

czwartek, 27 października 2011

Wtyczki do Eclipse’a wspomagające naukę TDD


Aby zacząć naukę TDD (Test-driven development) wiele nie trzeba. Wystarczy ulubione środowisko programistyczne IDE (Idea / NetBeans / Eclipse) i narzędzie do testowania (JUnit / TestNG / xUnit). I teraz albo czytamy mądre książki (polecam "TDD by Example" Kenta Becka) jednocześnie kodując, lub szukamy odpowiednich informacji w necie, albo szukamy kogoś doświadczonego, aby pokodzić w parach. No i można już uskuteczniać TDD. Tylko skąd wiadomo, że robimy to dobrze? Czy pamiętamy o wszystkich krokach? Może czasem zapominamy zrefaktoryzować kod? Czy rzeczywiście mamy rytm red, green, refactor?

W celu śledzenia i późniejszej oceny jak nam poszło TDD warto zainstalować w Eclipse’ie plugin Pulse. Kiedy pierwszy raz o nim usłyszałem (i tu podziękowania dla Bartka), pomyślałem sobie: po co komu plugin do TDD? Pewnie to jakaś ściema lub zbędny bajer. I rzeczywiście jest to bajer, ale całkiem użyteczny.

Plugin Pulse tworzy wykres naszej aktywności, zaznaczając kiedy nasze testy fail’owały, kiedy były zielone i kiedy robiliśmy refaktoring. Na stronie plugin’a można zobaczyć ciekawy filmik prezentacyjny możliwości.

W celu przetestowania plugin’u wziąłem się wiec za PrimeFactorsKata, gdyż tej Katy wcześniej nie robiłem. Efekt poniżej:



Początkowo napisałem testy dla jedynki i dwójki (pierwsze dwie czerwone kropki). Gdy napisałem test dla 4ki to się skapnąłem, że skoro jest to rozkład na czynniki pierwsze, to powinienem zwracać listę wartości, a nie pojedynczą wartość. Trzeba było wiec sygnaturę metody zmienić.

Następnie szły kolejne testy, czasem w miarę potrzeby jakiś refaktoring. Jako refaktoring jest rozpoznawane użycie któregoś z automatycznych mechanizmów, np. Extract Method (Alt + Shift + M), czy Rename (Alt + Shift + R). Niestety, jak robimy to ręcznie, to nie jest to wykrywane przez plugin. Ale w sumie niedziwne, gdyż wówczas każda edycja kodu by generowała niebieskie punkty na wykresie.

Gdy już nie chciało mi się już szukać (ani liczyć) kolejnych testowych wartości, które bym dodał do testów, przerwałem katę. Nie napisałem w pełni funkcjonalnej metody, gdyż pewnie dla jakiś kolejnych dużych wartości mogło by to nie działać. Ale nie o to chodzi w ćwiczeniach typu CodeKata by dojść do końca (bez skojarzeń;) ), a o poznanie jakiejś nowej techniki programistycznej, lub wyrobieniu sobie dobrych nawyków.

Wracając jeszcze z Warsjawy, co opisywałem we wpisie: Warsjawa 2011 już za nami, jechałem pociągiem i wziąłem się za BowlingGameKata. Tutaj już miałem sporo czasu na implementację. Nie było to moje pierwsze podejście do tego problemu, więc już jakoś w podświadomości było wiadomo, jak to rozwiązać. Ćwiczyłem dodatkowo tworzenie notatek, na temat tego, jakie testy trzeba jeszcze napisać i jakie są już napisane. Wykres z wtyczki Pulse poniżej:



Zacząłem równo o 16.00 i początkowo pisałem proste testy dla następujących przypadków (liczby to ilości kręgli zbitych w danym rzucie):
  • 0 - pierwszy zielony test.
  • 1- pierwszy czerwony i już po nim jakiś refaktoring.
  • 1, 3 - drugi czerwony test.
  • 1, 3, 4, 5 - któryś z zielonych testów po 16.10.
  • Pierwszy bonus: Spare – zacząłem o 16.14 i wtedy postanowiłem zrobić większy refaktoring, tj. wprowadzić design obiektowy. Skończyłem go o 16.30 i 5 minut później już mi śmigał pierwszy test dla Spare’a. Do 16.40 jeszcze coś refaktoryzowałem, co nawed plugin zarejestrował.
  • Double Spare  - zacząłem chwile po 16.40 i trochę musiałem podebugować do 16.55. Ostatecznie okazało się, że w teście źle przeliczyłem oczekiwaną wartość. 
  • Strike – o 17.03 skończyłem implementację drugiego bonusu.
  • Double Strike – skończyłem implementację o 17.33. W międzyczasie musiałem zmienić przedział w pociągu, gdyż skończył się prąd w gniazdku :P
  • Spare, Strike – skończyłem o 17.39 (miałem błąd w danych testowych)
  • Strike, Spare– skończyłem o 17.41(j.w.). Następnie chciałem napisać implementację Full Strike (czyli ciągle rzucamy 10 za pierwszym razem). Ale po tym jak test zfail’ował, dopisałem do mojej listy TODO jeszcze inne, prostsze testy dotyczące końcówki gry (ostatnia ramka) i zacząłem od nich implementację.
  • Spare w ostatniej ramce – skończyłem o 17.54.
  • Strike w ostatniej ramce – skończyłem o 18.00.
  • Double Strike w przedostatniej (i ostatniej) ramce skończyłem o 18.03.
  • Full Strike (ciągle rzucamy 10) – działał od razu po odkomentowaniu testu, gdyż wcześniejszymi testami zapewniłem odpowiednie działanie ostatniej ramki. Na koniec jeszcze usunąłem duplikacje z kodu ekstrachując odpowiednie metody.

Pierwszy raz doprowadziłem tą Katę do końca i jestem z niej zadowolony. Teraz moją implementację trzeba porównać z rozwiązaniem wujka Boba.

Kolejnym pluginem udostępnianym przez @iamhappyprog na stronie www.happyprog.com jest TDGotchi. Dzięki kolejnym wynikom testów zbieramy punkty i rozwiajamy nasze „zwierzątko”.

Podczas PrimeFactorsKata udało mi się uzbierać 5 punktów, przy bowlingu zapomniałem spojrzeć.



Przy zamknięciu i otwarciu widoku, punkty się resetują. możemy jeszcze obserwować nasze zwierzątko / ikonke jak się zmienia, gdy zminimalizujemy widok.

Ostatnim plugin’em tego samego autora jest PairHero. Wspiera on Pomodoro Technique i ping pong programing.  Za przechodzące testy i refaktoring dostajemy punkty, a za szybkie zmiany kierownik – pilot można dodatkowo pomnożyć te punkty. Fajne jeśli akurat ćwiczmy Ping Pong'a, szybkie zmiany, lub po prostu programowanie w parach.Przykładowy screenshot poniżej.



Z przedstawionych plugin’ów najbardziej polecam pierwszy, gdyż wg. Mnie niesie największą wartość i pozwala na dobrą retrospekcje jak i porównanie wykresu z innymi developerami. Pozwala również nam zauważyć, jak często wykonujemy automatyczny refaktoring, ile czasu potrzebujemy na przejście red -> green itp.

A jak wygląda Twój puls?