Last modified: September 21, 2024

This article is written in: 🇵🇱

Testy jednostkowe

Testy jednostkowe stanowią kluczowy element w procesie wytwarzania oprogramowania, mając na celu weryfikację indywidualnych fragmentów kodu (zazwyczaj funkcji lub metod). Pozwalają programiście mieć pewność, że napisane przez niego komponenty działają zgodnie z oczekiwaniami oraz pomagają w identyfikacji i naprawie błędów na wczesnym etapie.

Czerwone vs zielone testy

Czerwone testy:

Zielone testy:

Korzyści z testów jednostkowych

TDD (Test Driven Development)

Technika "test driven development" (TDD) to podejście do tworzenia oprogramowania, w którym testy są tworzone przed kodem źródłowym. Proces tworzenia oprogramowania w podejściu TDD jest cykliczny i składa się z trzech głównych etapów:

  1. Zanim zostanie napisany jakikolwiek kod, programista tworzy test jednostkowy, który definiuje oczekiwane zachowanie nowej funkcjonalności. Ten krok nazywamy "pisaniem testu". Na tym etapie test nie przechodzi, ponieważ brakuje odpowiedniej implementacji.
  2. Następnie programista pisze minimalny kod potrzebny do przejścia testu. Ten krok nazywamy "pisaniem kodu". Celem nie jest tworzenie idealnego rozwiązania, ale napisanie kodu, który sprawi, że test będzie przechodził.
  3. Kiedy test przechodzi, programista optymalizuje kod, eliminując redundancje i zapewniając, że struktura kodu jest klarowna i zgodna ze standardami. Ten krok nazywamy "refaktoryzacją".

Korzystanie z TDD pomaga w utrzymaniu czystego kodu, minimalizuje ryzyko błędów i zachęca do myślenia o projektowaniu i architekturze systemu od samego początku procesu tworzenia oprogramowania.

Organizacja projektu z testami

Dla wielu projektów, zwłaszcza tych większych, odpowiednia organizacja plików i katalogów jest kluczem do utrzymania przejrzystości i efektywności. Rozdzielenie kodu produkcyjnego od testów nie tylko pomaga w zarządzaniu plikami, ale także ułatwia konfigurację narzędzi CI/CD oraz automatyzację testów.

Przykładowa struktura folderów dla projektu z testami może wyglądać tak:

projekt/
│
├── przykladowy_pakiet/
│   ├── __init__.py
│   ├── modul_a.py
│   └── modul_b.py
│
└── tests/
    ├── __init__.py
    ├── test_modul_a.py
    └── test_modul_b.py

Kluczową ideą jest tu utrzymanie logicznej struktury, która odzwierciedla organizację kodu produkcyjnego. Dzięki temu, w miarę rozrostu projektu, łatwo będzie dodawać, modyfikować i lokalizować testy.

Narzędzia do testów jednostkowych w Pythonie:

W Pythonie istnieją dwie główne biblioteki do pisania i uruchamiania testów jednostkowych:

Cecha unittest pytest
Typ Standardowa biblioteka w Pythonie do testów jednostkowych Zewnętrzna biblioteka, która stała się bardzo popularna w społeczności Pythona
Tworzenie testów Umożliwia tworzenie testów, zestawów testów oraz uruchamianie ich Charakteryzuje się prostotą i bardziej naturalnym stylem pisania testów
Mechanizmy asercji Posiada wbudowane mechanizmy asercji Posiada bogatą funkcjonalność w zakresie parametryzacji testów, używania tzw. "fixtures" oraz wtyczek
Przygotowanie środowiska testów Posiada setup i teardown dla przygotowywania środowiska testów -

Ostateczny wybór pomiędzy unittest a pytest zależy od potrzeb projektu i preferencji zespołu. Niezależnie od wyboru, regularne pisanie i uruchamianie testów jednostkowych jest kluczem do tworzenia niezawodnego oprogramowania.

Przykład testu unittest

unittest to standardowa biblioteka w Pythonie przeznaczona do tworzenia testów jednostkowych. Podąża ona za paradygmatem programowania obiektowego, co oznacza, że testy są organizowane w postaci klas, a mechanizmy takie jak dziedziczenie mogą być wykorzystywane do tworzenia hierarchii testów czy rozszerzania funkcjonalności.

Kluczowe cechy unittest:

Przykład kodu:

import unittest

def int_to_roman(num):
    val = [
        1000, 900, 500, 400,
        100, 90, 50, 40,
        10, 9, 5, 4,
        1
        ]
    syms = [
        "M", "CM", "D", "CD",
        "C", "XC", "L", "XL",
        "X", "IX", "V", "IV",
        "I"
        ]
    roman_num = ''
    i = 0
    while  num > 0:
        for _ in range(num // val[i]):
            roman_num += syms[i]
            num -= val[i]
        i += 1
    return roman_num

class TestRomanNumerals(unittest.TestCase):

    def test_conversion(self):
        self.assertEqual(int_to_roman(1), 'I')
        self.assertEqual(int_to_roman(4), 'IV')
        self.assertEqual(int_to_roman(40), 'XL')
        self.assertEqual(int_to_roman(99), 'XCIX')
        self.assertEqual(int_to_roman(1000), 'M')

W powyższym przykładzie:

Aby uruchomić testy jednostkowe, można użyć następującego polecenia w konsoli:

python -m unittest nazwa_pliku_testowego.py

Pamiętaj, by nazwy plików z testami zaczynały się od słowa "test", ponieważ unittest szuka takich plików podczas skanowania katalogów.

Przykład testu pytest

pytest to popularna i wszechstronna biblioteka do tworzenia testów w Pythonie. W porównaniu z unittest, pytest oferuje bardziej skondensowany i czytelny sposób definiowania testów, eliminując potrzebę tworzenia klas i korzystania z funkcji assert. Dodatkowo, pytest jest znany ze swojego rozbudowanego wyjścia i możliwości diagnozy, które pomagają w identyfikowaniu i rozwiązywaniu problemów w testach.

Kluczowe cechy pytest:

Przykład kodu z użyciem:

def int_to_roman(num):
    val = [
        1000, 900, 500, 400,
        100, 90, 50, 40,
        10, 9, 5, 4,
        1
        ]
    syms = [
        "M", "CM", "D", "CD",
        "C", "XC", "L", "XL",
        "X", "IX", "V", "IV",
        "I"
        ]
    roman_num = ''
    i = 0
    while  num > 0:
        for _ in range(num // val[i]):
            roman_num += syms[i]
            num -= val[i]
        i += 1
    return roman_num

def test_int_to_roman():
    assert int_to_roman(1) == 'I'
    assert int_to_roman(4) == 'IV'
    assert int_to_roman(40) == 'XL'
    assert int_to_roman(99) == 'XCIX'
    assert int_to_roman(1000) == 'M'

W powyższym przykładzie:

Aby uruchomić testy napisane z użyciem pytest, można użyć następującego polecenia w konsoli:

pytest nazwa_pliku_testowego.py

Pamiętaj, by nazwy plików z testami oraz same funkcje testowe zaczynały się od słowa "test", ponieważ pytest szuka takich funkcji/plików podczas skanowania katalogów.

Od znalezienia buga do poprawnie działającego kodu

Odkrycie błędu w twoim kodzie to dopiero początek drogi. Oto kroki, które warto podjąć, aby odnaleźć i skutecznie naprawić problem:

  1. Spróbuj dokładnie odtworzyć sytuację, w której wystąpił błąd. Wiedza o tym, jak i kiedy błąd się pojawia, jest kluczowa, aby zrozumieć jego przyczynę. Na przykład, jeśli twoja aplikacja zamyka się po naciśnięciu pewnego przycisku, zacznij od manualnego przejścia przez wszystkie kroki prowadzące do tego efektu.
  2. Odszukaj w kodzie miejsce, które prawdopodobnie odpowiada za zaistniały problem.
  3. Utwórz test, który symuluje odnaleziony błąd. Jeśli problem pojawia się po wywołaniu funkcji foo(), dodaj lub modyfikuj istniejący test test_foo() w taki sposób, aby odzwierciedlał sytuację prowadzącą do błędu. Po uruchomieniu tego testu powinieneś otrzymać czerwony komunikat, informujący o niepowodzeniu testu.
  4. Teraz Twoim celem jest modyfikacja kodu tak, aby test przeszedł pomyślnie. Konieczne może być dostosowanie funkcji, zmiana argumentów przekazywanych do funkcji lub wyeliminowanie innych błędów. Pamiętaj, że kluczową sprawą jest to, by po wprowadzeniu zmian test test_foo() zakończył się sukcesem.
  5. Gdy test już przechodzi, warto zadbać o to, by Twój kod był jak najbardziej optymalny i czytelny. Refaktoryzacja może obejmować zmianę nazw zmiennych, reorganizację kodu czy usuwanie zbędnych fragmentów. Ważne jest jednak, aby w trakcie refaktoryzacji nie wprowadzić nowych błędów - dlatego po każdej większej zmianie warto ponownie uruchomić testy.
  6. W przyszłości regularnie uruchamiaj testy, aby upewnić się, że wszystko działa jak należy. Pamiętaj, że każda modyfikacja kodu powinna być poprzedzona aktualizacją lub dodaniem odpowiednich testów. Dzięki temu zapewnisz ciągłość jakości i unikniesz powrotu starych błędów.

Inne typy testów

W procesie tworzenia oprogramowania wykorzystuje się różne rodzaje testów, aby upewnić się, że system działa prawidłowo na różnych poziomach. Oto trzy główne typy testów:

Optymalne testowanie oprogramowania wymaga odpowiedniego balansu między tymi typami testów. Zbyt mało testów jednostkowych może prowadzić do konieczności intensywniejszego testowania na wyższych poziomach. Według rekomendacji zawartych w książce "Software Engineering at Google", proporcje podziału testów powinny być następujące:

Automatyczne generowanie danych testowych

Podczas tworzenia aplikacji niezmiernie ważne jest, aby mieć dostęp do rzetelnych danych testowych. Może to być kluczowe, szczególnie gdy aplikacja wymaga interakcji z bazami danych lub innymi zewnętrznymi źródłami danych. Skrypty generujące dane testowe pozwalają szybko i efektywnie wypełniać system wartościami, które są zbliżone do rzeczywistych. Dzięki temu programiści i testerzy mogą ocenić, jak aplikacja zachowa się w prawdziwym środowisku, bez ryzyka wprowadzenia błędów w produkcji.

Automatyzacja testów

Automatyzacja testów jest kluczowym elementem współczesnego procesu rozwoju oprogramowania, umożliwiając szybsze wykrywanie błędów i pewność, że wprowadzone zmiany nie wpłynęły negatywnie na istniejącą funkcjonalność. Współczesne narzędzia do automatyzacji testów i integracji ciągłej (CI/CD) umożliwiają nie tylko automatyczne uruchamianie testów, ale także automatyczne wdrażanie aplikacji w odpowiednich środowiskach.

Oto kilka powszechnie używanych narzędzi i platform:

Cecha Travis CI Jenkins GitLab CI/CD CircleCI Selenium
Typ Chmurowa usługa CI Narzędzie CI/CD open source Zintegrowany system CI/CD Chmurowa platforma CI/CD Narzędzie do testowania aplikacji webowych
Integracja z GitHubem Idealna dla projektów hostowanych na GitHubie Możliwa Zintegrowany z GitLabem Możliwa Nie dotyczy
Konfiguracja Prosta konfiguracja Elastyczna konfiguracja, bogaty ekosystem wtyczek Definiowanie potoków pracy przy użyciu plików konfiguracyjnych Automatyczne budowanie, testowanie i wdrażanie aplikacji Symulacja interakcji użytkownika, używanie z wieloma językami programowania
Środowisko Chmura Lokalny serwer lub chmura GitLab Chmura Różne przeglądarki
Popularność Wysoka popularność wśród projektów open source na GitHubie Bardzo popularne w dużych organizacjach i projektach korporacyjnych Wysoka popularność wśród użytkowników GitLaba Mniej popularne Bardzo popularne w testowaniu aplikacji webowych

Spis Treści

    Testy jednostkowe
    1. Czerwone vs zielone testy
    2. Korzyści z testów jednostkowych
    3. TDD (Test Driven Development)
    4. Organizacja projektu z testami
    5. Narzędzia do testów jednostkowych w Pythonie:
      1. Przykład testu unittest
      2. Przykład testu pytest
    6. Od znalezienia buga do poprawnie działającego kodu
    7. Inne typy testów
    8. Automatyczne generowanie danych testowych
    9. Automatyzacja testów