Instrukcja warunkowa if to rozgałęzienie programu: Java oblicza warunek typu boolean i wykonuje blok kodu tylko wtedy, gdy warunek ma wartość true, a opcjonalna klauzula else obsługuje przypadek przeciwny. To dziewiąta lekcja kursu Javy dla testerów. Poza składnią pokazujemy, jak warunki wyglądają w testach automatycznych, jakie błędy w nich najczęściej łapiemy na code review i dajemy trzy ćwiczenia z rozwiązaniami.
Wszystkie przykłady w tej lekcji skompilowaliśmy kompilatorem javac z opcją --release 25, czyli dla Javy 25, najnowszej wersji z długim wsparciem, na której prowadzimy cały kurs. Według strony projektu OpenJDK JDK 25 osiągnął dostępność ogólną 16 września 2025 roku, a według mapy wsparcia Oracle Java SE wsparcie Premier dla Javy 25 trwa do września 2030 roku, więc kod z tej lekcji posłuży Wam jeszcze długo. Jeśli nie macie jeszcze środowiska, zacznijcie od lekcji wprowadzenie do języka Java, a operatory, z których budujemy warunki, omawia lekcja 8 o operatorach logicznych.
Spis treści
- Czym jest instrukcja warunkowa if w Javie?
- Ile może być klauzul else i jak działa else if?
- Jak zagnieżdżać warunki i kiedy lepiej tego nie robić?
- Czym jest operator trójargumentowy i dopasowanie wzorca w if?
- Jakie błędy w warunkach popełniają początkujący testerzy?
- Jak używać instrukcji if w testach automatycznych?
- Ćwiczenia z instrukcji warunkowej if dla testerów
Czym jest instrukcja warunkowa if w Javie?
Według specyfikacji języka Java SE 25, rozdział 14.9, wyrażenie w nawiasach po if musi mieć typ boolean albo Boolean, inaczej kompilacja kończy się błędem. To ważna różnica wobec C i JavaScriptu: w Javie liczba 0 ani pusty napis nie są „fałszem”. Warunek to zawsze porównanie, wynik metody zwracającej boolean albo połączenie takich wyrażeń operatorami &&, || i !.
public class Skladnia {
public static void main(String[] args) {
int kod = 200;
// if bez else: blok wykona się tylko dla true
if (kod == 200) {
System.out.println("odpowiedź poprawna");
}
// if z else: wykona się dokładnie jedna z dwóch gałęzi
if (kod == 200) {
System.out.println("OK");
} else {
System.out.println("błąd, kod " + kod);
}
}
}

Klamry są formalnie opcjonalne, gdy w bloku jest jedna instrukcja, ale w kodzie testów zawsze je piszcie. Za chwilę zobaczycie, dlaczego brak klamer to jedno z ulubionych źródeł błędów, które potem wyglądają jak „niestabilny test”.
Ile może być klauzul else i jak działa else if?
Instrukcja if może mieć najwyżej jedną klauzulę else. Gałęzi else if może być za to dowolnie wiele. Starsza wersja tej lekcji twierdziła, że klauzul else może być więcej niż jedna, co jest nieprawdą: drugie else po zamkniętym bloku nie ma pasującego if i kompilator je odrzuci.
Tak wygląda komunikat kompilatora javac z JDK 25, gdy po bloku if ... else dopiszecie drugie else:
ZleElse.java:8: error: 'else' without 'if'
} else {
^

Konstrukcja else if to po prostu kolejna instrukcja if wstawiona w miejsce else. Java sprawdza warunki od góry i wykonuje pierwszą gałąź, której warunek jest prawdziwy. Według samouczka Oracle o instrukcjach if, po spełnieniu jednego warunku pozostałe nie są już w ogóle obliczane. Przykład z pracy testera: klasyfikacja kodu odpowiedzi HTTP.
public class KodHttp {
static String opisz(int kod) {
if (kod >= 200 && kod < 300) {
return "sukces";
} else if (kod >= 300 && kod < 400) {
return "przekierowanie";
} else if (kod >= 400 && kod < 500) {
return "błąd klienta";
} else if (kod >= 500 && kod < 600) {
return "błąd serwera";
} else {
return "nieznany kod";
}
}
public static void main(String[] args) {
int[] kody = {200, 302, 404, 503, 42};
for (int kod : kody) {
System.out.println(kod + " -> " + opisz(kod));
}
}
}
Źródło: Java Language Specification SE 25, rozdział 14.9; Oracle, The Java Tutorials, The if-then and if-then-else Statements.
Kolejność warunków ma znaczenie. Gdybyście w przykładzie wyżej sprawdzali najpierw kod >= 200, a dopiero potem kod >= 400, kod 404 trafiłby do pierwszej gałęzi jako „sukces”. Dlatego w gałęziach z przedziałami podawajcie obie granice albo układajcie warunki od najwęższego. To dokładnie ten sam problem, który w testowaniu nazywamy analizą wartości brzegowych: przy warunku >= 200 && < 300 testujecie 199, 200, 299 i 300.
Jak zagnieżdżać warunki i kiedy lepiej tego nie robić?
Instrukcję if można umieścić w środku innej. Specyfikacja Javy rozstrzyga przy tym klasyczną pułapkę zwaną dangling else: klauzula else należy zawsze do najbliższego, wewnętrznego if, niezależnie od tego, jak sformatujecie wcięcia.
public class Zagniezdzenie {
public static void main(String[] args) {
boolean zalogowany = true;
boolean admin = false;
// Pułapka dangling else: bez klamer else należy do najbliższego if
if (zalogowany)
if (admin)
System.out.println("panel administratora");
else
System.out.println("zaloguj się");
// Poprawnie: klamry mówią kompilatorowi to samo, co widzi człowiek
if (zalogowany) {
if (admin) {
System.out.println("panel administratora");
}
} else {
System.out.println("zaloguj się");
}
}
}
Pierwsza wersja wypisze „zaloguj się” zalogowanemu użytkownikowi, który nie jest administratorem, a niezalogowanemu nie wypisze nic. Wcięcie sugeruje coś innego, ale kompilator czyta klamry, nie spacje. Wcięcia w Javie są jak horoskop: dobrze się je czyta, ale kompilator nie bierze ich pod uwagę. Rozwiązanie widzicie w drugiej wersji: klamry zawsze. Co ciekawe, javac nie ostrzega przed tą pułapką nawet z opcją -Xlint:all.
Drugie rozwiązanie to mniej zagnieżdżeń. Zamiast trzech poziomów if sprawdzajcie warunki wykluczające na początku metody i od razu z niej wychodźcie (tak zwane guard clauses). Tak wygląda metoda, która wybiera adres środowiska testowego, napisana wczesnymi wyjściami:
Srodowisko.java, wersja z guard clauses, Java 25
public class Srodowisko {
static String adresBazowy(String env) {
if (env == null || env.isBlank()) {
return "http://localhost:8080";
}
if (env.equals("prod")) {
throw new IllegalStateException("Testy niszczące nie uruchamiają się na produkcji");
}
if (env.equals("staging")) {
return "https://staging.example.com";
}
return "https://" + env + ".example.com";
}
}
Każdy warunek od razu kończy metodę, więc nie ma ani jednego else, a przypadek ogólny stoi na dole bez wcięcia. Tę samą logikę zapisaną łańcuchem else if zobaczycie niżej, w części o testach automatycznych. Gdy wybór zależy od jednej wartości z listy, czytelniejszy bywa switch, którego omawiamy w lekcji 11 o instrukcji switch.
Czym jest operator trójargumentowy i dopasowanie wzorca w if?
Gdy warunek ma tylko wybrać jedną z dwóch wartości, zamiast pełnego if else użyjcie operatora trójargumentowego warunek ? wartośćGdyTrue : wartośćGdyFalse. Specyfikacja Javy SE 25 opisuje go w rozdziale 15.25: pierwszy operand musi być typu boolean, a operator grupuje się od prawej, więc a ? b : c ? d : e oznacza a ? b : (c ? d : e). Takie łańcuchy są poprawne, ale nieczytelne. Jeden poziom wystarczy.
Druga nowoczesna konstrukcja to dopasowanie wzorca w instanceof, wprowadzone na stałe w Javie 16 przez JEP 394. Zamiast sprawdzać typ, a potem rzutować, deklarujecie zmienną od razu w warunku i możecie jej użyć w dalszej części warunku po &&.
public class Trojargumentowy {
static String status(int nieudane) {
return nieudane == 0 ? "PASSED" : "FAILED";
}
static String opisWyniku(Object wynik) {
if (wynik instanceof String tekst && !tekst.isBlank()) {
return "komunikat: " + tekst;
} else if (wynik instanceof Integer liczba) {
return "kod: " + liczba;
}
return "brak danych";
}
public static void main(String[] args) {
System.out.println(status(0) + " " + status(3));
System.out.println(opisWyniku("Timeout") + ", " + opisWyniku(504) + ", " + opisWyniku(null));
}
}
Jakie błędy w warunkach popełniają początkujący testerzy?
Z naszego doświadczenia z code review testów automatycznych cztery błędy w warunkach wracają u początkujących regularnie. Każdy z nich kompiluje się bez błędu albo daje wynik, który na pierwszy rzut oka wygląda dobrze.
Kompilator Was tu nie uratuje
Dla zmiennej typu int zapis if (x = 5) się nie skompiluje, bo przypisanie zwraca int, a warunek musi być boolean. Ale dla zmiennej typu boolean zapis if (zalogowany = false) kompiluje się bez słowa. Sprawdziliśmy to na javac 25 z opcjami --release 25 -Xlint:all: zero błędów i zero ostrzeżeń. Warunek zawsze jest fałszywy, a zmienna zostaje po cichu nadpisana, więc test przechodzi „na zielono”, niczego nie sprawdzając.
public class Pulapki {
public static void main(String[] args) {
boolean zalogowany = true;
// Błąd: pojedynczy znak = przypisuje false, a warunek zawsze jest fałszywy
if (zalogowany = false) {
System.out.println("Ten komunikat nigdy się nie wyświetli");
}
// Poprawnie: if (!zalogowany)
String oczekiwany = "OK";
String zSerwera = new String("OK");
System.out.println(zSerwera == oczekiwany); // false: porównuje referencje
System.out.println(zSerwera.equals(oczekiwany)); // true: porównuje treść
String naglowek = null;
// && jest leniwe: przy null druga część się nie wykona i nie ma NullPointerException
if (naglowek != null && naglowek.startsWith("Bearer ")) {
System.out.println("Token jest");
} else {
System.out.println("Brak tokenu");
}
}
}
Źródło: kompilacja javac 25 z opcją --release 25, przykłady w tej lekcji.
Przy trzecim błędzie pomaga leniwe obliczanie operatora &&: jeśli lewa strona jest false, prawa w ogóle się nie wykona. Dlatego kolejność naglowek != null && naglowek.startsWith(...) jest bezpieczna, a odwrotna nie.
Jak używać instrukcji if w testach automatycznych?
W kodzie testów if pojawia się w dwóch rolach: w kodzie pomocniczym, na przykład przy wyborze środowiska, i w samych testach, gdzie zwykle jest sygnałem ostrzegawczym. Najpierw rola dobra. Ten sam zestaw testów uruchamiacie lokalnie, na stagingu i w pipeline, a adres aplikacji wybiera zmienna środowiskowa:
public class Srodowisko {
static String adresBazowy(String env) {
if (env == null || env.isBlank()) {
return "http://localhost:8080";
} else if (env.equals("staging")) {
return "https://staging.example.com";
} else if (env.equals("prod")) {
throw new IllegalStateException("Testy niszczące nie uruchamiają się na produkcji");
} else {
return "https://" + env + ".example.com";
}
}
public static void main(String[] args) {
String env = System.getenv("TEST_ENV");
System.out.println("Testy idą na: " + adresBazowy(env));
}
}
Zwróćcie uwagę na gałąź prod: zamiast zwracać adres, rzuca wyjątek. Taki warunek to tani bezpiecznik przed uruchomieniem testów, które tworzą i kasują dane, na środowisku produkcyjnym.
Teraz rola zła. Test z instrukcją if w środku, typu „jeśli komunikat jest, sprawdź jego treść”, potrafi przejść, nie sprawdzając niczego, bo warunek się nie spełnił. Test ma mieć jedną ścieżkę i asercję, która zawsze się wykona. Gdy test ma sens tylko w określonym środowisku, nie owijajcie go w if, tylko użyjcie założenia z JUnit 6: assumeTrue przerywa test i oznacza go jako pominięty, a nie zaliczony.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.Test;
class LogowanieTest {
static String komunikat(String login, String haslo) {
if (login.isBlank()) {
return "Podaj login";
} else if (haslo.length() < 8) {
return "Hasło musi mieć co najmniej 8 znaków";
}
return "Zalogowano";
}
@Test
void krotkieHasloDajeKomunikat() {
assertEquals("Hasło musi mieć co najmniej 8 znaków", komunikat("anna", "1234567"));
}
@Test
void hasloOsmioznakoweJestPrzyjete() {
assertEquals("Zalogowano", komunikat("anna", "12345678"));
}
@Test
void testTylkoNaStagingu() {
assumeTrue("staging".equals(System.getenv("TEST_ENV")), "Test uruchamiany tylko na stagingu");
assertEquals("Zalogowano", komunikat("anna", "Bezpieczne1"));
}
}
Logika warunkowa w testach to jeden z antywzorców, które szerzej opisuje Strefa QA w tekście o najczęstszych antywzorcach w testowaniu. O tym, jak pisać same asercje, żeby test mówił, co poszło nie tak, przeczytacie w artykule asercja: co to jest i jak jej używać w testach automatycznych. Według danych Quality Island nasz zespół napisał ponad 450 000 testów automatycznych i jeden wniosek z tej skali jest prosty: im mniej rozgałęzień w teście, tym rzadziej ktoś musi go debugować w piątek po południu.
Ćwiczenia z instrukcji warunkowej if dla testerów
Trzy ćwiczenia na wartościach z prawdziwej pracy testera. Najpierw napiszcie rozwiązanie sami, potem porównajcie z kodem poniżej i sprawdźcie wartości graniczne z tabeli.
Ćwiczenie 1
Czas odpowiedzi API
Metoda dostaje czas w milisekundach i zwraca „szybko” do 200 ms włącznie, „akceptowalnie” do 1000 ms włącznie i „za wolno” powyżej. Czas ujemny ma rzucić wyjątek.
Ćwiczenie 2
Czy wynik blokuje wydanie
Wydanie blokuje każdy defekt krytyczny. Bez krytycznych blokuje więcej niż 3 defekty wysokie albo pokrycie testami poniżej 80 procent.
Ćwiczenie 3
Rabat w koszyku
Od 200 zł rabat 5 procent, od 500 zł rabat 10 procent. Napiszcie metodę i wypiszcie przypadki testowe z wartościami brzegowymi.
public class Cwiczenia {
// Ćwiczenie 1: klasyfikacja czasu odpowiedzi API
static String czasOdpowiedzi(long ms) {
if (ms < 0) {
throw new IllegalArgumentException("Czas nie może być ujemny");
} else if (ms <= 200) {
return "szybko";
} else if (ms <= 1000) {
return "akceptowalnie";
} else {
return "za wolno";
}
}
// Ćwiczenie 2: czy wynik przebiegu blokuje wydanie
static boolean blokujeWydanie(int krytyczne, int wysokie, double pokrycie) {
if (krytyczne > 0) {
return true;
}
return wysokie > 3 || pokrycie < 0.8;
}
// Ćwiczenie 3: rabat w koszyku, wartości graniczne 199.99, 200, 500
static int rabatProcent(double kwota) {
if (kwota >= 500) {
return 10;
} else if (kwota >= 200) {
return 5;
}
return 0;
}
public static void main(String[] args) {
System.out.println(czasOdpowiedzi(200) + ", " + czasOdpowiedzi(201) + ", " + czasOdpowiedzi(1001));
System.out.println(blokujeWydanie(0, 4, 0.9) + ", " + blokujeWydanie(0, 1, 0.85));
System.out.println(rabatProcent(199.99) + ", " + rabatProcent(200) + ", " + rabatProcent(500));
}
}
Źródło: rozwiązania skompilowane i uruchomione w Javie 25.
Według danych Quality Island przeszkoliliśmy już ponad 2 500 specjalistów i na szkoleniach, na których szkolimy testerów wchodzących w automatyzację, właśnie ćwiczenie 2 najczęściej wychodzi źle za pierwszym razem: łatwo pomylić || z && i dostać warunek, który blokuje wydanie tylko wtedy, gdy wszystko naraz jest źle. Jeśli chcecie przerobić podstawy Javy z trenerem, służy do tego szkolenie wprowadzenie do programowania od podstaw w Javie, a dla osób, które już piszą proste programy, programowanie w Javie dla testerów oprogramowania. Kolejny krok to automatyzacja testów z Selenium WebDriver, gdzie warunki z tej lekcji wracają przy każdym oczekiwaniu na element.
Co zabrać z tego artykułu
01Warunek w if musi mieć typ boolean albo Boolean. W Javie liczba ani napis nie są „prawdą” ani „fałszem”.
02Instrukcja if ma najwyżej jedno else, zawsze na końcu. Gałęzi else if może być dowolnie wiele, a wykona się pierwsza prawdziwa.
03Else należy do najbliższego if, niezależnie od wcięć. Klamry przy każdym bloku oszczędzają godziny debugowania.
04Pułapki testera: = zamiast == przy boolean, == przy napisach i wywołanie metody przed sprawdzeniem null.
05W testach unikajcie if wokół asercji. Warunek środowiskowy obsłuży assumeTrue z JUnit 6, a nie cichy if.
Źródło: Java Language Specification SE 25, rozdziały 14.9 i 15.25; JEP 394; JUnit 6; przykłady skompilowane w Javie 25.
Chcecie, żeby Wasi testerzy pisali w Javie testy, które da się utrzymać? Na szkoleniu przerobicie warunki, pętle i kolekcje na przykładach z automatyzacji, z trenerem, który zna to z projektów.
Powiązane na blogu Quality Island
- 8. Operatory logiczne
- 11. Instrukcja switch w Javie
- Pętle w Javie: for, while, do while i for-each z przykładami dla testerów
- Asercja: co to jest i jak używać asercji w testach automatycznych
- Java dla testerów automatyzujących: jak zacząć naukę
Pełna lista źródeł
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 14.9 The if Statement, 2025. Stąd wymóg typu boolean lub Boolean i zasada, że else należy do najbliższego if.
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 15.25 Conditional Operator, 2025. Stąd typ pierwszego operandu i grupowanie od prawej.
- Oracle, The Java Tutorials: The if-then and if-then-else Statements, 2024. Stąd zasada, że po spełnieniu warunku pozostałe nie są obliczane.
- OpenJDK, JEP 394: Pattern Matching for instanceof, 2021. Wydanie 16.
- OpenJDK, JDK 25, strona projektu, 2025. Dostępność ogólna 16 września 2025.
- Oracle, Oracle Java SE Support Roadmap, 2026. Java 25 jako wydanie LTS z września 2025, wsparcie Premier do września 2030.
- Quality Island, dane własne: ponad 450 000 napisanych testów automatycznych, 2026.