Skuteczne testowanie własnego kodu

By 12 June 2017 Get Things Done, ITlogy

Skuteczne testowanie włas­nego kodu, jest rownoważne z samym pro­gramowaniem. Tekst: dzi­wnie, u mnie dzi­ała, raczej nie obroni źle zaim­ple­men­towanej funkcjon­al­noś­ci. Co zro­bić, żeby testowanie kodu nie bylo udręką, a rzetel­nym źrodłem infor­ma­cji o kodzie?

Jeśli dopiero zaczy­nasz swo­ją przy­godę z pro­gramowaniem sprawdź naszą lekcję javy odnośnie testów jed­nos­tkowych. Uwa­ga, we wpisie będę podawała przykłady bib­liotek javowych, ale na pewno zna­jdziecie ich odpowied­ni­ki w swoich technologiach.

W najwięk­szym skró­cie, sto­su­jąc ang­iel­s­ki skrót: testowanie kodu, to nic innego niz AAA: Arrange, Act and Assert. Po pier­wsze, musimy dobrze przy­go­tować i zainicjować testowany frag­ment kodu (jak i przyj­mowane przez niego para­me­try i dane testowe), po drugie musimy w poprawny sposób wywołać testowaną metodę i w końcu musimy zapewnić odpowie­nie sprawdze­nie. O tym, jak to dzi­ała w prak­tyce przeczy­tasz poniżej.

#0 Postawa

Na początku, jako pro­gramista, musisz uświadomić sobie, że testy two­jego kodu  nieod­zownym ele­mentem pra­cy dewelop­er­skiej. Nie zro­bi za Ciebie tego stażys­ta, junior, tester automaty­czny, czy nawet mama :). Stąd też świet­ną metodą na pisanie testów do swo­jego kodu, jest test dri­ven devel­op­ment (o którym więcej przeczy­tasz tutaj). To pode­jś­cie, nie tylko zmusza do pisa­nia testów, ale, a może przede wszys­tkim narzu­ca pisanie ich w sposób wartoś­ciowy dla two­jego kodu. Zaczy­na­jąc od testów, możesz nie tylko zapewnić dobre testowanie, ale też prze­jść przez wszys­tkie możli­we ścież­ki, czy, po pros­tu znaleźć odpowied­nie rozwiązanie prob­le­mu. Różni devel­op­erzy różnie do testów pod­chodzą. Dla jed­nych będzie to sposób na zapewnie­nie bezbłęd­noś­ci czy wyszukanie bugów, dla innych, sposób na refak­tor i lep­sze prze­myśle­nie imple­men­tacji, albo wręcz gwaranc­ja, że “potom­ni” będą mogli kon­tyn­uować pracę nad pro­jek­tem. Tak naprawdę, każdą z tych rzeczy warto mieć w głowie gdy tes­tu­je­my swój kod. Bo testy to tak naprawdę dru­ga noga pro­gramowa­nia i bez nich, nie moż­na mówić o ukońc­zonym zadaniu.

#1 Testy jednostkowe

Tak, dokład­nie takie – jed­nos­tkowe, czyli takie, które tes­tu­ją najm­niejsze ele­men­ty /jednostki pro­gra­mu. Taka jed­nos­t­ka powin­na być nieza­leż­na od innych ele­men­tów aplikacji, a każdy test nieza­leżny od siebie (kole­jność ich wykony­wa­nia nie powin­na grać roli).Jak moż­na zapewnić takie warun­ki? Po pier­wsze mock­u­jąc inne ele­men­ty pro­gra­mu, które są wywoły­wane przez testowany kod. W javie możesz sko­rzys­tać z bib­liotek takich jak Mock­i­to (tutaj nasz wpis, z prak­ty­cznym wyko­rzys­taniem tej bib­liote­ki), JMock­it, Easy­Mock czy Pow­er­Mock.

Po drugie każdy test powinien być nieza­leżną jed­nos­tką, dzi­ała­jącą w tych samych warunk­ach. Wszys­tkie zmi­enne, czy stany aplikacji powin­ny być “czyszc­zone”. Jeśli pewne czyn­noś­ci wykonu­je­my dla każdego tes­tu, uży­wa­jąc JUnit może­my sko­rzys­tać z ano­tacji @Before/After czy @BeforeAll/@AfterAll (gdy chodzi o jed­no­ra­zowe przy­go­towanie do testów np. zainicjowanie ser­wisu). Same testy nie powin­ny na siebie wza­jem­nie wpływać.

#2 Znajdź wszystkie ścieżki

Cza­sa­mi w swoim bądź cud­zym kodzie może­my znaleźć taki kwiatek: jeden test, napisany tak, że wszys­tkie wyma­gane para­me­try są ustaw­ione w sposób praw­idłowy, wszys­tkie wal­i­dac­je i aser­c­je prze­chodzą, otrzy­mu­je­my oczeki­wany wyni­ki. I w takim teś­cie nie ma nic złego — póki obok niego są też te obsługu­jące ścież­ki niepowodzeń. Nasze testy powin­ny pokry­wać wszys­tkie możli­we sce­nar­iusze, dlat­ego ważne jest, by dobrze zrozu­mieć zadany prob­lem biz­ne­sowy np. orga­nizu­jąc spotkanie Three Ami­gos. Nie sztu­ka napisać kod, który dzi­ała w ściśle określonych, oczeki­wanych warunk­ach. Niem­niej, jeśli zna­j­du­je się w nim wal­i­dac­ja, albo dla określonych para­metrów przewidy­wane jest inne zachowanie — to wszys­tko powin­no zostać uwzględ­nione w naszych tes­tach. Podob­nie warto sprawdz­ić warun­ki brze­gowe. A wszys­tko po to, by dostar­czyć rozwiązanie odpowiada­jące w pełni potrze­bą uczestników.

#3 Używaj danych zbliżonych do prawdziwych

Uży­wanie pliku z jed­ną lin­ijką danych, pod­czas gdy użytkown­ik na co dzień chce korzys­tać z takich, gdzie jest ich tysiące, może być ryzykowne. Od samego mech­a­niz­mu odczy­tu, po np. wal­i­dację czy zwracanie wyniku — dużo rzeczy może dzi­ałać inaczej, niż byśmy chcieli. Może to nai­wny przykład, ale dobrze ilus­tru­ją­cy zasadę: zawsze lep­iej stosować dane, które przy­pom­i­na­ją te od użytkownika.

#4 Sprawdzaj uczciwie

Sprawdze­nie, czy otrzy­many wynik nie jest nullem to za mało :) Warto tworzyć jak naj­dokład­niejsze sprawdzenia w naszym kodzie, tak by nic nie umknęło — czy pola są odpowied­nio ustaw­ione, czy zwracany obiekt prze­chodzi wal­i­dację (jeśli zwracamy taki sam typ jak przyj­mu­je­my), czy nie ma pól-kolekcji, które są nul­la­mi itd

#5 Nie trać czasu na oczywistości

Nie tes­tuj kodu bib­liotek, nie sprawdzaj oczy­wis­toś­ci. Boil­er­plate code, który nie ma logi­ki biz­ne­sowej nie musi być testowany. Jeśli jed­nak pokusiłeś się o włas­ną imple­men­tac­je metody Equals (a nie sko­rzys­tałeś np. z Lom­bowej ano­tacji @EqualsAndHashCode), to musisz ją sprawdz­ić. Aku­rat w tym specy­ficznym przy­pad­ku może­my pod­sunąć Ci fajną bib­liotekę, a mianowicie: EqualsVer­i­fi­er, który znacznie ułatwi to zadanie.

#6 Code Coverage

To częs­to zarówno bło­gosław­ieńst­wo, jak i przek­leńst­wo w pro­jek­cie. Dobrą prak­tyką jest mierze­nie pokrycia kodu tes­ta­mi, a także zasa­da, że nigdy doda­jąc nowy kod jej nie obniżamy. Złą zasadą jest pisanie testów pod zwięk­sze­nie tego wskaźni­ka, bez słusznych aser­cji, zas­tanowienia, pokrycia całej funkcjon­al­noś­ci, czy dopisy­wanie testów wspom­ni­anego wcześniej kodu, którego dzi­ałanie jest oczy­wiste. Dlat­ego jeśli w swoim pro­jek­cie zde­cy­du­je­cie się na mierze­nie tego wskaźni­ka (a moż­na tutaj sko­rzys­tać cho­ci­aż­by z Jaco­co, czy innych wbu­dowanych w IDE narzędzi), warto dodać do niego zdrowy rozsądek i nie testować “na siłę” coby słup­ki nie spadły. Tes­tuj to, co ma sens, ale pisz testy wycz­er­pu­jące wszys­tkie ścież­ki w kodzie.

O tym, jak nie należy pod­chodz­ić do Code Cov­er­age, poczy­tasz także na blogu dev.to .

#7 Nie tylko jednostkowe

Warto pamię­tać, że oprócz poprawnego dzi­ała­nia poje­dynczych frag­men­tów naszego kodu, liczy się też to jak będzie to dzi­ałało w ramach całego kom­po­nen­tu, a także, czy poza poprawnoś­cią mamy też oczeki­waną wyda­jność. Dlat­ego, do testów jed­nos­tkowych warto dodać testy inte­gra­cyjne, które poz­wolą przekro­jowo sprawdz­ić poprawność dzi­ała­nia aplikacji (a także udoku­men­tować jej dzi­ałanie) i stara­ją się jak najlepiej odd­ać jej nor­malne uży­wanie, kon­fig­u­rację i środowisko. Do tego, warto pokusić się o przeprowadze­nie testów wyda­jnoś­ci, lub skon­fig­urowanie narzędzi do zbiera­nia metryk. Jeśli w pewnym momen­cie aplikac­ja “zwol­ni”, będziecie o tym wiedzieli szy­b­ciej, niż użytkown­ik, bo już w fazie developmentu.

#8 Testy jako dokumentacja kodu

Dobrze napisane testy, są jak dobry podręcznik dla Devel­opera — tłu­maczą co tes­tu­ją, jaki­mi dany­mi, jaki jest oczeki­wany wynik, jakie możli­we ścież­ki inter­akcji. Im są bardziej uporząd­kowane (Giv­en — When — Then), im lep­sze mają nazwy, dokład­niej odzwier­cied­la­ją prawdzi­we dzi­ałanie aplikacji tym lep­iej mogą zastępować doku­men­tac­je, komen­tarze czy ses­je z nowy­mi osoba­mi w pro­jek­cie. Testy to też kod, więc jak najbardziej podle­ga­ją zasadom Clean Code’u, samo wyjaś­ni­a­jące się zmi­enne, kod podzielony na blo­ki, wyłączanie stałych, czy metod jak najbardziej tutaj obowiązuje ;)

W pisa­niu testów do swo­jego kodu, tak jak w samym pisa­niu kodu — nie moż­na iść na skró­ty. Dobrze przetestowany kod, to kod, który speł­nia założe­nia biz­ne­sowe, jest czytel­ny i prosty (bo TDD zachę­ca nas do refak­toru), dobre opisany (właśnie przez testy), oraz sprawd­zony nie tylko w izolowany sposób poprzez testy jed­nos­tkowe, ale też przekro­jow, tes­ta­mi inte­gra­cyjny­mi, jak i mon­i­torowany pod wzglę­dem wyda­jnoś­ci. Taki kod powinien być łatwiejszy w przekaza­niu koledze, gdy jedziesz na urlop, a także nie powinien powodować bólu głowy, gdy dosta­jesz go w spad­ku w starym fir­mowym projekcie.