Każdy doświadczony programista zdaje sobie sprawę, że solidne testowanie jest nieodzownym elementem procesu tworzenia oprogramowania. Testy jednostkowe stanowią kluczowy etap w tym procesie, umożliwiając weryfikację poprawności działania pojedynczych modułów w ramach większej aplikacji.
W niniejszym artykule zagłębimy się w temat testów jednostkowych, wykorzystując wbudowany moduł Pythona o nazwie `unittest`. Zanim jednak przejdziemy do praktycznych aspektów, warto przybliżyć sobie różne rodzaje testów, z którymi możemy się spotkać.
Wyróżniamy przede wszystkim testowanie manualne i automatyczne. Testy manualne polegają na ręcznym sprawdzaniu funkcjonalności programu przez testerów, zazwyczaj po zakończeniu fazy developmentu. Z kolei testowanie automatyczne opiera się na wykorzystaniu specjalnych programów, które wykonują testy za nas, dostarczając nam wyniki w sposób zautomatyzowany.
Łatwo zauważyć, że testowanie manualne może być czasochłonne i trudne w realizacji, szczególnie w przypadku rozbudowanych aplikacji. Z tego powodu programiści preferują pisanie kodu, który automatyzuje proces testowania. W ramach testów automatycznych istnieje wiele różnych kategorii, takich jak testy jednostkowe, testy integracyjne, testy end-to-end czy testy brzegowe.
Zastanówmy się nad typowym cyklem testowania:
- Rozpoczynamy od napisania lub modyfikacji kodu.
- Następnie tworzymy lub aktualizujemy testy, które obejmują różne scenariusze dla naszego kodu.
- Uruchamiamy testy, korzystając z narzędzi automatycznych lub manualnie.
- Analizujemy wyniki testów. Jeśli zostaną wykryte błędy, naprawiamy je i powtarzamy cały proces.
W dalszej części artykułu skupimy się na najważniejszym i najbardziej podstawowym rodzaju testowania, jakim są testy jednostkowe. Bez zbędnej zwłoki przejdźmy zatem do praktycznego omówienia tego zagadnienia.
Czym są testy jednostkowe?
Testowanie jednostkowe to proces weryfikacji niewielkich, niezależnych fragmentów kodu, często ograniczających się do pojedynczych funkcji. Termin „niezależny” oznacza, że dany fragment kodu nie jest powiązany z innymi częściami projektu i może być testowany w izolacji.
Rozważmy przykład. Chcemy sprawdzić, czy dany ciąg znaków jest równy „newsblog.pl”. W tym celu tworzymy funkcję, która przyjmuje jeden argument (ciąg znaków) i zwraca wartość logiczną (true lub false), informującą, czy łańcuch jest zgodny z oczekiwaniem.
def czy_rowne_newsblog(string): return string == "newsblog.pl"
Ta funkcja nie zależy od żadnych innych fragmentów kodu, dzięki czemu możemy przetestować ją niezależnie, podając różne dane wejściowe. Tego typu niezależne fragmenty kodu mogą być wielokrotnie wykorzystywane w różnych miejscach projektu.
Dlaczego testy jednostkowe są tak ważne?
Niezależne fragmenty kodu, które mogą być wykorzystywane w różnych częściach projektu, powinny być szczególnie starannie napisane i przetestowane. Właśnie w tym celu stosuje się testy jednostkowe. Co się stanie, jeśli zaniedbamy ten aspekt?
Wyobraźmy sobie, że pomijamy testowanie tych małych, kluczowych bloków kodu. W takiej sytuacji, inne testy, takie jak testy integracyjne, czy end-to-end, które korzystają z tychże fragmentów, mogą zakończyć się niepowodzeniem, co w konsekwencji może skutkować awarią całej aplikacji. Dlatego tak ważne jest, by zacząć od solidnego przetestowania podstawowych elementów naszego kodu.
Teraz, gdy rozumiemy znaczenie testów jednostkowych, możemy przejść do kolejnego etapu. W dalszej części tekstu omówimy moduł `unittest` w Pythonie i dowiemy się, jak za jego pomocą pisać i wykonywać testy jednostkowe.
Uwaga: Zakłada się, że czytelnik ma podstawową wiedzę o klasach, modułach i innych koncepcjach programowania obiektowego w Pythonie. Brak tej wiedzy może utrudnić zrozumienie dalszych rozdziałów.
Moduł unittest w Pythonie – co to jest?
Moduł `unittest` jest wbudowaną biblioteką Pythona, która służy do pisania i wykonywania testów kodu. Udostępnia on mechanizm, który pozwala na uruchamianie testów bez większego wysiłku, co czyni go doskonałym narzędziem do rozpoczęcia przygody z testowaniem w Pythonie. Oczywiście, w zależności od potrzeb, można rozważyć alternatywne rozwiązania.
Aby przetestować nasz kod Pythona z użyciem `unittest`, musimy wykonać następujące kroki:
1. Napisz kod, który chcesz przetestować.
2. Zaimportuj moduł `unittest`.
3. Utwórz plik, którego nazwa rozpoczyna się od słowa kluczowego `test`. Przykładowo, `test_liczb_pierwszych.py`. Słowo kluczowe `test` pozwala identyfikować pliki zawierające testy.
4. Stwórz klasę, która dziedziczy po `unittest.TestCase`.
5. Wewnątrz klasy zdefiniuj metody testowe, przy czym nazwa każdej z nich musi zaczynać się od słowa `test`. Każda metoda reprezentuje pojedynczy przypadek testowy.
6. Uruchom testy. Możemy to zrobić na kilka sposobów:
- Wykorzystując polecenie `python -m unittest nazwa_pliku_testowego.py`.
- Uruchamiając plik testowy jak zwykły plik Pythona, za pomocą polecenia `python nazwa_pliku_testowego.py`. W tym przypadku musimy dodać wywołanie metody głównej modułu `unittest` w naszym pliku testowym.
- Używając mechanizmu automatycznego wykrywania testów, za pomocą polecenia `python -m unittest discover`. W tym przypadku moduł `unittest` automatycznie znajdzie i uruchomi wszystkie pliki testowe, których nazwy zaczynają się od `test`.
Podczas testowania często porównujemy wynik działania naszego kodu z oczekiwanym wynikiem. Moduł `unittest` dostarcza wiele przydatnych metod do tego celu. Pełną listę metod porównujących można znaleźć tutaj.
Omówienie teorii mamy już za sobą, czas na praktyczne przykłady.
Uwaga: Jeżeli masz jakiekolwiek wątpliwości dotyczące modułu `unittest`, zajrzyj do dokumentacji. Przejdźmy zatem do konkretów.
Testowanie jednostkowe w Pythonie z wykorzystaniem unittest
Na początek zdefiniujemy kilka funkcji, a następnie skupimy się na pisaniu testów. W tym celu utwórz folder w ulubionym edytorze kodu i stwórz plik o nazwie `utils.py`. Wklej do niego następujący kod:
import math def czy_pierwsza(n): if n < 0: return 'Liczby ujemne nie są dozwolone' if n <= 1: return False if n == 2: return True if n % 2 == 0: return False for i in range(2, int(math.sqrt(n)) + 1): if n % i == 0: return False return True def szescian(a): return a * a * a def powiedz_czesc(imie): return "Witaj, " + imie
W pliku `utils.py` mamy trzy różne funkcje. Teraz naszym zadaniem jest przetestowanie każdej z nich za pomocą różnych przypadków testowych. Zacznijmy od funkcji `czy_pierwsza`.
1. Utwórz plik `test_utils.py` w tym samym folderze, w którym znajduje się `utils.py`.
2. Zaimportuj moduł `unittest` i `utils`.
3. Zdefiniuj klasę o nazwie `TestUtils`, która dziedziczy po klasie `unittest.TestCase`. Nazwa klasy może być dowolna, warto jednak nadać jej opisową nazwę.
4. Wewnątrz klasy zdefiniuj metodę o nazwie `test_czy_pierwsza`, która jako argument przyjmuje `self`.
5. Wewnątrz metody `test_czy_pierwsza` zdefiniuj różne przypadki testowe, przekazując argumenty do funkcji `czy_pierwsza` i porównując zwrócone wyniki z oczekiwanymi wartościami.
6. Przykładowy przypadek testowy: `self.assertFalse(utils.czy_pierwsza(1))`.
7. Oczekujemy, że wywołanie `czy_pierwsza(1)` zwróci wartość `False`.
8. W podobny sposób zdefiniuj pozostałe przypadki testowe.
Spójrzmy na przykładowe testy:
import unittest import utils class TestUtils(unittest.TestCase): def test_czy_pierwsza(self): self.assertFalse(utils.czy_pierwsza(4)) self.assertTrue(utils.czy_pierwsza(2)) self.assertTrue(utils.czy_pierwsza(3)) self.assertFalse(utils.czy_pierwsza(8)) self.assertFalse(utils.czy_pierwsza(10)) self.assertTrue(utils.czy_pierwsza(7)) self.assertEqual(utils.czy_pierwsza(-3), "Liczby ujemne nie są dozwolone") if __name__ == '__main__': unittest.main()
Wywołujemy główną metodę modułu `unittest`, aby uruchomić testy za pomocą polecenia `python nazwa_pliku.py`. Uruchommy teraz nasze testy.
Powinniśmy zobaczyć wynik podobny do poniższego:
$ python test_utils.py . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Teraz spróbuj napisać przypadki testowe również dla pozostałych funkcji. Pomyśl o różnych scenariuszach i zaimplementuj odpowiednie testy. Oto przykład testów dla pozostałych funkcji, dodanych do naszej klasy `TestUtils`:
... class TestUtils(unittest.TestCase): def test_czy_pierwsza(self): ... def test_szescian(self): self.assertEqual(utils.szescian(2), 8) self.assertEqual(utils.szescian(-2), -8) self.assertNotEqual(utils.szescian(2), 4) self.assertNotEqual(utils.szescian(-3), 27) def test_powiedz_czesc(self): self.assertEqual(utils.powiedz_czesc("newsblog.pl"), "Witaj, newsblog.pl") self.assertEqual(utils.powiedz_czesc("Chandan"), "Witaj, Chandan") self.assertNotEqual(utils.powiedz_czesc("Chandan"), "Cześć, Chandan") self.assertNotEqual(utils.powiedz_czesc("Hafeez"), "Cześć, Hafeez") ...
Wykorzystaliśmy tylko niektóre metody porównujące z modułu `unittest`. Pełną listę możesz znaleźć tutaj.
Nauczyliśmy się już, jak pisać testy jednostkowe z wykorzystaniem modułu `unittest`. Teraz przyjrzyjmy się różnym sposobom uruchamiania testów.
Jak uruchamiać testy za pomocą unittest
Jeden sposób na uruchomienie testów już poznaliśmy. Zobaczmy, jakie jeszcze opcje mamy do dyspozycji.
1. Uruchamianie testów z wykorzystaniem nazwy pliku i modułu `unittest`.
W tym podejściu używamy modułu `unittest` oraz nazwy pliku zawierającego nasze testy. Polecenie, które uruchomi nasze testy to: `python -m unittest nazwa_pliku.py`. W naszym przypadku będzie to `python -m unittest test_utils.py`.
2. Uruchamianie testów z wykorzystaniem metody wykrywania.
Moduł `unittest` posiada mechanizm automatycznego wykrywania plików testowych. Aby go wykorzystać, nazwy plików testowych muszą zaczynać się od słowa kluczowego `test`.
Polecenie, które automatycznie znajdzie i uruchomi testy, to `python -m unittest discover`. Moduł przeszuka katalog w poszukiwaniu plików testowych i uruchomi wszystkie znalezione.
Podsumowanie 👩💻
Testy jednostkowe stanowią fundamentalny element w procesie tworzenia oprogramowania. W rzeczywistych projektach stosuje się wiele innych rodzajów testów, dlatego warto systematycznie poszerzać swoją wiedzę w tym zakresie. Mam nadzieję, że ten artykuł pomoże Ci zrozumieć, jak pisać podstawowe testy w Pythonie z użyciem modułu `unittest`. Oczywiście, do dyspozycji mamy wiele innych bibliotek testowych, takich jak `pytest`, `Robot Framework`, `nose`, `nose2` czy `slash`, które można wykorzystać, w zależności od wymagań projektu.
Miłego testowania 😎
Być może zainteresują Cię także Pytania i Odpowiedzi na Wywiad w Pythonie.