Pozwolisz, że w artykule będę posługiwał się anglojęzyczną nazwą „tag helper” zamiast „tag pomocniczy”, bo to po prostu brzmi jakby ktoś zajeżdżał tablicę paznokciem.
Czym jest tag helper?
Patrząc od strony Razor (niezależnie czy to RazorPages, czy RazorViews, robi się to identycznie), tag helper to nic innego jak tag w HTML. Ale nie byle jaki. Stworzony przez Ciebie. Kojarzysz z HTML tagi takie jak a, img, p, div itd? No więc tag helper od strony Razor to taki dokładnie tag, który sam napisałeś. Co więcej, tag helpery pozwalają na zmianę zachowania istniejących tagów (np. <a>). Innymi słowy, tag helpery pomagają w tworzeniu wyjściowego HTMLa po stronie serwera.
To jest nowsza i lepsza wersja HTML Helpers znanych jeszcze z czasów ASP.
Na co komu tag helper?
Porównaj te dwa kody z Razor:
<div>
@Html.Label("FirstName", "Imię:", new {@class="caption"})
</div>
Który Ci się bardziej podoba? caption-label to właśnie tag helper. Jego zadaniem jest właściwie to samo, co metody Label z klasy Html. Czyli odpowiednie wyrenderowanie kodu HTML. Jednak… no musisz się zgodzić, że kod z tag helperami wygląda duuuużo lepiej.
(tag helper caption-label nie istnieje; nazwa została zmyślona na potrzeby artykułu)
Tworzenie Tag Helper’ów
Tworzenie nowego projektu
Utwórz najpierw nowy projekt WebApplication w Visual Studio. Jeśli nie wiesz, jak to zrobić, przeczytaj ten artykuł.
Nowy Tag Helper
GitHub i NuGet są pełne customowych (kolejne nieprzetłumaczalne słowo) tag helperów. W samym .NetCore też jest ich całkiem sporo. Nic nie stoi na przeszkodzie, żebyś stworzył własny. Zacznijmy od bardzo prostego przykładu.
Stwórzmy tag helper, który wyśle maila po kliknięciu. Czyli wyrenderuje dokładnie taki kod HTML:
Przede wszystkim musisz zacząć od napisania klasy dziedziczącej po abstrakcyjnej klasie… TagHelper. Chociaż tak naprawdę mógłbyś po prostu napisać klasę implementującą interfejs ITagHelper. Jednak to Ci utrudni pewne rzeczy. Zatem dziedziczymy po klasie TagHelper:
//using Microsoft.AspNetCore.Razor.TagHelpers
public class MailTagHelper: TagHelper
{
}
UWAGA! Nazwa Twojej klasy nie musi zawierać sufiksu TagHelper. Jednak jest to dobrą praktyką (tak samo jak tworzenie tag helperów w osobnym folderze TagHelpers lub projekcie). Stąd klasa nazywa się MailTagHelper, ale w kodzie HTML będziemy używać już tagu mail. Takie połączenie zachodzi automagicznie.
Super, teraz musimy zrobić coś, żeby nasz tag helper <mail> zamienił się na HTMLowy tag <a>. Służą do tego dwie metody:
Process – metoda, która zostanie wywołana SYNCHRONICZNIE, gdy serwer napotka na Twój tag helper
ProcessAsync – dokładnie tak jak wyżej, z tą różnicą, że to jest jej ASYNCHRONICZNA wersja
Wynika z tego, że musimy przesłonić albo jedną, albo drugą. Przesłanianie obu nie miałoby raczej sensu. Oczywiście będziemy posługiwać się asynchroniczną wersją, zatem przesłońmy tę metodę najprościej jak się da:
//using Microsoft.AspNetCore.Razor.TagHelpers
public class MailTagHelper: TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
await base.ProcessAsync(context, output);
}
}
Teraz zastanówmy się, jakie parametry chcemy przekazać do helpera. Ja tu widzę dwa:
adres e-mail odbiorcy
tytuł maila
Dodajmy więc te parametry do naszej klasy w formie właściwości:
//using Microsoft.AspNetCore.Razor.TagHelpers
public class MailTagHelper: TagHelper
{
public string Address { get; set; }
public string Subject { get; set; }
}
Rejestracja Tag Helper’ów
Rejestracja to w tym wypadku to bardzo duże słowo. Niemniej jednak, żeby nasz tag helper był w ogóle widoczny, musimy zrobić jeszcze jedną małą rzecz.
Odnajdź w projekcie plik _ViewImports.cshtml i zmień go tak:
Jeśli Twój tag helper znajduje się w innym namespace (np. umieściłeś go w katalogu TagHelpers), „zaimportuj” ten namespace na początku (moja aplikacja nazywa się WebApplication1): @using WebApplication1.TagHelpers
Następnie „zarejestruj” swoje tag helpery w projekcie, dodając na końcu pliku linijkę: @addTagHelper *, WebApplication1
Ostatecznie mój plik _ViewImports.cshtml wygląda tak:
A teraz małe wyjaśnienie. Nie musisz tego czytać, ale powinieneś.
Plik _ViewImports jest specjalnym plikiem w .NetCore. Wszystkie „usingi” tutaj umieszczone mają wpływ na cały Twój projekt. To znaczy, że usingi z tego pliku będą w pewien sposób „automatycznie” dodawane do każdego Twojego pliku cshtml.
To znaczy, że jeśli tu je umieścisz, to nie musisz już tego robić w innych plikach cshtml. Oczywiście nigdy nie rób tego „na pałę”, bo posiadanie 100 nieużywanych usingów w jakimś pliku jeszcze nigdy nikomu niczego dobrego nie przyniosło 🙂
Zatem w tej linijce @using WebApplication1.TagHelpers powiedziałeś: „Chcę używać namespace’a WebApplication1.TagHelpers na wszystkich stronach w tym projekcie”.
A co do @addTagHelper. To jest właśnie ta „rejestracja” tag helpera. Zwróć najpierw uwagę na to, co dostałeś domyślnie od kreatora projektu: @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers. Zarejestrował on wszystkie domyślne tag helpery.
A teraz spójrz na tę linijkę, którą napisaliśmy: @addTagHelper *, WebApplication1. Jeśli coś Ci tu nie pasuje, to gratuluję spostrzegawczości. A jeśli masz pewność, że to jest ok, to gratuluję wiedzy 🙂
Można łatwo odnieść wrażenie, że rejestrujesz tutaj wszystkie (*) tag helpery z namespace WebApplication1. Jednak @addTagHelper wymaga podania nazwy projektu, a nie namespace’a. Akurat Bill tak chciał, że domyślne tag helpery znajdują się w projekcie o nazwie Microsoft.AspNetCore.Mvc.TagHelpers. Nasz projekt (a przynajmniej mój) nazywa się WebApplication1.
Przypominam i postaraj się zapamiętać:
Klauzula @addTagHelper wymaga podania nazwy projektu, w którym znajdują się tag helpery, a nie namespace’a.
Pierwsze użycie tag helper’a
OK, skoro już tyle popisaliśmy, to teraz użyjemy naszego tag helpera. On jeszcze w zasadzie niczego nie robi, ale jest piękny, czyż nie?
Przejdź do pliku Index.cshtml i dodaj tam naszego tag helpera. Zanim to jednak zrobisz, zbuduj projekt. Może to być konieczne, żeby VisualStudio wszystko zobaczył i zaktualizował Intellisense.
Specjalnie użyłem obrazka zamiast kodu, żeby Ci pokazać, jak Visual Studio rozpoznaje tag helpery. Pokazuje je na zielono (to zielony, prawda?) i lekko pogrubia. Jeśli u siebie też to widzisz, to znaczy, że wszystko zrobiłeś dobrze.
OK, to teraz możesz postawić breakpointa w metodzie ProcessAsync i uruchomić projekt.
Jeśli breakpoint zadziałał, to wszystko jest ok. Jeśli nie, coś musiało pójść nie tak. Upewnij się, że używasz odpowiedniego namespace i nazwy projektu w pliku _ViewImports.cshtml.
Niech się stanie anchor!
OK, mamy taki kod w tag helperze:
//using Microsoft.AspNetCore.Razor.TagHelpers
public class MailTagHelper: TagHelper
{
public string Address { get; set; }
public string Subject { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
await base.ProcessAsync(context, output);
}
}
Teraz trzeba coś zrobić, żeby zadziałała magia.
Popatrz co masz w parametrze metody ProcessAsync. Masz tam jakiś output. Jak już mówiłem wcześniej, tag helper RENDERUJE odpowiedni kod HTML. Za ten rendering jest odpowiedzialny właśnie parametr output. Spróbujmy go wreszcie wykorzystać. Spójrz na kod metody poniżej:
TagHelperOutput ma taką właściwość jak TagName. Ta właściwość mówi dokładnie: „Jaki tag html ma mieć Twój tag helper?”. Uruchom teraz aplikację i podejrzyj wygenerowany kod html:
<a>Test</a>
Twój tag <mail> zmienił się na <a>.
I o to mniej więcej chodzi w tych helperach. Ale teraz dodajmy resztę rzeczy. Musimy jakoś dodać atrybut href. Robi się to bardzo prosto:
Jak widzisz wszystko załatwiliśmy outputem. Myślę, że ta linijka sama się tłumaczy. Po prostu dodajesz atrybut o nazwie href i wartości mailto:.... itd. Uruchom teraz aplikację i popatrz na magię.
Wszystko byłoby ok, gdybyśmy gdzieś przekazali te parametry Address i Subject. Przekażemy je oczywiście w pliku cshtml:
Zwróć tylko uwagę, że wg konwencji klasy w C# nazywamy tzw. PascalCase. Natomiast w tag helperach używasz już tylko małych liter. Tak to zostało zrobione. Jeśli masz kilka wyrazów w nazwie klasy, np. ContentLabelTagHelper, w pliku cshtml te wyrazy oddzielasz myślnikiem: <content-label>. Dotyczy to również atrybutów.
Oczywiście możesz z takim tag helperem zrobić wszystko. Np. zapisać na sztywno mail i temat, np:
Przeanalizuj ten kod. Po prostu, jeśli nie podasz adresu e-mail lub tematu wiadomości, zostaną one wzięte z wartości domyślnych. Oczywiście te wartości domyślne mogą być wszędzie. W stałych – jak tutaj – w bazie danych, w pliku… I teraz wystarczy, że w pliku cshtml napiszesz:
<mail>Test</mail>
Fajnie? Dla mnie bomba!
Atrybuty dla Tag Helper
Tag helpery mogą zawierać pewne atrybuty, które nieco zmieniają ich działanie:
HtmlTargetElement
Możemy tu określić nazwę taga, jaką będziemy używać w cshtml, rodzica dla tego taga, a także jego strukturę, np:
[HtmlTargetElement("email")]
public class MailTagHelper: TagHelper
{
}
Od tej pory w kodzie cshtml nie będziemy się już posługiwać tagiem <mail>, tylko <email>. Możemy też podać nazwę tagu rodzica, ale o tym w drugiej części artykułu. Możemy też określić strukturę tagu. Może on wymagać tagu zamknięcia (domyślnie) lub być bez niego, np:
[HtmlTargetElement("email", TagStructure = TagStructure.NormalOrSelfClosing)]
public class MailTagHelper: TagHelper
{
}
W przypadku tak skonstruowanego tagu email nie ma to sensu, ale moglibyśmy to przeprojektować i wtedy tag można zapisać tak: <email text="Napisz do mnie" /> lub tak: <email text="Napisz do mnie"></email>
TagStructure może mieć takie wartości:
TagStructure.NormalOrSelfClosing – tag z tagiem zamknięcia, bądź samozamykający się – jak widziałeś wyżej. Czyli możesz napisać zarówno tak: <mail address="a@b.c"></mail> jak i tak: <mail address="a@b.c" />
TagStructure.Unspecified – jeśli żaden inny tag helper nie odnosi się do tego elementu, używana jest wartość NormalOrSelfClosing
TagStructure.WithoutEndTag – niekonieczny jest tag zamknięcia. Możesz napisać tak: <mail address="a@b.c"> jak i tak: <mail address="a@b.c" />
HtmlTargetElement ma jeszcze jeden ciekawy parametr służący do ustalenia dodatkowych kryteriów. Spójrz na ten kod:
[HtmlTargetElement("email", Attributes = "send")]
public class MailTagHelper: TagHelper
{
//tutaj bez zmian
}
Aby teraz taki tag helper został dobrze dopasowany, musi być wywołany z atrybutem send:
<email send>Napisz do mnie</email>
Ten kod zadziała i tag zostanie uruchomiony. Ale taki kod już nie zadziała:
<email>Napisz do mnie</email>
Ponieważ ten tag nie ma atrybutu send.
Przy pisaniu własnych tagów raczej nie ma to zbyt wiele sensu, ale popatrz na coś takiego:
<p red>UWAGA! Oni nadchodzą!</p>
I tag helper do tego:
[HtmlTargetElement("p", Attributes = "red")]
public class PRedTagHelper: TagHelper
{
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
await base.ProcessAsync(context, output);
output.Attributes.Add("style", "color: red");
}
}
Teraz każdy tekst w paragrafie z atrybutem red będzie napisany czerwonym kolorem. To daje nam naprawdę ogromne możliwości.
HtmlAttributeNotBound
Do tej pory widziałeś, że wszystkie właściwości publiczne tag helpera mogą być używane w plikach cshtml. No więc nadchodzi atrybut, który to zmienia. HtmlAttributeNotBound niejako ukrywa publiczną właściwość dla cshtml.
Stosujemy to, gdy jakaś właściwość (atrybut) nie ma sensu od strony HTML lub jest niepożądana, ale z jakiegoś powodu musi być publiczna. Spójrz na ten kod:
public class MailTagHelper: TagHelper
{
[HtmlAttributeNotBound]
public string Address { get; set; }
public string Subject { get; set; }
public string Text { get; set; }
}
Teraz właściwość Address nie będzie widoczna w cshtml. Oczywiście tego atrybutu używamy na właściwościach, a nie na klasie.
HtmlAttributeName
Ten atrybut z kolei umożliwia zmianę nazwy właściwości tag helpera w cshtml. Nadpisuje nazwę atrybutu:
public class MailTagHelper: TagHelper
{
[HtmlAttributeName("Tralala")]
public string Address { get; set; }
public string Subject { get; set; }
public string Text { get; set; }
}
Teraz w pliku cshtml możesz napisać tak:
<email tralala="a@b.c" />
Ale ten atrybut ma jeszcze jedno przeciążenie i potrafi ostro zagrać. Może zwali Cię to z nóg. Dzięki niemu możesz w pliku cshtml napisać co Ci się podoba, a Twój tag helper to ogarnie. Możesz popisać atrybuty, które nie są właściwościami Twojego tag helpera! Spójrz na ten przykład:
public class MailTagHelper: TagHelper
{
[HtmlAttributeName(DictionaryAttributePrefix = "mb_")]
public Dictionary<string, string> Prompts { get; set; } = new Dictionary<string, string>();
public string Address { get; set; }
public string Subject { get; set; }
public string Text { get; set; }
}
Co tu zaszło? Spójrz, co podałeś w parametrze atrybutu HtmlAttributeName. Jest to jakiś prefix. A teraz zauważ, że w kodzie html użyłeś tego prefixu do określenia nowych atrybutów.
Po takiej zabawie, słownik Prompts będzie wyglądał tak:
adr: „a@b.c”
subject: „Temat”
Zauważ, że prefix został w słowniku automatycznie obcięty i trafiły do niego już konkretne atrybuty.
Oczywiście to przeciążenie może być użyte tylko na właściwości, która implementuje IDictionary. Kluczem musi być string, natomiast wartością może być string, int itd.
To tyle, jeśli chodzi o podstawy tworzenia tag helperów. O bardziej zaawansowanych możliwościach mówię w drugiej części artykułu. Najpierw upewnij się, że dobrze zrozumiałeś wszystko co tu zawarte.
Jeśli masz jakiś problem albo znalazłeś w artykule błąd, podziel się w komentarzu.
Gdy tworzysz i uruchamiasz webową aplikację (czy to WebApp, czy WebApi) w .NetCore, to domyślnie startuje ona pod adresem: http://localhost:port. Zazwyczaj to wystarcza do tworzenia, debugowania i późniejszej publikacji aplikacji. I super.
Jednak czasem potrzebujesz uruchomić aplikację pod konkretną domeną – np. gdy w grę wchodzą dodatkowe API – przykładem może być api facebooka lub różnego rodzaju captche (reCaptcha, hCaptcha itd.). Te API potrzebują być wywołane spod konkretnego adresu lub wywołać coś na konkretnym adresie. I wtedy localhost może być problemem.
Niektóre API w ogóle nie chcą współpracować z localhost. I co wtedy? No, zawsze można opublikować aplikację na serwerze i próbować ją debugować. Więcej z tym problemów, niż co warte – nie tędy droga. Na szczęście istnieje sposób, żeby twój localhost stał się prawilną domeną. I o tym będzie w tym poście.
UWAGA!
Pamiętaj, że jeśli to robisz, nie twórz domen istniejących w sieci. Jeśli masz zarejestrowaną domenę, np. xmoney-app.pl, to wtedy Twoim lokalnym odpowiednikiem powinna być subdomena istniejąca jedynie lokalnie, np: local.xmoney-app.pl, czy też dev.xmoney-app.pl albo moja-super-subdomena-lokalna.xmoney-app.pl
Rozwiązanie
Posługując się taką subdomeną, zazwyczaj pozwalasz zewnętrznym API na uznanie jej jako subdomeny Twojej prawdziwej domeny i nie ma tu problemów.
Jak to osiągnąć (mowa o Windowsie):
Znajdź swój plik hosts. Domyślnie znajduje się w lokalizacji: C:\Windows\System32\drivers\etc. Plik hosts to takie coś w rodzaju „lokalnego DNSa”.
Wyedytuj go (w trybie administratora) i wskaż swojej pętli zwrotnej swoją subdomenę. Na ludzki, dodaj do 127.0.0.1 – swoją subdomenę, np:
127.0.0.1 local.xmoney-app.pl
tutaj oczywiście zamiast xmoney, umieszczasz swoją subdomenę 🙂 Pamiętaj, żeby pozbyć się hasha '#’ jeśli znajduje się na początku (hash oznacza komentarz). A między adresem, a domeną koniecznie daj tabulator.
Teraz musisz swoją subdomenę powiązać ze swoją aplikacją. Wejdź do katalogu swojej solucji. Jeśli nie widzisz tam katalogu .vs, to pokaż ukryte pliki i foldery. Generalnie poszukaj pliku applicationhost.config, w VisualStudio powyżej 15 wersji powinien znajdować się w katalogu: [TwojaSolucja].vs[Nazwa]\config\
Skopiuj ten plik gdzieś obok i wyedytuj oryginał. Ten plik to są ustawienia IISExpress.
Odszukaj w nim sekcję "sites", a następnie konkretny projekt, którego domenę chcesz zmienić. Jeśli masz tylko jeden webowy projekt w solucji, to w sekcji sites powinieneś mieć tylko jeden wpis.
Odnajdź informację o bindowaniu. Generalnie szukasz tego:
oczywiście wpisując tutaj swoją subdomenę, którą umieściłeś w pliku hosts.
UWAGA! Wpis powinieneś dodać, a nie podmienić ten oryginalny
Zaktualizuj projekt w Visual Studio – kliknij na swój projekt webowy prawym klawiszem myszy i wybierz „Properties”.
Na karcie „Debug” w polu App URL wpisz adres swojej domeny, np: http://local.xmoney-app.pl:44313 – czyli zasadniczo to, co przed chwilą zrobiłeś w pliku applicationhost.config. Tylko teraz – dla projektu.
I to już.
UWAGA! Jest jeden haczyk. Żeby to zadziałało i strona się normalnie uruchomiła, musisz otworzyć VisualStudio w trybie Administratora. Niestety w tym momencie (początek roku 2021) uruchomienie IISExpress w innej domenie niż localhost, wymaga uprawnień administratora.
Możliwe problemy:
W rzeczywistości nie zawsze jest miło i przyjemnie. Czasem nawet wódka szkodzi. Tak i tutaj może dojść do kilku problemów.
Uruchamiasz aplikację, a tam komunikat "Website cannot be found", "Nie można odnaleźć strony" lub tego typu cuda. To może mieć związek z serwerem proxy. Musisz zatem pominąć ten adres (całą swoją subdomenę) w ustawieniach serwera proxy. W różnych przeglądarkach robi się to inaczej. Ja, używając Windowsa 10, robię to dla całego systemu, czyli:
otwórz „pasek start”
wpisz: „Zmień ustawienia serwera proxy”
na dole powinieneś zobaczyć okienko, w którym możesz dołożyć wyjątki do proxy – czyli adresy zaczynające się od tych wpisów, będą pomijane przez proxy. Tylko UWAGA! Musisz dodać całą swoją subdomenę, czyli w moim przypadku: local.xmoney-app.pl
Fragment okna konfiguracji serwera proxy w Windows 10
Problem z SSL. Teoretycznie wystarczy, że w ustawieniach projektu na karcie Debug zaznaczysz opcję „Enable SSL”. Automatycznie powinien się zrobić wpis w pliku applicationhost.config. Uruchamiając teraz aplikacje, dostaniesz komunikat, że jest niebezpieczna i opcję jej uruchomienia. I tyle. Jeśli debugujesz na Chromie, to będzie on Ci pokazywał brzydkie rzeczy na pasku. Ale nie przejmuj się tym, póki debugujesz.
Jeśli debugujesz na Firefoxie, nie zobaczysz takich strasznych rzeczy, bo FF pokazuje tylko małą uwagę, że certyfikat jest developerski na localhost.
Jeśli znalazłeś w tekście jakiś błąd lub masz inny problem, podziel się w komentarzu
Z tego tekstu dowiesz się jak i po co stosować testy jednostkowe. Tu są same podstawy. Bardziej zaawansowany tekst jest dostępny tutaj.
Co to takiego?
Testowanie jednostkowe (ang. unit testing) polega na automatycznym testowaniu Twojego kodu. Automatyczne – czyli robi to za Ciebie framework. Jest kilka popularnych frameworków testujących dla .NETu. Wszystkie one współpracują z VisualStudio.
Na czym to polega?
Polega to na sprawdzeniu Twojej metody mniej więcej na takie sposoby:
czy kod się nie wywala lub wywala się wtedy, gdy powinien
czy kod zachowa się dobrze z różnymi parametrami (parametry brzegowe i „zwyczajne”)
czy kod zwraca poprawne dane
jak kod zachowa się w specyficznych warunkach
Test jednostkowy polega z grubsza na napisaniu specjalnego kodu, który testuje inny kod. O tym za chwilę.
Testowanie jednostkowe pomaga
Testy jednostkowe pomagają… bardzo pomagają uniknąć błędów w kodzie produkcyjnym. Generalnie bardzo potrafią ograniczyć występowanie błędów. Testy jednostkowe są najbardziej użyteczne w momencie, gdy refaktorujemy kod lub robimy zmiany w już działającym.
Zapewne nie raz przekonałeś się, że poprawki zrobione w jednej części kodu tworzą błędy zupełnie gdzieś indziej, prawda? Jest to swego rodzaju zmora. Ale cóż… jako ludzie nie jesteśmy w stanie wszystkiego przewidzieć. I tutaj bardzo pomocne okazują się być testy jednostkowe.
Testy dokumentują kod
Testy jednostkowe „dokumentują” też w pewien sposób kod. To nie znaczy, że nie powinno prowadzić się prawdziwej dokumentacji 😉 W jaki sposób test dokumentuje? Po prostu widzisz fragment poprawnie napisanego kodu – np. użycie jakiejś klasy – w odpowiedni sposób – w taki jaki została zaprojektowana. Dzięki temu wiesz, jak taką konkretną klasę utworzyć i jak jej używać. Przeglądając zatem kody testów możesz się sporo dowiedzieć o całym systemie.
Jak testować?
Pokażę Ci to na przykładzie frameworka nUnit. Dlaczego ten? Po prostu z własnego przyzwyczajenia. Są inne, MSTest, xUnit (te dwa są na dzień dobry w VisualStudio) i sporo innych. Generalnie zasada jest ta sama. Różnice są jeśli chodzi o kod testujący, inicjujący itp.
Przygotowanie
Zainstaluj w Visual Studio następujące rozszerzenia: NUnit 3 Test Adapter i NUnit VS Templates. NUnit TestAdapter to jest rozszerzenie, które współpracuje z TestExplorerem w Visual Studio (takie specjalne okno z widocznymi testami i ich wynikami). NUnit VS Templates natomiast to szablony, dzięki którym możesz tworzyć od razu nowe projekty testujące.
Potrzebujemy teraz jakiegoś programu do testowania. Więc zróbmy coś małego i prostego. Utwórz sobie projekt Class Library (.NET Standard) – dlatego taki, ponieważ chcę uniknąć na tym poziomie aplikacji okienkowych lub konsolowych. Skupimy się tylko na testach i samej logice.
Stwórz prostą klasę Calculator:
public class Calculator
{
public int Add(int x, int y)
{
return x + y;
}
public int Mul(int x, int y)
{
return x * y;
}
}
Sytuacja wygląda tak – mamy publiczną klasę i dwie publiczne metody. To są idealni kandydaci, żeby zastosować na nich testy jednostkowe.
Żeby mieć porządek w solucji, utwórz teraz w niej katalog Tests. W tym katalogu utwórz nowy projekt: nUnit Test Project (.NET Core) -> nie znajdziesz go, jeśli nie zainstalowałeś NUnit VS Templates.
Interesuje nas ten konkretnie projekt, ponieważ będziemy testować bibliotekę .NETStandard. Gdybyśmy testowali aplikację na Androida, wybralibyśmy NUnit 3 Test Project (Android). Analogicznie z innymi szablonami, które widzisz w oknie dodawania nowego projektu.
Możesz nazwać swój projekt, np: NUnit.Nazwa-Projektu-Ktory-Testujesz. Oczywiście możesz nazwać go dowolnie, ale takie nazewnictwo wydaje się być rozsądne, bo:
pokazuje Ci jakim frameworkiem testujesz
pokazuje Ci jaki projekt testujesz
Osobiście bardziej używam standardowego nazewnictwa w stylu: NazwaFirmy.NazwaAplikacji.ProjektTests
(gdzie Projekt, to nazwa projektu, który testuję)
Pierwszy test
OK, zróbmy zatem pierwsze testy jednostkowe. Ja zazwyczaj robię tak (i Tobie też polecam), że jedną klasę testuję w jednym pliku. Zatem w projekcie testowym utwórz sobie plik C# o nazwie CalculatorTests.cs
Od razu do pliku dodaj dyrektywę using NUnit.Framework I teraz tak – całe testowanie w nUnit opiera się głównie na atrybutach. Żeby Twoja klasa była uznana za klasę z testami, musisz ją oznaczyć atrybutem TestFixture:
[TestFixture]
public class CalculatorTests
{
}
Teraz będziemy w niej dodawać metody testujące. Każdą swoją metodę powinieneś przetestować na różne sposoby (jeśli to jest sensowne). A więc do każdej metody napiszesz pewnie kilka metod testujących. I tutaj dwie uwagi.
Metoda testująca powinna testować tylko jedną, konkretną rzecz – tak samo jak metoda w klasie powinna robić tylko jedną, konkretną rzecz
W programowaniu obowiązuje żelazna reguła DRY (Don’t Repeat Yourself). Czyli nie piszemy analogicznego kodu kilka razy. Jeśli chodzi o testowanie, to zasada DRY nie jest już taka żelazna. Pamiętaj, że im więcej logiki masz w testach, tym więcej błędów możesz w tych testach popełnić (w samym kodzie testującym). Zatem testy staramy się pisać jak najprościej, nawet jeśli to wymaga duplikowania kodu. Rzecz jasna, możesz tworzyć jakieś metody pomocnicze i nikt Ci tego nie zabroni. Sam też tak robię. Ale podejdź do tego ostrożnie i zdroworozsądkowo.
Metoda testująca
OK, napiszmy więc metodę, która sprawdzi, czy Add w ogóle działa i się nie wywala. Jeśli chodzi o nazewnictwo metod testujących, jest tutaj też pewna reguła. Być może nie jest to żelazna zasada, ale na pewno sensowna i wspomagająca. Więc staraj się jej trzymać.
Nazwa metody testującej powinna składać się z 3 części:
nazwa metody, którą testujemy
warunki, które testujemy
spodziewany efekt
Tak jak mówiłem, to nie jest jakaś żelazna zasada, ale pomaga. Więc staraj się stosować to lub podobne wzory. Zatem napiszmy teraz metodę testującą:
public void Add_ValidArguments_DoesNotThrow()
{
}
Co to oznacza:
testujemy metodę Add
testujemy ją w normalnych warunkach – a więc przy użyciu poprawnych argumentów (ValidArguments)
spodziewamy się, że metoda nie rzuci żadnym wyjątkiem (DoesNotThrow). Możemy też spodziewać się, że metoda zwróci poprawny wynik, czyli trzeci człon moglibyśmy nazwać np. „ReturnsValidValue„. Napiszmy teraz ciało tej metody:
public void Add_ValidArguments_DoesNotThrow()
{
Calculator calc = new Calculator();
int result = calc.Add(2, 2);
}
W tym momencie tworzymy obiekt Calculator i wywołujemy metodę Add. Czyli dokładnie to, co zrobimy gdzieś w programie, wykorzystując obiekt Calculator.
Teraz upewnijmy się, że otrzymany wynik jest taki, jakiego się spodziewamy:
public void Add_ValidArguments_DoesNotThrow()
{
Calculator calc = new Calculator();
int result = calc.Add(2, 2);
Assert.AreEqual(4, result);
}
Sprawdzenie poprawności testu
Jak widzisz jest tutaj metoda AreEqual z klasy Assert. Klasa Assert pochodzi z frameworka nUnit. Inne frameworki testujące też mogą mieć klasę o nazwie Assert z innymi metodami albo zupełnie inne sposoby na sprawdzenie wyniku. Jak widzisz, klasa Assert ma sporo możliwości różnych sprawdzeń. Większość z nich jest „samoopisująca się”, ale opis ich wszystkich znajdziesz w dokumentacji nUnit.
Metoda AreEqual sprawdza, czy zmienna result ma spodziewaną wartość. W tym przypadku 4. Jeśli nie miałaby takiej wartości, test zakończyłby się niepowodzeniem.
To teraz zróbmy drugie sprawdzenie, upewnijmy się, że metoda Add nie rzuca wyjątku:
public void Add_ValidArguments_DoesNotThrow()
{
Calculator calc = new Calculator();
int result = 0;
Assert.DoesNotThrow(() => result = calc.Add(2, 2));
Assert.AreEqual(4, result);
}
Teraz, jeśli metoda Add rzuci wyjątek lub rezultat będzie inny niż spodziewany, test zakończy się niepowodzeniem.
Jest jeszcze jedna rzecz, którą trzeba zrobić, żeby test rozpocząć. Jak pisałem wcześniej – możesz mieć w klasie testującej różne metody pomocnicze. A więc nie każda metoda musi być metodą testującą. Aby oznaczyć metodę jako testującą posłuż się atrybutem Test:
[Test]
public void Add_ValidArguments_DoesNotThrow()
{
Calculator calc = new Calculator();
int result = 0;
Assert.DoesNotThrow(() => result = calc.Add(2, 2));
Assert.AreEqual(4, result);
}
Uruchomienie testu
Super! Teraz otwórz sobie okienko TestExplorer (menu View -> Test Explorer). To jest okienko, w którym zobaczysz wyniki testów. To okno jest z VisualStudio, a rozszerzenie nUnit 3 Test Adapter, które zainstalowałeś wcześniej, pozwala na połączenie nUnit z mechanizmem testowania dostępnym w Visual Studio.
Ok, teraz zbuduj swoją solucję. Jeśli się nie buduje, prawdopodobnie otrzymałeś błąd, że .NetFramework 4.6 nie jest kompatybilny z .NetStandard. Po prostu kliknij prawym klawiszem myszy na projekt testujący i wybierz Properties. Tam przejdź na zakładkę Application i z comboboxa TargetFramework ustaw właściwość .Net Framework 4.7.2. Teraz powinieneś już móc zbudować wszystko.
Okienko TestExplorer
Zobacz, jak wygląda okienko Test Explorer. Powinieneś tu zobaczyć wszystkie testy, jakie masz w solucji. Możesz uruchomić poszczególne testy, możesz uruchomić całą grupę. Jeśli klikniesz prawym klawiszem myszy na jakiś element (grupę testów albo poszczególny test) zobaczysz elementy Run i Debug. To Cię w tym momencie interesuje najbardziej. Polecenie RUN po prostu uruchomi testy. Polecenie DEBUG uruchomi testy w trybie debugowania. Tzn. że dopiero przy DEBUG będą istotne breakpointy, które postawisz w kodzie testującym.
Uruchom teraz test i zobacz wynik. Test możesz uruchomić na trzy sposoby:
Klikając zielony PLAY na górze okienka TestExplorer -> w ten sposób możesz uruchomić od razu WSZYSTKIE testy
Klikając prawym klawiszem myszy na konkretnym teście (w okienku TestExplorer) i wybierając opcję RUN.
Klikając prawym klawiszem myszy na konkretnym teście (w okienku TestExplorer) i wybierając opcję DEBUG – w ten sposób będziesz mógł debugować swoje testy. Tylko przy tej opcji breakpointy w kodzie testowym będą aktywne.
Super! Wszystko się udało. To teraz w ramach ćwiczeń zrób coś, żeby test nie powiódł się i zobacz, jak to wygląda.
Testy jednostkowe dla wielu przypadków
Ok, wróćmy do poprawnych kodów i zróbmy jakiś lepszy test. W tym momencie testujemy tylko jeden przypadek – 2 + 2. Lepszy test przetestuje kilka przypadków. nUnit tutaj ułatwia sprawę. Wystarczy, że atrybut Test zamienisz na TestCase i zmienisz lekko metodę:
[TestCase(2, 2, 4)]
[TestCase(3, 3, 6)]
[TestCase(0, 2, 2)]
[TestCase(100, 150, 250)]
public void Add_ValidArguments_DoesNotThrow(int x, int y, int expected)
{
Calculator calc = new Calculator();
int result = 0;
Assert.DoesNotThrow(() => result = calc.Add(x, y));
Assert.AreEqual(expected, result);
}
Atrybut TestCase po prostu przyjmuje pewne zmienne. Zmienne te są później przekazywane do metody, dlatego musisz mieć tutaj tyle parametrów, ile masz w atrybucie. Myślę, że całość sama się tłumaczy. W taki sposób pisząc tylko jeden test, stworzyłeś 4 różne przypadki testowe (co zobaczysz w okienku Test Explorer).
Wypadałoby też dodać testy, które sprawdzą jak metoda zachowuje się z ujemnymi argumentami i co ważniejsze – z argumentami BRZEGOWYMI: int.MinValue, int.MaxValue. Tutaj wszystko zależy od konkretnego programu. No bo co się stanie, jeśli do maksymalnej wartości dodasz 1? Licznik się przekręci i otrzymasz minimalną wartość… Lub program się wywali… W zależności, co chcesz osiągnąć. To tylko przykład.
Co testować?
Mógłbyś teraz się pokusić o to, żeby testować każdą swoją metodę. Oczywiście nie ma to sensu w praktyce. Jeśli 100% Twojego kodu jest obłożonych testami, to coś jest nie tak. Co powinieneś testować? Kody, w których jest jakaś logika. Ale tylko swoje! To jest ważne, bo sporo osób łapie się na tym, że testują kody innych frameworków. To jest błąd. Z założenia, frameworki są już przetestowane. To oznacza, że jeśli klasę Calculator miałbyś z innego frameworka, to wtedy nie powinieneś jej testować. Jeśli masz taki przypadek:
public IList<string> GetWords(string str)
{
return str.Split(' ');
}
To nie testuj takiej metody. Inaczej wyszłoby na to, że testujesz metodę Split z klasy string. To nie ma sensu. Ale już coś takiego:
public IList<string> GetWords(string str)
{
var arr = return str.Split(' ');
return arr.ToList();
}
MOŻE mieć sens (ale nie musi). Masz tutaj trochę dodatkowej logiki. A co, jeśli przekażesz pustego stringa? Zmienna arr będzie nullem. Więc metoda się wywali. I w tym przypadku testujesz już swój kod, a nie kod frameworka. Zwracaj na to uwagę.
To tyle jeśli chodzi o podstawy testów jednostkowych. Więcej w drugiej części artykułu, do której szalenie Cię zachęcam 🙂
Jeśli masz pytania lub znalazłeś błąd w tekście, napisz w komentarzu.
Hej, w tym artykule pokażę Ci, czym jest szablon potomny w WordPress (child theme), jak i po co go stosować. Rozsiądź się wygodnie i zaczynamy.
Po co szablon potomny?
Prędzej, czy później dojdzie do tego, że będziesz chciał zrobić jakąś zmianę w szablonie – czy to w pliku php, czy css, a może jeszcze gdzieś indziej. To może być mała, ale kluczowa zmiana. Jeśli nie zastosowałbyś szablonu potomnego, to mogłoby się okazać, że po aktualizacji szablonu, który używasz, straciłeś swoje zmiany. No i klops, no i cześć.
Dlatego istotne jest, aby stosować szablony potomne. Dzięki nim, raczej nigdy nie powinieneś stracić swoich zmian (pomijając jakieś duże lub źle zrobione aktualizacje).
Czym jest szablon potomny?
W WordPress są dwa rodzaje szablonów – szablon rodzic (parent theme) i szablon potomny (child theme). Instalując szablon na swoim WordPressie, instalujesz szablon typu rodzic – czyli główny szablon. On może być całkowicie zmieniony podczas aktualizacji.
Szablon potomny to szablon, który tworzysz niejako na podstawie szablonu rodzica. I to właśnie szablonów potomnych powinieneś używać. NIGDY nie używaj szablonu, który instalujesz, za każdym razem utwórz szablon potomny i ten właśnie używaj.
Jak stworzyć szablon potomny?
Na szczęście jest to niesamowicie proste.
Zainstaluj szablon, który chcesz używać, ale nie wybieraj go w ustawieniach WordPress. U mnie to Divi, który szczególnie polecam.
Teraz musisz uruchomić jakąś przeglądarkę plików. Może to być Windows Explorer (jeśli Twoja strona istnieje tylko lokalnie), Total Commander lub jakakolwiek inna przeglądarka (klient FTP)
W katalogu głównym bloga masz katalog wp-content. Przejdź do niego
W nim masz katalog themes. Przejdź tam.
W katalogu themes masz katalogi zainstalowanych szablonów. Utwórz tutaj nowy katalog z nazwą istniejącego szablonu i dopiskiem -child. Tzn. jeśli np. chcesz używać szablonu TwentyTwenty, utwórz katalog TwentyTwenty-child (oczywiście katalog TwentyTwenty musi tutaj istnieć – ponieważ zainstalowałeś ten szablon)
W nowo utworzonym katalogu (z sufiksem -child) utwórz plik style.css. To jest główna część Twojego szablonu potomnego. Do tego pliku dodaj taką zawartość:
/*
Theme Name: Nazwa szablonu (np. TwentyTwenty Child)
Theme URI: strona szablonu (np. http://twenty-twenty.com)
Description: Opis Twojego szablonu (np: Szablon potomny TwentyTwenty)
Author: Ja
Author URI: strona autora
Template: TwentyTwenty (wpisz tutaj nazwę szablonu rodzica)
Version: 1.0.0
*/
Mimo, że powyższy kod jest w komentarzu, to jednak jest on istotny. Krótkie wyjaśnienie:
Theme Name – nazwa szablonu, który będzie wyświetlony w menu WordPressa. Nazwa musi być unikalna. Ta wartość jest wymagana
Theme URI – strona szablonu, możesz pominąć
Description – opis szablonu, możesz pominąć
Author – autor szablonu – możesz pominąć
Author URI – strona autora szablonu – możesz pominąć
Template – szablon rodzic dla tego szablonu. Ta wartość jest wymagana
Version – wersja, możesz pominąć.
Jest jeszcze kilka samoopisujących się elementów, które możesz tutaj zawrzeć, ale raczej nie będziesz ich stosował (License – nazwa licencji (np. GNU); License URI – strona z licencją; Tags – tagi szablonu; Text Domain – szczerze, nie mam pojęcia co to. Jeśli wiesz, podziel się w komentarzu)
Kolejkowanie styli szablonów
To właściwie tyle. Utworzyłeś szablon potomny. W pliku style.css możesz dalej robić modyfikacje swojego szablonu. Jest jednak jeszcze jedna rzecz, którą powinieneś zrobić. Powinieneś skolejkować style.css szablonu rodzica i potomnego. Kiedyś robiło się to inaczej (dyrektywa @import w pliku style.css). Ale dzisiaj zalecany jest sposób z wykorzystaniem php.
W świecie idealnym szablon rodzica powinien zaczytać zarówno swoje style, jak i style szablonu potomnego. Jednak nie zawsze to się dzieje. Dlatego powinieneś zrobić to ręcznie:
Utwórz w katalogu szablonu potomnego jeszcze jeden plik: functions.php
Ten kod po prostu rejestruje i kolejkuje arkusze styli. Teraz jest ważne kilka rzeczy:
szablon potomny jest wczytywany przed szablonem rodzicem
jeśli nie podasz numeru wersji w pliku style.css, odwiedzający zobaczą to, co zostało zapisane w cache, a nie aktualną wersję. Dlatego po każdej zmianie zmień też numer wersji
funkcje get_stylesheet* szukają najpierw elementów w szablonach potomnych
Teraz już możesz aktywować ten styl potomny w opcjach wyglądu WordPressa.
Jeśli znalazłeś błąd w artykule, masz jakieś pytania lub chcesz coś dodać, podziel się w komentarzu