w Praktyce
7 godz. 1 min · Angular · Full-stack i Programowanie
Mateusz KuleszaSenior Software Developer, Konsultant, TrenerPodstawą produktywnej pracy z angularem jest możliwość zagwarantowania że wszystko działa bez potrzeby wielokrotnego “przeklikiwania” poszczególnych ekranów. W pierwszej części kursu omawiam więc temat testów automatycznych.Już po obejrzeniu pierwszych lekcji zobaczysz dlaczego warto testować kod i jak dzięki narzędziom dostarczonym z angularem jest to proste. Zobaczysz jak testowanie automatyczne pozwoli zaoszczędzić Ci wiele godzin spędzonych na szukaniu błędów.
Za jakość aplikacji odpowiada nie tylko to czy działa ona poprawnie, ale w dużej mierze decyduje o tym dobry interfejs użytkownika oraz UX, czyli user experience. Dwie kolejne sekcje kursu poświęcone są właśnie dobremu UI oraz UX. Omawiam typowe problemy w oparciu o zasady i wskazówki specyfikacji Google Material Design - jednej z najbardziej szczegółowych specyfikacji UX. Wszystkie przykłady UI zarówno w wariantach desktopowych jak i mobilnych omówione są na przykładzie Angulara oraz obszernej biblioteki komponentów Angular Material. Zobaczysz nie tylko jak korzystając z gotowych komponentów błyskawicznie budować nowe funkcjonalności, ale przy okazji omówimy wiele praktycznych wskazówek oraz dobrych praktyk UX które warto wykorzystać w Twoich aplikacjach.
W ramach budowania interfejsów skupimy się na najmniejszych szczegółach takich jak prawidłowe zachowanie linków, przycisków oraz wskaźników przechodząc stopniowo do coraz większych elementów aplikacji takich jak nawigacja, układ strony, paski nawigacyjne czy okna dialogowe. Przy każdym etapie czeka Cię masa wskazówek, przykładów i rozwiązań typowych problemów user experience.
Jednak UX to nie tylko wygląd i zachowanie pojedynczych elementów. W ostatnich sekcjach kursu dowiesz się jak projektować bardziej złożone interakcje z użytkownikiem. Zobaczysz wieloetapowe formularze kreatora oraz dowiesz się jak zarządzać stanem aplikacji oraz różnymi źródłami danych. Pokaże Ci jak zbudować samodzielnie sortowane, filtrowane i stronicowane źródła danych oraz połączyć je z różnymi komponentami UI takimi jak listy czy datagrid.
Na koniec kursu zobaczysz jak możemy różne gotowe elementy UI połączyć w przepływ ekranów budując ścieżkę użytkownika. Zobaczysz jak prawidłowo zaprojektowane komponenty wraz z dobrze zaplanowanym UX pozwalają być niesamowicie produktywnym jednocześnie nie poświęcając jakości czy dobrej architektury aplikacji.
Jest to kurs dla osób które już pracowały z angularem i chciałyby wyjść poza pojedyncze techniki oraz poznać proces projektowania aplikacji w praktyce z uwzględnieniem najlepszych praktyk programistycznych oraz user experience. Zarówno w wariancie webowym jak i projektując pod urządzenia mobilne.
Angular 6, 7, 8+
W poprzednich lekcjach umówiliśmy konfigurację testów oraz pokazałem
ci jak zbudować prostą tutaj strukturę testów korzystając z
frameworka tutaj jasmine i z asercji także które oferuje
nam jasmine nie mniej tego typu testy nadają się
tylko do testowania kodu javascript'owego kodu javascript'owego
bez angulara możesz tutaj klasy funkcje możesz te funkcje
zaimportować i możesz tutaj jak widzieliśmy wcześniej sprawdzić
wiele różnych asercji tak czyli czy nasza wartość oczekiwana
jest równa czy jest większa mniejsza i tak dalej możemy
sobie udowodnić że nasze testy działają jednak do pracy z angularem to
jest troszeczkę za mało mianowicie ja mogę utworzyć po prostu klasę
javascript'ową w ten sposób ale tutaj jak widzisz to
jest to po prostu zwykła klasa jeśli będziemy testować elementy angulara
więc będziemy mieli komponenty no to potrzeba nam będzie czegoś
więcej przejdę więc tutaj do konsoli i przy użyciu narzędzia tutaj
angular cli ng generate component wygeneruje
nowy komponent i tutaj generalnie jeśli masz domyślne ustawienia
to angular powinien nam automatycznie wygenerować testy
do tego i jeśli tu na przykład jest spec false
to oznacza że testy nie mają być generowane jeśli domyślnie
masz ustawione tutaj w konfiguracji angular json że nie
chcemy testów no to tutaj można też ustawić na true czyli
powiedzieć wyjątkowo dla tego elementu chcemy ja wyjątek zacznę właśnie bez
testów po to żeby ten test skonfigurować zupełnie
ręcznie od początku żeby móc ci wytłumaczyć po kolei które elementy co robią w
następnych lekcjach i także tobie w projekcie polecam po prostu już generować
komponenty od razu z taką domyślną podstawową
strukturą testu ja ten pierwszy komponent zrobimy taki komponent powiedzmy
zadania na liście czyli task list
item mogę w ten sposób zapisać mogę zrobić to task list
item w ten sposób okej
zobaczmy i wygenerował się tutaj raz dwa trzy
pliki html type script i scss i
oczywiście to zarejestrowało mi się tutaj w module czyli w apple module test
pojawił się nasz task list component jest on zadeklarowany tutaj w deklaracjach
komponentów komponent tutaj jest bardzo prosty nie ma żadnej logiki ma tylko
html i css
ja teraz dla tego komponentu naszego utworzę plik testów
utowrzę sobie nowy plik
spróbujemy go zapisać w tym katalogu task list item
pod nazwą task list item component
spec ts czyli tak jak mamy tutaj nasz wzorzec wyszukujący testy
i podobnie jak w przypadku naszej klasy opiszemy tutaj przy użyciu jasmine
co to jest za test czyli to jest describe
i opisujemy nasz task list component
tutaj powiedzmy zrobimy sobie pierwszy test it should create
czyli powinniśmy móc stworzyć taki komponent powiedzmy
sobie new component przypisze do zmiennej
może w ten sposób to
trzeba oczywiście zaimportować zaimportujemy sobie może
w ten sposób relatywnie względna ścieżka że w tym katalogu po
prostu znajdź ten nasz komponent i sprawdzimy component
to be i zrobimy może fossil falsy zobaczmy
czy ten test w ogóle będzie działał teraz uruchommy sobie na sucho ng test zobaczymy czy nasz
test będzie tutaj uwzględniony aplikacja
się buduje tu nam się uruchomiło okno przeglądarki i
mamy jeden błąd tak jak błąd że nasz
komponent jest nieprawdziwy czyli nasz jest tutaj działa zróbmy na prawdziwy
i zielono tutaj wszystkie testy jak widzisz poprzednie
testy które tu były wygenerowane automatycznie się uruchamiają testy naszej klasy pustej
też tou się uruchamiają no i tutaj nasz nowy test komponentów także się uruchomił
mamy 6 na 6 sukces okej czyli jak widzisz w ten
sposób mogę utworzyć instancje komponentu niemniej tworzę tylko i wyłącznie
instancji klasy więc ten test zupełnie się nie różni od tego testu który
mieliśmy zbudowanego wcześniej tutaj możemy wytestować zwykłe funkcje klasy
obiekty javascript'owe niemniej pojawia się pewien problem bo
gdybym chciał na przykład zobaczyć czy nasz task
list item komponent powiedzmy czy on prawidłowo
wyświetla powiedzmy ten html tutaj no to jest problem bo ten
html jak widzisz nie istnieje w klasie on jest generowany osobno
przez angulara pytanie więc brzmi jak
uruchomić ten komponent właśnie w kontekście angulara tak żeby został
html wyrenderowany zostało wykrywanie zmian dependency
injection cały mechanizm angulara a nie tylko utworzona sama klasa
i spójrzmy na plik main tutaj test w którym startuje i uruchamia się
cały angular to jak widzisz uruchamiamy tej platform browser i uruchamiamy
moduł app module i tutaj startuje angular to się uruchamia cała
aplikacja angulara uruchamiamy app module i w app module jak widzisz mamy zdefiniowany
główny komponent komponent który chcemy uruchomić który chcemy na
stronie wyrenderować razem z html'em i ten w tym komponencie możemy użyć
na przykład tego komponentu i angular odpowiednio wszystkie te komponenty nam wyrenderuje
wyświetli na ekranie teraz mógłbym to samo w testach zrobić
tylko był mały problem nie mogę samego komponentu uruchomić musimy go razem
z modułem uruchomić jeśli zaimportuje cały nasz app module no to musiał
przy okazji w testach uruchomić także routing module browsing module i wszystkie
inne moduły plugin wtyczki klasy usługi wszystko to co
mam tutaj w app module to wszystko musiałbym uruchomić także w testach jak
pamiętasz mamy testy jednostkowe i testy jednostkowe polegają na tym aby
skupić się na wycięciu jednej jednostki logicznej
z aplikacji i testowaniu tej jednostki jakby w oderwaniu
od reszty aplikacji czyli w taki sposób żeby przetestować sam element bez
wpływu na niego innych elementów czyli musi być musielibyśmy właśnie stworzyć
no właśnie sam komponent nie możemy tworzyć samego komponentu ale
moglibyśmy stworzyć osobny moduł tylko na
potrzeby testowania tego komponentu i teraz angular dysponuje bardzo fajnym mechanizmem
mianowicie w naszych testach możemy użyć czegoś takiego jak test
bed tak zwane łoże albo uprząż testowa jest
to takie środowisko testowe które pozwala nam jakby w oderwaniu
od całej aplikacji utworzyć jakieś elementy angulara
i uruchomić je zobaczyć jak one się zachowują właśnie po to
żeby móc w testach je zbadać z każdej strony zobaczyć
i jak się zachowują jeśli wykonamy na nich pewne operacje
jest w naszym pierwszym teście użyję tego łoża
testowego test bed i tutaj jak widzisz mamy tutaj kilka takich
przydatnych metod możemy skompilować komponenty skonfigurować
kompilator skonfigurować moduł testowy utworzyć
komponent i pobrać instancje różnych
klas różnych funkcji usług komponentów używając tokenów
dependency injection nas oczywiście interesuje utworzenie modułu testowego
który pozwala utworzyć specjalnie pod testy taki
testowy moduł który symuluje nasz główny moduł aplikacji ale możemy w nim podmienić
wszystkie dyrektywy providery wszystkie elementy które nie potrzebujemy
w teście możemy pasuje pominąć a te które chcemy podmienić możemy łatwo podmienić
tu jak widzisz definicję tego testing module praktycznie nie
odbiega za bardzo od definicji zwykłego modułu mamy tutaj deklaracje
mamy tak samo importy mamę providerów mamy
schemat czyli tak jak widzisz mamy praktycznie te same opcje co w przypadku zwykłego modułu jednak
tu konfigurujemy testowy moduł testowy aby móc naszą tutaj nasz komponent
po prostu w oderwaniu od całego środowiska przetestować zobaczyć jak on sam się zachowuje
bez wpływu innych elementów czyli tak declarations okej
tutaj musimy zadeklarować nasz komponent zauważ że nie ma tu opcji bootstrap
tak bo my nie chcemy żeby to startował nam samo my chcemy w testach dokładnie
znać moment kiedy chcemy uruchomić dany komponent okej
czyli w ten sposób mamy testowy moduł właśnie z naszym komponentem zarejestrowanym
w dekoracjach i tutaj jest jeden mały problem mianowicie
komponent może mieć tutaj jak widzisz html i style
w osobnych plikach i teraz na potrzeby testu te pliki
muszą być załadowane razem z komponentem muszą być skomplikowane
i to wszystko jak rozumiesz muszę załadować pliki z zewnątrz nie
może stać się natychmiast to musi przez chwilę potrwać więc ja będę ten cały mechanizm który
mechanizm jest asynchroniczny mianowicie tutaj jeśli ja zrobię
komponent tutaj mam i chciałbym go uruchomić muszę to już mieć compiled components
tak używając właśnie template url i
tutaj widzisz to jest potrzebne dlatego że ta funkcja jest asynchroniczna
że on musi pobrać tamte nasze url pod którym znajdują się szablony
html i css więc ta funkcja jeszcze przyjrzymy
się ona zwraca nam na końcu promise zwraca
obietnice czyli tutaj muszę zrobić then i dopiero
tutaj dopiero w tym miejscu mógłbym
wykonywać moje testy i oczywiście mogłbym te testy przesunąć do środka i tak
byłoby to bardzo niewygodnie musiałbym w każdym teście tutaj robić ten compiled component then
i tak dalej druga
sprawa jest taka że jeśli będę chciał jeszcze drugi test tutaj dodać to musiałbym
przy drugim także utworzyć jeszcze raz cały ten moduł i jeszcze skompilować
komponenty i jeszcze raz to wszystko uruchomić i teraz pytanie dlaczego dlaczego
nie możemy wykorzystać ponownie tego modułu dlaczego całym
dlaczego chcemy go utworzyć na nowo na czysto w każdym teście dobrą
praktyką jest to aby testy były czyste to znaczy żeby testy
nie zawierały żadnych danych zmiennych komponentów które istniały
w poprzednich testach dlatego że przez przypadek możemy zanieczyścić test
jakimś starym testem na przykład test może powiedzieć że dane
istnieją że komponent ma dane załadowane są a to nie będzie prawda
bo dane po prostu zostaną nam jako artefakt z poprzedniego testu
i nasze kolejne testy mogą wykazywać nieprawdę dlatego jest
bardzo fajna opcja możemy z taką całą konfigurację przenieść do oczywiście bloku
before each i taki blok
before each pozwala mi właśnie tak jak nazwa wskazuje wykonać jakieś
przygotowanie konfigurację czy inicjalizację różnych obiektów zmiennych przed
każdym testem mamy tutaj before all który miałeś przed całą sekcją
before describe natomiast before each uruchamia się przed
automatycznym uruchamia się przed każdym id czyli cokolwiek tu umieszczę będzie jeszcze
raz uruchomione czyli nasz ten cały configure testing module mamy gwarancję
że każdy test będzie zaczynał od świeżego od czystego środowiska
nie będzie tam żadnych pozostałości i teraz taki mały problem
bo musimy tak robić żeby ten test pierwszy poczekał
na zakończenie tej kompilacji komponentów i teraz jasmine ma taką
funkcję możemy tutaj przekazać parametr dom jeśli jasmine
wykryje że tu się znajduje zmienna to zobacz co się stanie w tym momencie
z testami spróbuję uruchomić musimy chwilkę poczekać i tutaj
powinienem dostać błąd zobacz async callback was not invoked within timeout
specified czyli jeśli ja podam tutaj callback to
jasmine nie przejdzie dalej nie wyjdzie poza ten test dopóki
ten callback nie będzie wykonany jeśli w tym teście ja nie wykonam tego callbacka to
znaczy że coś poszło nie tak i on przerwie wykonywanie testów tutaj mam informację że czekał
określoną w konfiguracji długość czasu i ten
test uznał za uszkodzony że on tu nie wywołuje się w określonym
czasie i teraz żeby to zadziałało po prostu done muszę
wykonać w tym miejscu tutaj mam funkcję w których mamy funkcje
mogą też prościej zapisać i po prostu wsadzić done jako callback
bezpośrednio do then zobaczmy
teraz poszło tu idealnie bo
ten pierwszy before each poczekał na zakończenie tego i
dopiero wtedy nam przeszedł do tego drugiego testu czyli jak
widzisz już ten sposób bardzo fajnie bardzo łatwo można testy asynchroniczne wykonać tutaj
dodajemy sobie callback i tu mamy pewność że test zakończył się jakby
asynchronicznie operacje zakończył że możemy iść dalej dopiero wtedy wykonuje ten callback
i ten test wtedy ta sekcja before each wie że cała
konfiguracja została zakończona asynchronicznie i że możesz śmiało przejść do kolejnych testów tak
samo funkcji done możesz użyć w kolejnych testach żeby też opóźnić wykonywanie
kolejnego testu dopóki ten się nie skończy aczkolwiek o tym powiemy sobie później okej
i teraz to wszystko fajnie ale my nadal tworzymy komponent
tutaj własnoręcznie mając ten nasz test bed i mając
ten nasz tutaj skonfigurowany główny moduł testowy mamy skompilowane komponenty
i wtedy w teście ja mogę zrobić tutaj sobie test bed
create component tutaj widzisz muszę przekazać typ komponentu
czyli nasz task list component i to nam zwróci
coś takiego nazywa się w component fixture typu task list item
ja zrobiłem sobie const fixture ok i
w tym fixture między innymi mamy
coś takiego jak component instance
i tutaj znajduje się utworzona utworzony komponent ale już utworzony tutaj
przez angulara nie przez nas jak widzisz nie ma tu słowa new tylko ja deleguje
żeby angular wewnątrz tego modułu wraz z wszystkimi deklaracjami importami
providerami konfiguracją angulara żeby on za mnie uruchamiał komponent i tu
zobaczymy sobie jest komponent ale oprócz tego powiedzmy
napiszę sobie tutaj html native element
inner html i
zobaczymy sobie console log html jak
widzisz tutaj mamy tym razem nie tylko samą instancję klasy komponentu
a mamy całe komponent wyrenderowany wraz z html'em wraz
z odpowiednimi tagami css'owymi w ten sposób jak widzisz mogę
uruchomić angulara ale uruchomić go w taki sposób żeby wyrenderował tylko
i wyłącznie nasz pojedynczy element tutaj jak widzisz przy testach angular po
kolei tutaj pod testami będzie po prostu uruchamiał renderował nasz komponent
w prawdziwej przeglądarce jako prawdziwy html tu nam się pojawił dokładnie
ten sam html tu jeszcze inna ciekawa rzecz ja
mógłbym to wszystko jakby kopiować za każdym razem wklejać za każdym razem gdy
potrzebuję właśnie któryś z tych rzeczy albo możemy utworzyć koleją
sekcje before each before each
i mogę sobie na przykład te dwie rzeczy których ja tu potrzebuję
przyniosę wyżej zmiennych nie zadeklaruję
tutaj tylko zmienną fixture zadeklaruje na samej górze jako let fixture
typu component fixture
i nasz typ komponentu mi podpowiada tutaj prawidłowo
wszystkie opcje parametry i komponent nasz czyli
let component także tutaj nam sobie przypisze do zmiennej to jest
bardzo fajna rzecz bo ja będę miał tworzone te dwie rzeczy before each
czyli przed każdym testem ale nie muszę tworzyć
tu w teście i jak widzisz już przepisuje do zmiennych które
są utworzone tu na samej górze czyli ta zmiana będzie za każdym razem czyszczona ustawiona
na nowo przed każdym testem ale we wszystkich tutaj sekcjach before each id i każdym tutaj
teście ja mam dostęp do tego fixture jak widzisz tutaj fixture tutaj piszemy
prawidłowo wskazuje na ten fixture i przy każdym tekście mamy też gwarancję
że przy każdym teście będzie on niszczony tworzony na nowo będzie za każdym razem miał nowy
świeży komponent świeżą instancje stworzoną ze świeżego
nowego modułu okej tutaj
jest jedna fajna rzecz jeśli mamy tutaj gdzieś w teście promise i
mamy jasmina to ten done to jest jedna opcja to tak wykomentuje na razie
okej że mogę wewnątrz tutaj testu lub
id lub wewnątrz before each jeśli mam promise to znaczy że ja
zwrócę takie nierozwiązany promise i on tak samo jak bym tu dał callback
then i wywołał callback on wtedy zrobi to za mnie czyli jeśli ja tutaj zwrócę
promise to on także sobie
z tym poradzi jest jeszcze jedna fajna opcja zamiast zwracać
promise mogę zrobić to jeszcze inaczej mogę tutaj z angular testing
zaimportować coś takiego jak async i mogę cały test objąć
właśnie tutaj w tą operację async uwaga to nie jest słowo kluczowe async to jest zwykła
funkcja angulara async jeśli opakuję nasz test w async to jest
jeszcze prościej bo on sam wykryje czy mam jakieś tutaj promisy i
wtedy już nie muszę przejmować się tutaj tymi callbackami po
prostu on sam będzie wiedział kiedy ten promise się zakończy i od zablokuje
wywołanie kolejnych testów dopóki ten test się tutaj nie uruchomi okej
dobra mamy html i w ten sposób mniej więcej wygląda
konfiguracja tutaj testów dla komponentów
angular ale nie tylko bo tak samo jakbyś chciał testować dyrektywy czy jakieś
usługi check i inne rzeczy to musisz zawsze stworzyć moduł testowy i
dopiero wtedy możesz poprosić ten moduł tą uprząż testową żeby
utworzyła ci dany moduł lub usługę i w twoich testach możesz elegancko korzystać
z elementów których dostarcza po prostu tutaj nam angular możesz
też w ogóle polecam wygeneruj sobie jeszcze jeden komponent wygeneruj go z testami
i porównać jak bardzo się różni ta nasza wersja od tej
wersji automatycznej generowanej przez angulara i tak samo jak już będziesz tworzył nowe
komponenty to właśnie nie pomijaj testów generuj testy będziesz miał
tą część jakby załatwioną będziesz mógł od razu skupić się na pisaniu testów
i tym właśnie zajmiemy się w kolejnych lekcjach umiemy fixture
i omówimy sobie jak wykorzystać te wszystkie elementy które nam angular w ogóle dostarcza żeby wygodnie
pisać testy ale o tym już w kolejnych lekcjach dozobaczenia