PrzedsprzedażAkademia AI w testach i jakości oprogramowania: 1 350 zł zamiast 1 750 zł netto do 30 listopada

Startuje przedsprzedaż Akademii AI w testach i jakości oprogramowania: 5 tygodni z Tomaszem Stelmachem, start 11 stycznia 2027. Wyjątkowa cena 1 350 zł netto zamiast 1 750 zł tylko do 30 listopada.

zostało 38 dniZobacz program i zapisz się
5 kroków do zrozumienia i wdrożenia Test-Driven Development (TDD): przykład w Javie i JUnit 6
Czas czytania: 13 minut

TDD, czyli Test-Driven Development, to sposób pisania kodu, w którym najpierw piszecie jeden mały test opisujący oczekiwane zachowanie, uruchamiacie go i patrzycie, jak nie przechodzi, a dopiero potem dopisujecie kod, który go spełnia, i porządkujecie całość. Cykl powtarza się co kilka minut. W tym artykule przejdziecie go na prawdziwym przykładzie w Javie 21 i JUnit 6, z komunikatami błędów, które sami zobaczyliśmy w terminalu.

Tekst jest dla programistów, testerów automatyzujących i liderów, którzy chcą wprowadzić TDD w zespole bez rewolucji. Najpierw zrozumienie, potem przykład, potem 5 kroków wdrożenia i uczciwa lista sytuacji, w których TDD nie jest najlepszym narzędziem. Jeśli szukacie szerszego kontekstu, czyli gdzie w strategii testów leżą testy jednostkowe, zacznijcie od artykułu o piramidzie testów.

Czym jest TDD, czyli Test-Driven Development?

Test-Driven Development to praktyka programistyczna, w której test powstaje przed kodem produkcyjnym i prowadzi jego projekt. Spopularyzował ją Kent Beck w książce „Test-Driven Development: By Example” z 2002 roku, a w 2023 roku opisał jej kanoniczną wersję w eseju Canon TDD. Martin Fowler w haśle o TDD zwraca uwagę na to, co zespoły najczęściej pomijają: trzecią fazę, czyli refaktoryzację. Bez niej TDD daje działający, ale coraz bardziej splątany kod.

Najważniejsza zmiana nie jest techniczna. W klasycznym podejściu pytanie brzmi „jak to zaimplementować?”, w TDD „po czym poznam, że działa?”. Test staje się pierwszym użytkownikiem Waszej klasy, więc od razu widać, czy jej interfejs jest wygodny. Jeśli test trudno napisać, to zwykle sygnał, że projekt kodu jest zbyt splątany, a nie że test jest zły.

TDD w liczbach

5

kroków Canon TDD według Kenta Becka, 2023

60 do 90%

mniejsza gęstość defektów w 4 zespołach Microsoft i IBM

15 do 35%

dłuższy czas wytworzenia w tych samych zespołach

39

profesjonalistów w badaniu Fucci i in., kolejność testu nie miała znaczenia

Źródło: Kent Beck, Canon TDD, 2023; Microsoft Research, 2009 (badanie Nagappan i in.); Fucci i in., IEEE TSE, 2016

Jak wygląda cykl Red, Green, Refactor?

Cykl ma trzy fazy, a każda ma jedną pracę do wykonania. Red: piszecie test, który nie przechodzi, bo zachowania jeszcze nie ma. Green: dopisujecie najprostszy kod, który ten test spełnia. Refactor: poprawiacie projekt kodu i testów, nie zmieniając zachowania, przy wszystkich testach na zielono. Kent Beck w Canon TDD dodaje przed cyklem listę scenariuszy testowych i wraca do niej po każdym obrocie.

Faza Co robicie Czego nie robicie Typowy błąd według Canon TDD
Lista testów Spisujecie scenariusze: przypadki typowe, brzegowe, błędne Nie projektujecie jeszcze implementacji Mieszanie decyzji o implementacji z listą zachowań
Red Zamieniacie dokładnie jeden punkt listy w działający test i widzicie, że pada Nie piszecie od razu wszystkich testów z listy Testy bez asercji, pisane dla pokrycia
Green Najprostszy kod, który spełnia ten i wszystkie wcześniejsze testy Nie refaktoryzujecie w tej samej chwili Kopiowanie wyniku z kodu do oczekiwanej wartości w teście
Refactor Nazwy, stałe, usuwanie powtórzeń, przy zielonych testach Nie dodajecie nowego zachowania Abstrahowanie za wcześnie i ponad potrzeby tej sesji

Źródło: Kent Beck, Canon TDD, newsletter.kentbeck.com, 11.12.2023

Ciekawostka, która podważa nazwę metody

Według badania Fucciego, Erdogmusa, Turhana, Oivo i Juristo opublikowanego w IEEE Transactions on Software Engineering w 2016 roku, obejmującego 82 zadania wykonane przez 39 zawodowych programistów, jakość i produktywność wiązały się przede wszystkim z drobnością i równomiernością kroków, a kolejność pisania testu i kodu nie miała istotnego wpływu. Innymi słowy, magia TDD bardziej siedzi w krótkich, regularnych cyklach niż w samym „test najpierw”. Co nie znaczy, że możecie pisać testy w piątek po południu za cały tydzień i nazywać to TDD.

Jak wygląda TDD w praktyce? Przykład w Javie krok po kroku

Weźmy regułę z prawdziwego sklepu: zamówienie od 500 zł dostaje 5 procent rabatu, poniżej 500 zł płacicie pełną kwotę, a ujemna kwota to błąd danych. Każdy krok poniżej skompilowaliśmy javac w trybie Java 21 i uruchomiliśmy w JUnit 6.1.3 10.10.2026. Komunikaty w terminalu są prawdziwe, skrócone tylko o ślad stosu.

Lista testów przed pierwszą linią kodu

Scenariusz 1

Poniżej progu

499,99 zł zostaje 499,99 zł. Wartość tuż pod progiem, bo tu najczęściej mieszka błąd.

Scenariusz 2

Na progu

500,00 zł po rabacie to 475,00 zł. Sprawdza, czy próg jest włącznie.

Scenariusz 3

Dane błędne

Kwota -0,01 zł jest odrzucana wyjątkiem, a nie liczona.

Dopisane w trakcie

Zaokrąglenie

1 234,56 zł po rabacie to 1 172,83 zł, czyli grosze zaokrąglone do dwóch miejsc.

Iteracja 1: Red i Green

Pierwszy test opisuje najprostszy scenariusz. Klasy KalkulatorRabatu jeszcze nie ma, więc czerwone światło zapala się już przy kompilacji. Tak też wygląda Red: test, który nie może przejść, bo zachowania nie ma.

KalkulatorRabatuTest.java, iteracja 1

package pl.qualityisland.tdd;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.math.BigDecimal;
import org.junit.jupiter.api.Test;

class KalkulatorRabatuTest {

    private final KalkulatorRabatu kalkulator = new KalkulatorRabatu();

    @Test
    void zamowieniePonizej500NieMaRabatu() {
        assertEquals(new BigDecimal("499.99"), kalkulator.cenaPoRabacie(new BigDecimal("499.99")));
    }
}

Terminal, faza Red

KalkulatorRabatuTest.java:10: error: cannot find symbol
    private final KalkulatorRabatu kalkulator = new KalkulatorRabatu();
                  ^
  symbol:   class KalkulatorRabatu

Green oznacza najprostszy kod, który spełnia test. Tutaj to po prostu zwrócenie kwoty. Wygląda naiwnie i taki ma być: nie piszecie logiki, której nie wymaga żaden test.

KalkulatorRabatu.java, iteracja 1

package pl.qualityisland.tdd;

import java.math.BigDecimal;

public class KalkulatorRabatu {

    public BigDecimal cenaPoRabacie(BigDecimal kwota) {
        return kwota;
    }
}

Iteracja 2: rabat na progu i pułapka BigDecimal

Drugi test sprawdza próg. Uruchamiacie go przed zmianą kodu i dostajecie czytelny komunikat: expected: <475.00> but was: <500.00>. Dopisujecie warunek z mnożeniem przez 0,95 i… test nadal jest czerwony.

Terminal, pierwsza próba Green

=> org.opentest4j.AssertionFailedError: expected: <475.00> but was: <475.0000>

Dlaczego 475.00 to nie 475.0000

Według dokumentacji klasy BigDecimal metoda equals uznaje dwie liczby za równe tylko wtedy, gdy mają tę samą wartość i tę samą skalę, więc 475.00 i 475.0000 są dla niej różne, a assertEquals korzysta właśnie z equals. Mnożenie 500.00 przez 0.95 daje skalę 4. To dokładnie ten rodzaj błędu, który w raporcie z produkcji wygląda jak „grosze się nie zgadzają w fakturach”, a w TDD wychodzi w trzeciej minucie pracy.

KalkulatorRabatu.java, iteracja 2, Green

package pl.qualityisland.tdd;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class KalkulatorRabatu {

    public BigDecimal cenaPoRabacie(BigDecimal kwota) {
        if (kwota.compareTo(new BigDecimal("500")) >= 0) {
            return kwota.multiply(new BigDecimal("0.95")).setScale(2, RoundingMode.HALF_UP);
        }
        return kwota;
    }
}

Iteracja 3: dane błędne

Trzeci test żąda wyjątku dla kwoty ujemnej. Red: Expected java.lang.IllegalArgumentException to be thrown, but nothing was thrown. Green: jeden warunek na początku metody. Wszystkie trzy testy zielone.

Refactor: stałe, czytelny warunek, testy parametryzowane

Teraz, przy zielonych testach, porządkujecie kod: magiczne liczby trafiają do stałych z nazwami, warunek brzmi jak reguła biznesowa, a trzy podobne testy zamieniają się w jeden test parametryzowany z tabelą wartości, do której łatwo dopisać kolejny przypadek. Po zmianie uruchamiacie wszystko: 5 testów, 5 zielonych.

KalkulatorRabatu.java, po refaktoryzacji

package pl.qualityisland.tdd;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class KalkulatorRabatu {

    static final BigDecimal PROG_RABATU = new BigDecimal("500.00");
    static final BigDecimal MNOZNIK_PO_RABACIE = new BigDecimal("0.95");

    public BigDecimal cenaPoRabacie(BigDecimal kwota) {
        if (kwota.signum() < 0) {
            throw new IllegalArgumentException("Kwota nie może być ujemna: " + kwota);
        }
        if (kwota.compareTo(PROG_RABATU) < 0) {
            return kwota;
        }
        return kwota.multiply(MNOZNIK_PO_RABACIE).setScale(2, RoundingMode.HALF_UP);
    }
}

KalkulatorRabatuTest.java, po refaktoryzacji

package pl.qualityisland.tdd;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class KalkulatorRabatuTest {

    private final KalkulatorRabatu kalkulator = new KalkulatorRabatu();

    @ParameterizedTest(name = "{0} zł po rabacie to {1} zł")
    @CsvSource({
            "0.00,    0.00",
            "499.99,  499.99",
            "500.00,  475.00",
            "1234.56, 1172.83"
    })
    void cenaPoRabacie(String kwota, String oczekiwana) {
        assertEquals(new BigDecimal(oczekiwana), kalkulator.cenaPoRabacie(new BigDecimal(kwota)));
    }

    @Test
    void ujemnaKwotaJestOdrzucana() {
        assertThrows(IllegalArgumentException.class,
                () -> kalkulator.cenaPoRabacie(new BigDecimal("-0.01")));
    }
}

Zwróćcie uwagę na dwie rzeczy. Wartości brzegowe 499,99 i 500,00 pochodzą z listy testów, nie z wyobraźni w ostatniej chwili, i to one zabiją typową pomyłkę > zamiast >=. Po drugie, kod produkcyjny ma kilkanaście linii i powstał w trzech obrotach cyklu, a każdy obrót trwał kilka minut. Techniki doboru takich wartości, czyli podział na klasy równoważności i analiza wartości brzegowych, ćwiczymy na szkoleniu testy jednostkowe dla testerów.

Dlaczego TDD działa i co mówią badania?

TDD działa, bo zmusza do zrozumienia wymagań przed pisaniem kodu i daje informację zwrotną w minutach, a nie po tygodniu w środowisku testowym. Dzięki temu:

  • niejasne wymaganie wychodzi przy pisaniu pierwszego testu, a nie na demo u klienta: w naszym przykładzie pytanie „czy 500 zł to już rabat?” musiało paść przed implementacją,
  • każdy fragment kodu ma od początku przynajmniej jednego użytkownika, czyli test, więc interfejsy klas są prostsze,
  • błąd wprowadzony przy zmianie widać po kilku sekundach, w małym kroku, więc wiadomo, która linia go spowodowała,
  • testy zostają jako wykonywalna dokumentacja: tabela z testu parametryzowanego mówi więcej o regule rabatu niż komentarz w kodzie,
  • refaktoryzacja przestaje być ryzykiem, bo siatka testów od razu pokazuje zmianę zachowania.

Najczęściej cytowane dane pochodzą z badania Nagappana, Maximiliena, Bhata i Williams na 3 zespołach Microsoftu i 1 zespole IBM. Według podsumowania Microsoft Research z 2009 roku zespoły stosujące TDD miały gęstość defektów lepszą o 60 do 90 procent od porównywalnych zespołów bez TDD, ale projekty trwały o 15 do 35 procent dłużej. To uczciwy rachunek: płacicie czasem na początku, odzyskujecie go na utrzymaniu i regresji.

Co mierzono Wynik Źródło Jak to czytać
Gęstość defektów lepsza o 60 do 90% Nagappan i in., Microsoft Research, 2009 Duży efekt, ale 4 zespoły to mała próba
Czas wytworzenia dłuższy o 15 do 35% to samo badanie Koszt widoczny od razu, zysk rozłożony w czasie
Kolejność test i kod bez istotnego wpływu Fucci i in., IEEE TSE, 2016 Liczą się małe, regularne kroki
Pokrycie kodu 60% akceptowalne, 75% godne pochwały, 90% wzorowe Google Testing Blog, 2020 Wytyczna, nie cel sam w sobie

Źródło: zestawienie Quality Island na podstawie źródeł z listy na końcu artykułu

Czy TDD daje pełne pokrycie kodu testami?

Nie, i warto to powiedzieć wprost, bo w wielu tekstach o TDD pada obietnica „niemal stuprocentowego pokrycia”. TDD zwykle daje wysokie pokrycie nowego kodu, bo każda linia powstaje w odpowiedzi na test. Ale pokrycie zależy od praktyki zespołu: od tego, czy lista testów obejmuje przypadki brzegowe i błędne, czy zespół pisze test do każdej poprawki błędu i czy kod starszy niż TDD w ogóle ma testy. Autorzy wpisu Code Coverage Best Practices z Google piszą wprost, że wysoki procent pokrycia nie gwarantuje wysokiej jakości testów, a gonienie stu procent daje fałszywe poczucie bezpieczeństwa.

Dobrym przykładem jest nasz kalkulator. Gdyby lista testów zawierała tylko 1 000 zł i 100 zł, pokrycie linii wynosiłoby 100 procent po dopisaniu testu ujemnej kwoty, a pomyłka > zamiast >= na progu przeszłaby niezauważona. Pokrycie mówi, które linie się wykonały, nie mówi, czy sprawdziliście właściwe wartości. Dlatego w naszych projektach przy przeglądzie testów jednostkowych zamiast samego procentu oglądamy listę przypadków brzegowych i to, czy asercje sprawdzają wynik, a nie tylko brak wyjątku.

Jak wdrożyć TDD w zespole w 5 krokach?

TDD wdraża się zmianą nawyków, nie zarządzeniem. Pięć kroków poniżej układa tę zmianę w kolejności, która u naszych klientów najczęściej kończy się tym, że zespół zostaje przy TDD po pierwszym miesiącu, a nie wraca do „testy dopiszemy później”.

Krok 1

Zrozumcie cykl na katach

Dwie lub trzy sesje po 60 do 90 minut na prostych ćwiczeniach (FizzBuzz, kalkulator rabatu, walidacja numeru konta) w parach. Cel: poczuć rytm Red, Green, Refactor bez presji kodu produkcyjnego.

Krok 2

Przygotujcie narzędzia

Framework testów dla Waszego języka: JUnit 6 dla Javy, pytest dla Pythona, Vitest albo Jest dla TypeScriptu, xUnit albo NUnit dla C#. Testy uruchamiane jednym skrótem w IDE i automatycznie w CI przy każdym pull requeście.

Krok 3

Zacznijcie od nowego kodu

Pierwsze tygodnie tylko na nowych, dobrze odizolowanych modułach z logiką: reguły cenowe, walidacje, obliczenia. Kod z bazą danych i interfejsem użytkownika zostawcie na później.

Krok 4

Pilnujcie cyklu i listy testów

Lista scenariuszy w opisie zadania, jeden test naraz, refaktoryzacja tylko na zielonym. Krótkie sesje pracy w parach albo mob programming pomagają bardziej niż dokument z zasadami.

Krok 5

Mierzcie i rozmawiajcie

Co sprint: liczba defektów znalezionych po merge, czas od zgłoszenia do poprawki, czas trwania testów jednostkowych. Retrospektywa z pytaniem, gdzie TDD pomogło, a gdzie przeszkadzało.

Najwięcej zespołów potyka się na kroku 3. Próba wprowadzenia TDD od razu w starym kodzie bez testów kończy się frustracją, bo klasa z dziesięcioma zależnościami nie da się łatwo przetestować w izolacji. Wtedy lepszy pierwszy ruch to testy charakteryzujące obecne zachowanie i stopniowe wydzielanie logiki. O tym, jak wpleść jakość w pracę zespołu zwinnego bez spowalniania dostaw, piszemy też na Strefie QA w tekście Jak wdrożyć QA w zespole agile bez spowalniania developmentu.

Jeśli chcecie przejść kroki 1 i 4 z trenerem na Waszym stosie technologicznym, prowadzimy szkolenie testy jednostkowe i integracyjne dla programistów, a gdy potrzebny jest plan dla całej organizacji, pomagamy w ramach doradztwa w automatyzacji testów. Według danych Quality Island przeszkoliliśmy już ponad 2 500 specjalistów, a ankiety po szkoleniach (ponad 350 wypełnionych) dają 5 na 5 za poziom merytoryczny.

Kiedy TDD się nie sprawdza i co robi w nim tester?

TDD nie jest odpowiedzią na wszystko i dobrze jest znać jego granice, zanim zespół zacznie się z nim kłócić.

Sytuacja Dlaczego TDD przeszkadza Co zamiast
Prototyp, który za tydzień wyrzucicie Testy utrwalają projekt, którego i tak nie będzie Szybki prototyp, a TDD od pierwszej wersji produkcyjnej
Wygląd interfejsu i układ strony Test „przycisk jest niebieski” zmienia się częściej niż kod Testy wizualne, przegląd projektu, testy dostępności
Stary kod bez punktów wstrzyknięcia zależności Trudno napisać pierwszy test w izolacji Testy charakteryzujące, potem wydzielanie logiki
Integracja z zewnętrznym systemem Test jednostkowy z atrapą nie sprawdzi prawdziwego kontraktu Testy integracyjne i kontraktowe obok TDD

Źródło: doświadczenie Quality Island z przeglądów testów jednostkowych

A gdzie w tym wszystkim tester? Bliżej, niż się wydaje. Lista testów z kroku zero to dokładnie to, w czym testerzy są najlepsi: przypadki brzegowe, dane błędne, kombinacje warunków. W zespołach, które szkolimy, dobrze działa prosta zasada: przed zadaniem z logiką biznesową programista i tester przez 10 minut spisują razem listę scenariuszy. Jeśli scenariusze opisujecie językiem biznesu, naturalnym kolejnym krokiem jest BDD i składnia Gherkin, o której piszemy w artykule język Gherkin: co to jest i jak go używać, a praktykę ćwiczymy na szkoleniu Behavior-Driven Development w praktyce. Na wyższych poziomach testów, gdzie TDD nie sięga, przydają się testy integracyjne.

Co zabrać z tego artykułu

01TDD to cykl Red, Green, Refactor poprzedzony listą testów; Kent Beck w Canon TDD z 2023 roku opisuje go w 5 krokach.

02Najcenniejsze w TDD są małe, regularne kroki: badanie Fucci i in. z 2016 roku nie znalazło istotnego wpływu samej kolejności test i kod.

03Prawdziwy przykład pokazał błąd, którego nikt nie planował: BigDecimal 475.00 to dla equals nie to samo co 475.0000.

04TDD nie gwarantuje pełnego pokrycia; pokrycie zależy od listy przypadków brzegowych i dyscypliny zespołu, a Google traktuje 60, 75 i 90 procent jako wytyczne, nie cel.

05Wdrożenie zacznijcie od kat i nowego kodu z logiką, pilnujcie cyklu w parach i mierzcie defekty po merge, zamiast liczyć tylko procent pokrycia.

Źródło: Kent Beck 2023, Fucci i in. 2016, Microsoft Research 2009, Google Testing Blog 2020, przykład Quality Island 10.10.2026

Chcecie wprowadzić TDD w zespole, ale nie wiecie, od którego modułu zacząć? Przejrzymy Wasze testy jednostkowe i przygotujemy plan wdrożenia na Waszym kodzie.

Zobacz doradztwo


Powiązane na blogu Quality Island

Pełna lista źródeł

Autor: Tomasz Stelmach, Quality Island.

Co o tym sądzisz?

Dodaj komentarz

Bądź na bieżąco
Strefa QA, portal z eksperckimi artykułami o jakości oprogramowania
Bądź na bieżąco
Bezpieczne programowanie z AI
Bezpieczne programowanie z AI: tworzenie oprogramowania z asystentami kodu

1 690,00 zł

26.11.26, 17.12.26, 28.01.27, 04.03.27, 25.03.27
1 dzień
AI Act i RODO w firmie
AI Act i RODO w firmie: bezpieczne korzystanie ze sztucznej inteligencji

1 290,00 zł

24.11.26, 15.12.26, 26.01.27, 02.03.27, 30.03.27
1 dzień
Testy bezpieczeństwa dla początkujących
Testy bezpieczeństwa dla początkujących: OWASP Top 10 i pierwsze testy aplikacji webowych

1 000,00 zł

18.11.26, 09.12.26, 13.01.27, 03.02.27, 24.02.27, 17.03.27
1 dzień
Popularne artykuły
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania
Smoke test: co to jest, kiedy go uruchamiać i czym różni się od sanity testu
Jak zostać testerem oprogramowania: ścieżka krok po kroku bez dyplomu informatyka
Najnowsze artykuły
Testowanie migracji danych i systemów: co sprawdzić, zanim przełączycie produkcję
Metryki DORA psują się w erze AI: dlaczego szybkość wydawania przestaje być dowodem jakości
OWASP Top 10:2025 okiem testera: co sprawdzić w każdej z dziesięciu kategorii
Popularne kategorie