Śpiechu pisze testy jednostkowe

Ostat­nio gdzie nie zajrzę, poja­wia się tajem­ni­cze hasło testy jednost­kowe, unit tests lub co gorsza Test-driven deve­lop­ment. Od kilku dni bawię się testami auto­ma­tycz­nymi i śmiało mogę powie­dzieć, że mają sens, a co więcej, wpro­wa­dzają dużo więcej zabawy do programowania.

Na czym to ustroj­stwo polega? W chwili pisa­nia kodu, w osob­nym pliku równo­cze­śnie piszemy do niego testy. Pisząc w TDD zaleca się najpierw napi­sa­nie testu, a dopiero potem kod, który speł­nia dany test! Fajne, co? Testy pilnują żeby podczas rozbudowy/refaktoryzacji kodu całość działała.

Im trud­niej speł­nić test, tym lepiej. Kluczowe jest prze­wi­dze­nie reak­cji kodu na warto­ści graniczne oraz momenty, kiedy coś się popsuje. Jeżeli kiedy­kol­wiek musimy użyć var_dump(), to od razu powinna nam się zapa­lić lampka w głowie, że można napi­sać test.

Jak w ogóle się za to zabrać? Potrze­bu­jemy apli­ka­cji testu­ją­cej o nazwie PHPU­nit. Na stro­nie jest również fajna doku­men­ta­cja (co prawda po angiel­sku, ale napi­sana bardzo prostym języ­kiem). W Ubuntu można łatwo sobie doin­sta­lo­wać program poprzez Synap­tic (trochę star­szą wersję, ale to nic).

Druga sprawa, której potrze­bu­jemy to kod do testo­wa­nia. Na szybko napi­sa­łem sobie prostą klasę szyfru­jącą ciasteczka za pomocą wybra­nego algo­rytmu, trybu i hasła. Nie wiem czy użyt­kow­ni­kom spodo­ba­łoby się trzy­ma­nie u siebie czegoś, do czego nie mają dostępu, ale cóż… potrze­bo­wa­łem mate­riału do testów. Jest tego kilka­dzie­siąt lini­jek, dlatego wkle­iłem do Paste­bin.

Jak kogoś inte­re­suje, to używa się tego dosyć prosto: tworzymy obiekt, wybie­ramy jeden z algo­ryt­mów modułu mcrypt (który wcze­śniej musimy mieć zain­sta­lo­wany!), tryb i hasło, a następ­nie za pomocą metody setEncryptedCookie() usta­wiamy cookie. Mając usta­wiony obiekt, getEncryptedCookie() wycią­gnie nam cookie i rozszy­fruje treść.

$cookieEncrypter = new SpiechuCookieEncrypter();
$cookieEncrypter->setAlgorithm('twofish')
        ->setMode('cbc')
        ->setPassword('jakies haslo')
        ->setEncryptedCookie('testowe_ciacho', 'testowa wartosc ciacha');
 
echo $cookieEncrypter->getEncryptedCookie('testowe_ciacho');

Jeżeli do kodo­wa­nia używa­cie NetBe­ansa, to pod prawym przy­ci­skiem nad nazwą pliku jest wygodna opcja, która stwo­rzy nam „szkie­le­tor” testów (Tools->Create PHPU­nit tests).

Zasada jest taka, że klasa testu­jąca musi rozsze­rzać klasę PHPUnit_Framework_TestCase. Ponadto przy­jęło się, że nazwa kończy się słowem *Test, za to wszyst­kie metody testu­jące muszą zaczy­nać się od test*.

Testy operują na aser­cjach, czyli specjal­nych meto­dach, których waru­nek musi zostać speł­niony aby test powiódł się. I tak, dla przy­kładu assertEqual($wartosc1,$warosc2) zgłosi nam błąd, jeżeli obydwie warto­ści nie są takie same. Jest tego w hu hu, dlatego zain­te­re­so­wa­nych skie­ruję do źródła.

Oprócz metod testCośTam mamy jesz­cze dwie metody specjal­nego prze­zna­cze­nia: setUp()tearDown(). Pierw­sza jest odpa­lana przed każdym testem, druga po teście. Pole­gają na przy­go­to­wa­niu środo­wi­ska do testów i „posprzą­ta­nie” po teście. W moim przy­padku setUp tworzy obiekt testo­wany i umiesz­cza w polu obiektu testu­ją­cego, a tear­Down jest puste, bo nie miałem np. otwar­tych plików, połą­czeń do bazy czy innych rzeczy, które trzeba by zamknąć.

Zabie­ra­jąc się za pisa­nie testów próbo­wa­łem wzoro­wać się na kimś dobrym. Uzna­łem, że twórcy Face­bo­oka speł­niają to kryte­rium :) , dlatego dosyć dokład­nie prze­ana­li­zo­wa­łem ich SDK na GitHu­bie. Przy okazji mogłem zmał­po­wać rozwią­za­nie problemu testów metod chro­nio­nych: tworzysz sobie klasę PublicNazwaOryginalna i wysta­wiasz metody chro­nione jako public. Np. tak:

class PublicSpiechuCookieEncrypter extends SpiechuCookieEncrypter {
    public function publicCheckMcryptList($list, $check) {
        return $this->checkMcryptList($list, $check);
    }
}

Całość moich testo­wych wypo­cin znaj­dzie­cie również na Paste­bi­nie. Wszyst­kiego nie będę tłuma­czył, zamiast tego zwrócę uwagę na kilka kluczo­wych rzeczy:

  • do metod aser­cji można doda­wać na końcu opcjo­nalny komu­ni­kat co nawaliło,
  • jeżeli masz ochotę wysy­pać kod i ocze­ku­jesz wyjątku, to najle­piej do prze­chwy­ce­nia użyć metody setExpectedException('NazwaWyjatku')
  • jeżeli dwie metody są tak ściśle powią­zane, że nie ma sensu odpa­lać testu jednej, jeżeli test drugiej nawali, należy w tej zależ­nej dopi­sać w komen­ta­rzu @depends nazwaPierwszejMetody
  • jeżeli mamy ochotę prze­te­sto­wać całą sekwen­cję różnych warto­ści jednym testem, możemy napi­sać metodę dostar­cza­jącą dane w postaci tablicy tablic oraz skie­ro­wać metodę korzy­sta­jącą z danych do dostar­czy­ciela poprzez komen­tarz @dataProvider nazwaMetodyDostarczajacej

Pisa­nie dobrych testów jest sztuką. Musimy prze­wi­dzieć scena­riu­sze co może pójść nie tak. W moim przy­padku kod testu­jący zajmuje więcej miej­sca niż testo­wany, a na pewno by się jesz­cze kilka rzeczy znala­zło do prze­te­sto­wa­nia. Czasem trzeba się nieźle nagim­na­sty­ko­wać żeby prze­pro­wa­dzić test we właściwy sposób.

Póki co progra­mo­wa­nie trak­tuję jako zabawę (zawo­dowo zajmuję się czymś zupeł­nie innym), a testy jednost­kowe dostar­czają mi nową porcję zaba­wek — czego i Wam życzę :-D

Podobne wpisy:

  1. Wzorzec projek­towy memento w PHP
  2. Stan­dardy kodo­wa­nia zgodne z Zend, cz. 2/3
  3. Wygrze­bane z GitHuba (3) : Validation
  4. Śpie­chu certyfikowany

2 Comments

  • 7 listopada 2010 - 08:26 | Permalink

    Aby z testów korzy­stać efek­tyw­nie, należy sobie przede wszyst­kim usta­wić porząd­nie środo­wi­sko testowe. Sam TDD spraw­dza się, gdy wiemy, co chcemy napi­sać i jak to powinno dzia­łać. Stoso­wa­nie tego do wszel­kiego rodzaju ekspe­ry­men­tów to niepo­ro­zu­mie­nie, bo zanim dojdziesz do czegoś sensow­nego, musiał­byś wszyst­kie testy z 10 razy przepisywać.

  • 11 listopada 2010 - 18:14 | Permalink

    Przed­mówca szerzy nieprawdę. Prze­czy­taj „Test-Driven Deve­lop­ment by exam­ple” Kenta Becka. Ten czło­wiek współ­two­rzy m.in. JUnit, wie co mówi.

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>