Czas na kolejny post z serii „TDD dla początkujących”. Ostatnim razem przedstawiłem podstawowe założenia metodologii TDD. Zilustrowałem też przykładem proces tworzenia oprogramowania zorientowanego testowo. Na koniec obiecałem, że opiszę dwa przydatne narzędzia, znacznie usprawniające pracę w TDD.
Te narzędzia to Jasmine i Karma. Jasmine jest frameworkiem, dającym programistom JS wiele przydatnych funkcji, pomagających w tworzeniu testów. Karma służy do automatycznego uruchamiania tych testów.
Do wykorzystania omawianych dziś narzędzi potrzebne jest środowisko nodejs. Jest ono obsługiwane z linii komend. Mam wrażenie, że początkujący programiści, szczególnie Ci bez wykształcenia w dziedzinie informatyki, obawiają się używania linii komend. Nie potrzebnie.
Dziś będę sporo korzystał z linii komend. Mam nadzieję, że uda mi się pokazać, że nie ma nic strasznego w tym środowisku 🙂
Pierwszą rzeczą jaką należy zrobić to instalacja node’a. Instrukcje jak to zrobić można znaleźć na ich stronie.
Aby sprawdzić czy node jest zainstalowany należy otworzyć linie komend i wpisać
1 |
node -v |
Jeżeli zobaczysz, numer wersji, oznacza to, że node zainstalował się poprawnie.
Kolejny krok to instalacja Karmy oraz Jasmine. Można to zrobić bardzo wygodnie dzięki node’owi i jego menadżerowi paczek npm. W linii komend, należy wpisać po kolei:
1 |
npm install karma -g |
aby zainstalować Karme. Następnie:
1 |
npm install karma-jasmine -g |
Aby zainstalować Jasmine. Na końcu jeszcze:
1 |
npm install karma-chrome-launcher -g |
aby zainstalować automatyczny launcher chroma. Będzie do przeglądarka używana przez karmę do automatycznego przeprowadzania testów.
Oczywiście każde polecenie należy potwierdzić wciskając enter 🙂 Upewnij się też, że uruchamiasz je jako administrator, lub użytkownik mający uprawnienia do instalowania oprogramowania na komputerze.
Teraz pora przygotować projekt, nad którym będę pracował. Po prostu tworzę katalog o pięknej nazwie „TEST”. Wewnątrz tego katalogu tworzę kolejny – „JS”. W nim będą znajdować się wszystkie skrypty. Aby w tym projekcie korzystać z Karmy, muszę ją zainicjalizować. Robię to z linii komend. Najpierw przechodzę do katalogu projektu czyli TEST i wpisuję
1 |
karma init |
W oknie konsoli pojawią się pytania dotyczące ustawień Karmy dla tego projektu. Domyślne odpowiedzi powinny być ustawione tak, że pasują idealnie. Pierwsze to wybór frameworka do testowania (powinno być ‚Jasmine’. Jeśli wyświetla się coś innego możemy zmienić wybór tabem). Drugie pytanie to czy chce używać requireJS – ‚No’. Trzecie to w jakiej przeglądarce testowany ma być kod. Wybieram ‚Chrome’ ponieważ zainstalowałem launcher własnie tej przeglądarki. Dwa kolejne pytania, to które pliki mają być testowane a które pomijane. Te pola na razie zostawiam puste. Ostatnie pytanie, to czy chcę aby pliki w projekcie były obserwowane przez Karmę w czasie rzeczywistym. Chcę, dlatego wybieram – ‚yes’.
W katalogu TEST powinien pojawić się nowy plik – karma.confg.js. Aby uruchomić karmę w konsoli, będąc w katalogu TEST wpisuję
1 |
karma start |
Pojawi się okno chrome’a zawierające informację o tym, że Karma się połączyła. Natomiast w konsoli wyświetli się tekst zawierający informacje o przeglądarce i używanym przez nas systemie oraz takie zdanie
1 |
Executed 0 of 0 ERROR (0.016 secs / 0 secs) |
Oznacza to, że zero na zero testów zostało przeprowadzonych. Zgadza się ponieważ, żadne testy nie zostały jeszcze zdefiniowane. Czas aby się tym zająć.
W katalogu JS tworzę dwa pliki. Chcę aby kod testów i faktyczny kod projektu były oddzielone. Pierwszy plik nazywam scripts.js a drugi tests.js. Struktura katalogu projektu powinna wyglądać tak:
1 2 3 4 5 |
TEST -JS --scripts.js --tests.js -karma.confg.js |
Póki co, oba pliki są puste. Zanim dodam do nich treść, chcę zmienić ustawienia karmy, tak aby były przez to narzędzie obserwowane. Oznacza to, że wszystkie testy, które dodam do obserwowanych plików, będą wykonywane, gdy tylko, któryś z nich zapiszę. Wyniki będą pojawiały się bezpośrednio w konsoli. Wygodne? Pewnie 🙂
Aby zakończyć pracę Karmy, należy wcisnąć ctrl + z gdy konsola jest aktywna. Okno Chroma, które otworzyło się samo, musi być włączone gdy Karma działa. Można je po prostu zminimalizować i o nim zapomnieć.
Dodanie plików do obserwowania przez karmę jest bardzo proste. Otwieram plik karma.confg.js i szukam takiego fragmentu:
1 2 3 |
// list of files / patterns to load in the browser files: [ ], |
Do tablicy files dodaje, jako łańcuchy znaków ścieżki do plików, które mają być obserwowane. Główny katalog projektu, w którym zainicjalizowałem karmę, jest traktowany jako root systemu plików. Mogę więc użyć ścieżek względnych. Tak wygląda powyższy fragment kodu po dodaniu moich plików:
1 2 3 4 5 |
// list of files / patterns to load in the browser files: [ 'JS/scripts.js', 'JS/tests.js' ], |
Istnieje sprytniejszy sposób na dodanie większej ilości plików ale nie będę się w to tu zagłębiał. Ważne, że te dwa pliki są już obserwowane przez Karmę.
Czas dodać pierwszy test. W pliku tests.js wpisuję następującą treść:
1 2 3 4 5 |
describe("test",function(){ it("should be true",function(){ expect(true).toBe(true); }); }); |
Syntaks, którego tu używam to Jasmine. Za pomocą funkcji describe tworzę nowy zestaw testów. Przyjmuje ona dwa argumenty, pierwszy to łańcuch znaków reprezentujący nazwę zestawu a drugi to bezimienna funkcja. Będzie ona zawierać kod testów. We wnętrzu funkcji argumentu describe, wywołuję kolejną funkcję o nazwie it. Każde wywołanie it to jeden test obsłużony przez Karmę.
it również przyjmuje dwa argumenty. Tak samo jak w przypadku describe, jest to nazwa testu przekazana w postaci łańcucha znaków, oraz bezimienna funkcja, zawierająca już bezpośrednio treść testu.
A sam test to funkcja expect, która jako argument wartość true. Następnie wywoływana jest metoda toBe, która jako argument otrzymuje taką samą wartość. Dla znających angielski, ta składnia czyta się jak normalne zdanie. Można by to było prawie dosłownie przetłumaczyć na „spodziewaj się, że true, będzie true” :).
wartość argumentu przekazywanego funkcji expect, będzie porównywana w tym teście. W jaki sposób będzie porównywana i z jaką wartością zależy od metody expect, która jest wywoływana. W moim przypadku jest to metoda toBe. toBe sprawdza czy argument przekazany expect jest równy argumentowi przekazanemu do toBe.
Inne przykładowe metody porównujące expect w Jasmine to:
- toBeDefined – czy nie jest równe undefined.
- toBeLessThan – czy jest mniejszy niż…
- toBeTruthy – czy zawiera wartość traktowaną jako true.
Nie wszystkie muszą przyjmować argumenty. Nie są to też wszystkie metody a jedynie przykłady. Co ciekawe istnieje też metoda not, którą można wstawić pomiędzy expect a metodę porównująca. Wtedy efekt jest odwrócony. Na przykład:
1 |
expect(true).not.toBe(true); |
Będzie sprawdzało czy true, nie równa się true. Tam nie ma błędu, po not, nie pojawiają się nawiasy 🙂
Wracając do mojego przykładu. Gdy dodam test do pliku tests.js i zapiszę ten plik. Karma od razu ten test wykona i przedstawi wynik w konsoli. W tym wypadku wynikiem będzie
1 |
Executed 1 of 1 SUCCESS |
Jedno wywołanie funkcji it to jeden test. True jest równe true, więc test przeszedł. Sukces 🙂 Ale jeśli zmienię kod na:
1 |
expect(false).toBe(true); |
I zapiszę plik. Karma od razu zasygnalizuje w konsoli błąd. Wypiszę nazwę podaną w describe w której znajduje się it, które zawiera nieudany test. Nazwa tego it, również zostanie wypisana, więc od razu wiadomo gdzie jest problem.
Ponieważ Karma śledzi dwa pliki, treść scripts.js również brana jest pod uwagę w czasie testów. W pliku scripts.js tworzę zmienną variable1:
1 |
var variable1 = true; |
Następnie korzystam z tej zmiennej w teście w pliku test.js:
1 2 3 4 5 |
describe("test",function(){ it("should be true",function(){ expect(variable1).toBe(true); }); }); |
Zapisuję i sprawdzam konsolę. Sukces, test został zakończony pomyślnie.
Skoro już wszystko jest ustawione i wiadomo co jak działa. Czas przerobić przykład, który omawiałem w poprzednim poście. Chcę stworzyć obiekt, którego metoda będzie łączyć dwa łańcuchy znaków w jeden. Oczywiście będę korzystał z technik TDD.
Usuwam zawartość obu plików w projekcie i do pliku test.js dodaję następujące testy.
1 2 3 4 5 6 7 8 9 10 11 |
describe("Obiekt łączący dwa różne łańcuchy znaków",function(){ it("czy ten obiekt istenieje",function(){ expect(concatClass).toBeDefined(); }); it("czy ten obiekt posiada odpowiednią metodę", function(){ expect(concatClass.concat).toBeDefined(); }); it("czy metoda obiektu zwraca co trzeba", function(){ expect(concatClass.concat('hello','world')).toBe('hello world'); }); }); |
Tak jak można było się spodziewać, Karma wypisuje mi trzy błędy. Żaden z trzech testów nie zakończył się pomyślnie.
Dodam odpowiednią klasę do pliku scripts.js. Jego treść będzie wyglądała tak:
1 |
var concatClass = {}; |
Po zapisaniu zerkam w konsolę. Wciąż mam powiadomienia o błędach, ale już tylko dwa. Jeden test przeszedł. concatClass jest już zdefiniowane. Kolejny krok to oczywiście dodanie odpowiedniej metody:
1 2 3 4 5 |
var concatClass = { concat: function(){ return; } }; |
Zapisuję plik i sprawdzam konsolę. Już tylko jeden test, nie przechodzi. Karma wypisuje nazwy zestawu testów oraz samego testu, który się nie udał
1 |
Obiekt łączący dwa różne łańcuchy znaków czy metoda obiektu zwraca co trzeba FAILED |
Ponieważ w miarę sensownie ponazywałem swoje testy, od razu wiem gdzie jest problem. Metoda nie zwraca tego co trzeba, czyli hello world. Zmienię to:
1 2 3 4 5 |
var concatClass = { concat: function(){ return 'hello world'; } }; |
Sukces! Wszystkie testy zostały zaliczone. Oczywiście wiem, że to jeszcze nie koniec, teraz czas na usprawnienie kodu. Ale to już tylko formalność:
1 2 3 4 5 |
var concatClass = { concat: function(string1,string2){ return string1 + " " + strign2; } }; |
Upss… błąd? No tak, literówka. Zamiast string napisałem strign. Dzięki testom, od razu wiedziałem gdzie szukać błędu i zareagowałem w porę. Wykrycie takich literówek wcale nie jest proste po paru godzinach siedzenia przed komputerem 🙂 Na szczęście zautomatyzowałem swoje testy, więc jestem kryty 🙂
Końcowa zawartość pliku scripts.js:
1 2 3 4 5 |
var concatClass = { concat: function(string1,string2){ return string1 + " " + string2; } }; |
Wszystkie testy zaliczone!
W tym poście pokazałem jak zainstalować i uruchomić narzędzie do automatyzacji testów Karma. Przy pomocy frameworka Jasmine, napisałem testy, które były uruchamiane po każdej zmianie w obserwowanych plikach.
Oczywiście to tylko czubek góry lodowej. W kolejnych postach pokażę jak pisać bardziej złożone testy dla większych aplikacji. Upewnij się, że polubiłeś moją stronę na facebooku. Dzięki temu będziesz zawsze na bieżąco z nowymi postami. Zawsze zamieszczam tam wszelkie informacje o nowościach.
1) error braj jasmine-core ( zainstalowałem )
2) pierwszy test Error
Chrome 59.0.3071 (Windows 7 0.0.0) ERROR
Uncaught SyntaxError: Unexpected token <
at […]
Bardzo fajny tutorial. Super napisany. Wszytko działa.
Dzięki, pierwszy tutek który startuje od dobrego zdefiniowania co się testuje i co gdzie jest; inne przechodzą od razu do mięsa zakładając że jtoś tam już zna podstawy. Duży +
Niepotrzebnie*