Studio Pragmatists
A blog about how we work and what we're recently up to.
Wczoraj kapituła Javarsovii wysłała mi email, że moja prezentacja została zaakceptowana. Tak więc z przyjemnością ujawniam o czym będę mówił w tym roku:
Temat: ”Clean Tests” by Uncle Paul, czyli jak pisać testy, żeby dobrze Ci służyły.
Abstrakt: Niewielu programistów ma dobry zestaw testów dla swoich aplikacji. Jeszcze mniej ma takie, które są zarówno dobrą “siatką bezpieczeństwa” przy realizacji jakichkolwiek zmian, ale również mogą służyć za zawsze aktualną dokumentację systemu. W czasie prezentacji pokażę jak pisać i refaktoryzować testy by się je łatwo czytało, nietrudno utrzymywało, oraz by dobrze dokumentowały aktualny stan systemu.
Dzięki aktywności Bartka Zdanowskiego (a dokładniej jego niedoczytaniu dokumentacji
) wypuściłem wersję 0.2.1, która jasno mówi jak coś jest nie tak. Czyli jak jest JUnit <4.6 albo ktoś używa @Test zamiast @Scenario. No more NPEs ;-)
Do tego Tumbler 0.2.1 pojawi się w repo maven’a pewnie już jutro. Szczegóły będą na stronie Tumbler’a.
Aktualizacja:
Tumbler jest już w repo mavenowym.
groupId: pl.pragmatists.tumbler
artifactId: tumbler
Przez ostatnie dwa tygodnie znowu znalazłem trochę czasu i dorobiłem do Tumblera rzeczy, których brakowało. Dzięki temu wydałem właśnie wersję 0.2.0. W sumie powinienem chyba dobić do 1.0.0, bo w tej chwili ma on wszystkie podstawowe cechy takiego narzędzia, ale jakoś jeszcze nie jestem przekonany do takiego skoku w numerku.
Co się zmieniło?
- Zmieniłem moje wynalazki: Subject i Example na standardowe w BDD Story i Scenario. One może troszkę gorzej oddają o co chodzi, ale za to są spójne z zapisem Dana North’a używanym m.in. w JBehave, NBehave i jeszcze pewnie gdzieś indziej. A nuż ktoś będzie się chciał zmigrować, to będzie miał łatwiej. A i czytelnicy Dan’owych artykułów od razu zmapują sobie jego idee na Tumblerową rzeczywistość.
- Dodałem raporty w HTML. Jeśli to ma być narzędzie używane jakoś przez biznes, to musi być dla nich czytelne. Można używać własnych szablonów (freemarker) albo domyślnego. Zgodnie z sugestią Szczepana Fabera jest też “spis treści” dla stories, wraz ze statystykami wykonania dla całego projektu.
- Za namową Lasse Koskeli usunąłem @CamelCase. Jeśli ktoś nie chce pełnych zdań w eclipsie, to niech sobie ustawi zmienną -DcamelCase=true. W ten sposób nie wpływa na resztę zespołu.
W sumie więc nie ma tak dużo nowości, ale z drugiej strony myślę, że użyteczność narzędzia wzrosła znacznie.
Bardzo chciałbym się dowiedzieć co myślicie o tym narzędziu. Co można by dodać, co powinno być inaczej.
Tumbler działa tylko z nowymi wersjami JUnit’a. Wersja domyślnie będąca w eclipsie, jest dość stara (i buggy) – 4.5. Do poprawnego działania Tumbler wymaga wersji 4.8. Użycie starszej wersji JUnit’a skutkuje następującym wyjątkiem:
java.lang.NoSuchMethodError: org.junit.runner.Description.getMethodName()Ljava/lang/String;
at tumbler.internal.ScenarioListener.isItSingleScenario(ScenarioListener.java:97)
at tumbler.internal.ScenarioListener.testStarted(ScenarioListener.java:92)
at org.junit.runner.notification.RunNotifier$3.notifyListener(RunNotifier.java:83)
Tak sobie ostatnio pomyślałem, że wyjechałem z tym Tumblerem bez przybliżenia najpierw o co w nim chodzi. To znaczy wyjaśniłem (mam nadzieję) o co w NIM chodzi, ale nie pisałem nigdy o samej idei na której on bazuje, czyli BDD. Samo hasło pewnie większość z Was słyszała, ale ciekawy jestem jaki procent wie o co w tym chodzi (nic, i tak się nie dowiem).
No więc BDD wywodzi się od Dana North’a (kiedyś z Thoughtworks, teraz nie wiem), który doszedł do wniosku, że jest jakaś dziura między tym co zwykle programiści myślą o projekcie, który robią, a tym czego oczekuje biznes. Główny problem jest taki, że każda z tych grup mówi innym językiem – biznes zasuwa wyłącznie o pieniądzach, a programiści chcą mieć wszystko wyłożone w javie. No więc Dan wpadł na pomysł jak to połączyć.
Po pierwsze trzeba się uporać z językiem – nie ma sensu, żeby każdy nazywał rzeczy po swojemu, więc Dan doszedł do wniosku, że skoro nas klient nas Paaaannnnnn to wszędzie należy używać tej nomenklatury (tak, to to samo co Ubiquitous Language w DDD).
Po drugie biznes(meni) patrzy na system z punktu widzenia jego użycia, a programiści z punktu widzenia… frameworków? Języka? Czy czegoś podobnego. Mówiąc w skrócie biznes patrzy z zewnątrz do wewnątrz a programiści odwrotnie.
No więc Dan wpadł na pomysł, żeby zbliżyć te wizje do siebie przez zmuszenie programistów do tego, żeby myśleli po angielsku, a więc za pomocą rzeczowników i czasowników a nie instancji i metod. Konkretnie chodziło mu o to, żeby wyrażać testy jako przykłady (coding by example) wyrażane w języku naturalnym (angielskim, bo: primo – polska fleksja nie nadaje się do parsowania, secundo – Dan nie włada polszczyzną). Idealnie powinno być tak, że nieprogramiści (tak, to jednak pewnie grubo ponad 99% ludzkości) są w stanie to przeczytać i zrozumieć. Tu wchodzimy na grząski grunt DSLi, którymi nie chcę się dziś zajmować, bo jest późno, a i tak już dużo napisałem.
Wracając do tematu, Dan wymyślił dość zwięzły i krótki zapis, który jest zrozumiały dla innych (no…, że nie tylko dla nas), a przy okazji jest parsowalny i da się przerobić łatwo na coś zrozumiałego dla maszyn. Zapis Dana jest następujący (ukradłem ten tekst ze strony Dana, więc mu nie mówcie):
Title (one line describing the story)
Narrative:
As a [role]
I want [feature]
So that [benefit]
Acceptance Criteria: (presented as Scenarios)
Scenario 1: Title
Given [context]
And [some more context]...
When [event]
Then [outcome]
And [another outcome]...
Scenario 2: ...
Story to coś a’la User Story w zwinnych metodykach, czyli jakaś funcjonalność. Narrative to znów znany ze zwinnych metodyk zwrocik (oj, zdrobnienie – wyszło, żem Warszawiak) “Jako KTOŚTAM chciałbym COŚTAM żeby COŚ SIĘ STAŁO”. Do tego dochodzi Scenario, czyli konkretny przykład stosowania funkcjonalności, w formie znanej z wykładów Szczepana Fabera:
Given …
When …
Then …
Czyli prosty zapis tego co dane jest na wejściu, operacji, i jej wyniku.
Takie zdania powinien być w stanie nam dostarczyć każdy analityk (no dobra, tak naprawdę, to biznes powinien siedzieć nad tym z programistą, w końcu customer on-site…) A my z tych zdań budujemy szybko sobie testy, które prowadzą (młodzież powiedziałaby drajwują, ale ja jestem stary) zarówno zakres funkcjonalny tworzonej aplikacji, jak i w pewnym stopniu design – przynajmniej API.
Podsumowując – chodzi o to, by mieć wspólny zapis dla przykładów, które prowadzą development, oraz by te przykłady były wybierane (przez biznes) na podstawie ich wartości dla systemu. Realizacja tego pomysłu powoduje, że cały development jest prowadzony de facto przez interfejsy użytkownika, bo to jest to, o czym biznes myśli i co jest sobie w stanie wyobrazić (i dobrze!)
Do tego w BDD jest cała podszewka lingwistyczno-psychologiczna (powinienem pojechać tu hipotezą Sapir’a-Whorf’a ale na to jest zdecydowanie za późno….), która mówi o tym, że programiści lepiej myślą o kodzie jeśli rozumieją i wyrażają go po ludzku.
No i na koniec wisienka na torcie: oczywiście przykłady to tak naprawdę testy. Same przykłady to już dużo, bo pozwalają myśleć o kodzie z punktu widzenia jego użycia. Ale skoro przykłady opracowuje się na początku – przed pisaniem kodu, to chyba jest jasne, że nie wyrazić ich w postaci testów było by głupotą. No więc w ten sposób przykłady zachowań z BDD stykają się z testami akceptacyjnymi, prowadząc do A-TDD (Acceptance-Test Driven Development).
No, to chyba tyle. To teraz jak to się ma do Tumbler’a?
Jak już wiemy o co chodzi, to Tumbler po prostu realizuje tę ideę i trochę ją rozszerza (cóż – jestem przekonany, że Dan nie wymyślił tego po ubarwionej zielskiem wycieczce po Camden Town, tylko faktycznie miał to na myśli). Nie tylko pozwala on przekształcać pełnotekstowe przykłady/scenariusze (o składni +/- takiej jak powyższa) na testy JUnit’owe, ale generuje również raporty (aktualnie txt oraz html) z tego co przechodzi, co nie, a co jeszcze nie zostało zaimplementowane. To już dużo. Jednak główną myślą, która mi przyświecała przy tworzeniu tego narzędzia było, że większość programistów nie ma planu co ma zrealizować, tylko pisze co im się akurat wydaje w danym momencie ważne. Tak powstają nie tylko potwory i spółka, ale również po prostu niekompletne implementacje. A gdyby tak zastanowić się chwilę nad każdą funkcjonalnością, zapisać ją w formie przykładów i dopiero potem zrealizować? Dam głowę, że tak napisany kod nie powodowałby u autora myśli “gdybym tylko mógł napisać to jeszcze raz!” – bo raz piszemy to najpierw w głowie w przykładach, a potem przelewamy na papier – tfu, klawiaturę – w dopracowanej formie.
PS. Tumbler z troszkę zmienionym API oraz nowymi funkcjonalnościami wyjdzie w ten weekend, więc kto żyw ściągać w poniedziałek rano i stosować!
Miło mi poinformować, że założona została grupa Agile Warsaw, która ma na celu stworzenie w Warszawie społeczności ludzi związanych ze zwinnym wytwarzaniem oprogramowania. Chcielibyśmy utworzyć przestrzeń z klimatem wzajemnego uczenia się, wymiany doświadczeń, wspólnego szukania nowych dróg do lepszego tworzenia oprogramowania, prowadzenia zespołów i zaspokajania potrzeb naszych klientów. Chcielibyśmy by to środowisko samo było zwinne, dlatego szczególną wagę przykładać będziemy do prostoty, komunikacji, informacji zwrotnej, odwagi i szacunku. Jeśli więc starasz się realizować te wartości w swojej pracy, chciałbyś dowiedzieć się jak pracują inni i dzielić się swoimi zwinnymi doświadczeniami – jesteś więcej niż mile widziany.
Spotkania planowane są w wybrane poniedziałki, o godzinie 19stej w siedzibie firmy Aenima, ul Łucka 15 pok. 227. Planowana formuły spotkań to mini-wykład (15-30min) po którym następuje dyskusja i wymiana doświadczeń, oraz Open Space Technology. Spotkania kończą się retrospektywą / socjalizacją / networkingiem w okolicznym pubie.
Najbliższe spotkanie odbędzie się 24tego maja, tematem przewodnim będzie przejście ze Scruma do Kanabana, a poprowadzi go Marek Kirejczyk.
Agile Warsaw ma też swoje miejsce w internecie – jest to googlowa grupa agile-warsaw.
Zapraszamy!
Ten post powstał na wyraźną prośbę Jacka Laskowskiego i Bartka Zdanowskiego, którzy zastanawiali się (w komentarzach poprzedniego posta) nad kawałkiem przykładu z dokumentacji Tumbler’a. Przykład wygląda tak:
Zaciekawiła ich konkretnie linijka
library.lend(sampleBook).to(reader);
No i faktycznie, nie dziwię się, że ich zaciekawiła, bo nie jest to wyrażenie typowe dla Javy. Napisałem je w dokumentacji dość bezmyślnie, po prostu tak myślałem o tym kawałku kodu. To bardzo czytelne i jasne wyrażenie. Z drugiej strony jest ono dość trudne do zrealizowania i jego implementacja może sama nie być już taka czytelna, głównie dlatego, że logika pożyczania książki musi być wywołana w metodzie to a nie lend.
Ale zacznijmy od początku. Płynne interfejsy (fluent interfaces) to technika skupiająca się na takim zaprojektowaniu klas, by możliwe było ich wywoływanie w możliwie najczytelniejszy sposób. Żeby czytało się je prawie jak zwykłe zdania (to jest łatwiejsze w językach o elastyczniejszej składni jak ruby, groovy czy scala – w javie składnia jest bardzo sztywna). Powyższy przykład dość dobrze to obrazuje. W prostym przypadku płynny interfejs to tylko ciąg metod z których każda zwraca this. Tak na przykład tworzy się builder‘y:
new Person()
.withName(“Paweł”)
.withHeight(178)
.withBirthdayOn(OCTOBER, 23);
Takie podejście pozwala obejść problem istnienia wielu różnych konstruktorów dla różnych wersji parametrów. Co więcej pozwala rozwijać klasę (dodawać nowe pola) bez zmiany dotychczasowego kodu. Ryzyko tylko jest takie, że możemy w jakichś metodach bazować na nowo dodanych polach, ale od czego są testy
Problem z płynnymi interfejsami jest taki, że języki umożliwiające wyłącznie statyczne definiowanie metod raczej wspierają bardziej ‘funkcyjne’ podejście, gdzie metoda to funkcja operująca na swoich (pewnie paru) parametrach. W przypadku płynnych interfejsów na wykonanie jednej funkcji składa się często parę wywołań metod, z których więkość tylko zmienia stan (magiczne słowo) obiektu/wywołania a dopiero ostatnia w łańcuchu (method chaining) faktycznie wykonuje jakąś operację. W powyższym przykładzie więc metoda lend wyłącznie ustawi w jakimś polu klasy library sampleBook, które będzie użyte potem w metodzie to jako parametr dla faktycznej operacji pożyczenia książki.
No dobra, ale metoda o nazwie to może pewnie obsługiwać dużo wiecej operacji – np:
library.send(someBook).to(emailAddress);
albo
library.move(otherBook).to(poetryShelf);
Jak obsłużyć takie sytuacje?
Najprostsze rozwiązanie to przeciążenie metody to. W zależności od typu parametru będziemy wywoływać tak naprawdę różne to, dzięki czemu możemy oddzielić ładnie te operacje.
No dobra, wcale nie ładnie – nazwa metody to nic nam nie mówi o tym co ta metoda naprawdę będzie robić, więc rozróżnianie jej typem parametru zmniejsza tylko czytelność tego kodu. Trochę lepszym rozwiązaniem jest delegacja z metody to do jakiejś metody o nazwie konkretnie definiującej funkcjonalność (np. przez doSend(someBook, emailAddress)). To trochę lepsze rozwiązanie, ale dalej niewiele daje czytelnikowi API. Posiadanie w klasie library trzech różnych metod to nie jest czystym rozwiązaniem tego problemu. Tak czy siak pierwsza implementacja naszej biblioteki wygląda następująco:
Metoda to powinna mieć jeszcze weryfikację stanu – nie można wywołać jej samej, bo to nie ma sensu. Dorzucę więc zaraz walidację stanu (sprawdzenie czy ustawiona została operacja).
Sam kod zbyt śliczny nie jest – już na pierwszy rzut oka widać, że metoda to będzie puchła – przyda się zatem mały refactoring:
No, teraz dużo lepiej. Metoda to jest już dość abstrakcyjna – jedyne co robi to weryfikacja stanu (czy może być wywołana), dodanie jej parametru (celu operacji) i wywołanie odpowiedniej metody. Nie mamy warunków, a cała logika odpowiedzialna za operacje jest w podklasach operacji (tu magia enumów, ale można było zastosować oczywiście klasyczną strategię). Teraz dodanie kolejnych metod to (dla innych parametrów) oraz innych podobnych metod będzie tylko podobną delegacją do odpowiedniej logiki. Będzie jasne jak i gdzie je dodawać. Jestem zadowolony.
Ale zaraz, powiedziałby Bartek Zdanowski, a co z równoległym dostępem do klasy library?
Hm, no to nie takie trudne chyba. Wystarczy wsadzić operację i parametry do ThreadLocal‘a i tak operować na nich. Spróbujmy (klasę Operations sie nie zmieniła, więc skonwertowałem ją do top-level i jej nie widać, za to widać resztę):
Jak widać specjalnie nam to kodu nie skomplikowało, a mamy pewny równoległy dostęp. Dorzuciłem przy okazji czyszczenie stanu wywołania, tak, żeby następujące po sobie wywołania na pewno nie miały na siebie wpływu.
Jak widać płynne interfejsy nie są takie straszne, za to mają ogromny wpływ na czytelność kodu. Tak jak każdej innej techniki nie ma ich co nadużywać, ale w przypadku często używanego i/lub złożonego API są one bardzo przydatne. W bardziej skomplikowanych przypadkach może się okazać, że będziemy musieli wprowadzić wzorzec state by ładnie obsługiwać różne stany obiektu, choć zwykle jeśli to jest niezbędne warto się zastanowić, czy nasza klasa nie zaczyna odbiegać trochę od swojej głównej funkcjonalności.




