Test jednostkowy (ang. unit test) wg Wikipedii to „metoda testowania tworzonego oprogramowania poprzez wykonywanie testów weryfikujących poprawność działania pojedynczych elementów (jednostek) programu – np. metod lub obiektów w programowaniu obiektowym lub procedur w programowaniu proceduralnym. Testowany fragment programu poddawany jest testowi, który wykonuje go i porównuje wynik (np. zwrócone wartości, stan obiektu, zgłoszone wyjątki) z oczekiwanymi wynikami – tak pozytywnymi, jak i negatywnymi (niepowodzenie działania kodu w określonych sytuacjach również może podlegać testowaniu).”
Jedną z zalet testów jednostkowych jest możliwość wykonywania w sposób cykliczny w pełni zautomatyzowanych testów na modyfikowanych elementach programu, co umożliwia często wychwycenie błędu natychmiast po jego pojawieniu się i szybką jego lokalizację, zanim dojdzie do wprowadzenia błędnego fragmentu do programu. Takie testy mogą być wielokrotnie uruchamiane przez programistę w celu sprawdzenia zachowania określonej jednostki kodu dla różnych zestawów danych wejściowych.Testy jednostkowe są również formą specyfikacji.
Testy jednostkowe zasadniczo powinny sprawdzać zachowanie konkretnej jednostki kodu (np. metody) i zazwyczaj są pisane przez programistę, który implementuje daną funkcjonalność. Programista powinien przetestować zachowanie takiego fragmentu kodu zanim stanie się on częścią całego systemu i przetestować jego zachowanie w różnych warunkach. Zwykle dana metoda jest testowana w izolacji od innych testowanych metod.
W .NET istnieją dwie opcje pisania testów jednostkowych:
1. Testy jednostkowe MS, wbudowane w Visual Studio.
2. Wykorzystanie biblioteki NUnit.
Dzisiejszy wpis będzie poświęcony uruchamianiu testów jednostkowych z wykorzystaniem języka C # i NUnit.
Załóżmy, że chcemy napisać test jednostkowy dla metod z działaniami matematycznymi. Aby to zrobić tworzymy nowy projekt o nazwie DoMath, korzystając z szablonu Console Application (.NET Framework).
Do projektu dodajemy interfejs o nazwie IDoMath.
Interfejs jest zdefiniowany tak, jak pokazano poniżej:
Następnie do projektu dodajemy nową klasę o nazwie Math.
Klasa ta implementuje interfejs IDoMath. W metodzie Multiply błąd merytoryczny związany z obliczeniem został popełniony celowo, aby później pokazać, jak testy działają w praktyce.
Aby rozpocząć pracę z biblioteką NUnit i napisać test, do rozwiązania dodajemy nowy projekt testowy. Stosując się do konwencji nazewniczych nazywamy projekt tekstowy tak samo jak projekt, który chcemy testować i do tej nazwy po kropce dodajemy słowo Tests. Stąd projekt testowy nazywamy DoMath.Tests.
Po utworzeniu projektu testowego mamy dwie możliwości dodania odwołania do NUnit w projekcie:
1. Skorzystanie z opcji Rozszerzenia i Aktualizacje. Aby skorzystać z tego sposobu, z menu Tools wybieramy Extensions and Updates …, a potem wybieramy NUnit z sekcji Online.
2. Skorzystanie z pakietu NuGet.
Klikamy prawym na projekcie testowym DoMath.Tests, z menu podręcznego wybieramy Manage Nuget Packages …, potem w sekcji Browse szukamy NUnit i instalujemy.
Po dodaniu NUnit do projektu klikamy prawym przyciskiem myszy na References, potem Add Reference … i dodajemy referencję do projektu DoMath.
Po dodaniu odpowiednich referencji tworzymy klasę testową i metody testowe. Nazwa klasy testowej to zazwyczaj nazwa klasy, którą chcemy testować z przyrostkiem Test. Postępując zgodnie z tymi wytycznymi nazwa Naszej klasy testowej będzie brzmiała DoMathTest. Co do metody testowej to jej nazwa powinna wskazywać na metodę, która będzie testowana, a jej nazwa powinna zostać poprzedzona słowem Should. Oprócz odpowiednich nazw dla klas i metod, klasę testową poprzedzamy atrybutem TestFixture, a metodę atrybutem Test.
Zmieniamy nazwę pliku i klasy z Class1.cs na DoMathTest.cs (klikamy prawym na plik Class1.cs i wybieramy opcję Rename …, przy pytaniu, czy zmienić też wszystkie referencje do tej klasy, wybieramy opcję Yes). Po tych zmianach modyfikujemy kod klasy jak poniżej:
W powyższym kodzie tworzymy obiekt klasy Math o nazwie idm. Potem wywołujemy metodę z tej klasy. Na końcu przy pomocy metody Assert porównujemy wynik zwracany przez tą metodę z oczekiwanym przez Nas wynikiem. W drugiej metodzie Multiply podajemy prawidłowy wynik mnożenia dla podanych przez Nas liczb (mimo, iż w kodzie metody popełniliśmy błąd w działaniu).
Po skompilowaniu projektu testowego uruchamiamy test. Możemy to zrobić albo za pomocą Eksploratora testów w Visual Studio, albo za pomocą NUnit GUI.
Visual Studio Test Explorer
Z menu Test wybieramy Windows, a następnie Test Explorer.
Powinniśmy zobaczyć testy jednostkowe w Eksploratorze testów. Jeśli testy nie są widoczne (tak jak poniżej), to oznacza, że do projektu testowego musimy dodać pakiet NuGet NUnit3TestAdapter.
Po zainstalowaniu tego pakietu i ponownym skompilowaniu projektu testy powinny być już widoczne.
Możemy wybrać i uruchomić konkretny test lub wybrać uruchomienie wszystkich testów. Po uruchomieniu testów z projektu DoMath.Tests, w Eksploratorze testów możemy zobaczyć wynik wskazujący, iż test ShouldAdd2Numbers przeszedł, a test ShouldMultiply2Numbers zakończył się porażką (co jest zgodne z oczekiwaniami). Możemy też zobaczyć informację ze wskazówką, dlaczego drugi test nie powiódł się.
NUnit GUI
Narzędzie to możemy pobrać z Githuba. Obecnie jest to projekt w wersji Preview. Pobieramy plik z rozszerzeniem .zip i po rozpakowaniu instalujemy NUnit GUI, uruchamiając plik nunit-gui.exe. Po zainstalowaniu i uruchomieniu programu z menu File wybieramy opcję Open, a następnie dodajemy bibliotekę DLL z projektu DoMath.Tests.
Po dodaniu biblioteki DLL widzimy testy w interfejsie użytkownika. Aby uruchomić test, wybieramy go i klikamy Run.
Jeśli uruchomienie wybranego przez Nas testu zakończy się porażką, to w interfejsie NUnit GUI możemy wyraźnie zobaczyć komunikat o porażce i jej przyczynach.
Atrybuty SetUp i TearDown
Dwie bardzo ważne koncepcje testów NUnit to SetUp i TearDown. W klasie DoMathTest tworzymy obiekty klasy Math w obu metodach testowych. Może to czasami powodować problemy, zwłaszcza wtedy gdy potrzebne modyfikacje wykonamy tylko w jednej metodzie, zapominając o dokonaniu zmian w kolejnych. Powinniśmy więc tworzyć wszystkie obiekty wymagane do testu przed wykonaniem jakiegokolwiek testu.
Przykładowe scenariusze testów to:
1. Utworzenie połączenia z bazą danych przed wykonaniem pierwszego testu.
2. Utworzenie wystąpienia konkretnego obiektu przed wykonaniem pierwszego testu.
3. Usunięcie całego połączenia z bazą danych po wykonaniu wszystkich testów.
4. Dodanie/ usunięcie określonego pliku z systemu przed wykonaniem dowolnego testu.
Możemy rozwiązać powyższe scenariusze za pomocą atrybutów SetUp i TearDown. SetUp to kod, który zostanie wykonany przed wykonaniem pierwszego testu, a TearDown to kod, który zostanie wykonany po wykonaniu wszystkich testów.
Możemy teraz zmodyfikować Naszą klasę DoWork.Test. W obu metodach testowych tworzymy obiekt klasy DoMath. W rzeczywistości obiekt klasy DoMath musimy utworzyć przed wykonaniem pierwszego testu i musimy go usunąć po wykonaniu wszystkich testów. Aby to zrobić, modyfikujemy klasę testową dodając atrybuty SetUp i TearDown.
W powyższym kodzie:
1. Dodaliśmy metodę poprzedzoną atrybutem [SetUp]. Ta metoda zostanie wykonana przed wykonaniem jakiegokolwiek testu. Wewnątrz tej metody tworzymy obiekt klasy Math tak, aby utworzona instancja była dostępna dla wszystkich testów.
2. Dodatkowo dodaliśmy metodę poprzedzoną atrybutem [TearDown], która zostanie wykonana po wykonaniu wszystkich testów.
Po dodaniu obu tych metod usuwamy obiekt klasy Math ze wszystkich pozostałych metod testowych. Po ponownym skompilowaniu projektu i uruchomieniu testów, możemy zaobserwować, że wszystko działa tak, jak oczekiwaliśmy.
Ignore test
Innym scenariuszem, który możemy napotkać podczas pracy z testami może być sytuacja, gdy chcemy na jakiś czas zawiesić wykonywanie pewnych testów. Na przykład mamy Nasze dwa testy i chcemy, aby test, który nie przechodzi, został zignorowany np. podczas uruchamiania testów za pomocą polecenia Run All. Możemy to zrobić, poprzedzając wybraną metodę testową atrybutem [Ignore].
Test można zignorować, jak pokazano na poniższej liście:
Po ponownym skompilowaniu projektu, przejściu do Eksplorera testów i kliknięciu linka Run All możemy zobaczyć, że jeden z testów przeszedł, a drugi ma status TestSkipped.
Podsumowanie
Post ten pokazuje, jak możemy rozpocząć pracę z testami jednostkowymi używając biblioteki NUnit w .NET. Mam nadzieję, że ten wpis pomoże Ci w rozpoczęciu tworzeniu testów jednostkowych!
Gotowy projekt DoMath jest dostępny do pobrania z GitHuba.