Instrukcja warunkowa IF
Czas czytania: 12 minut

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.

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);
        }
    }
}
Instrukcja warunkowa if w Javie: schemat składni if (warunek) z blokiem instrukcji do wykonania
Najprostsza postać: blok wykona się tylko wtedy, gdy warunek jest prawdziwy.

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 {
          ^
Instrukcja warunkowa if else w Javie: schemat z gałęzią dla warunku prawdziwego i fałszywego
Jedno if, jedno opcjonalne else: dwie gałęzie, z których wykona się dokładnie jedna.

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));
        }
    }
}
Konstrukcja Ile razy może wystąpić Kiedy się wykona
if (warunek) dokładnie raz, na początku gdy warunek jest true
else if (warunek) zero lub więcej razy gdy wszystkie wcześniejsze warunki były false, a ten jest true
else zero lub jeden raz, zawsze na końcu gdy żaden wcześniejszy warunek nie był true

Ź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");
        }
    }
}
Błąd Skutek w teście Poprawnie
= zamiast == przy boolean warunek zawsze false, zmienna nadpisana if (!zalogowany) albo if (zalogowany == false)
== przy porównaniu napisów porównuje referencje, wynik zależy od tego, skąd przyszedł napis oczekiwany.equals(zSerwera)
metoda na zmiennej, która może być null, przed sprawdzeniem null NullPointerException zamiast czytelnej porażki najpierw x != null &&, potem wywołanie
brak klamer przy zagnieżdżonym if else trafia do innego if, niż sugeruje wcięcie klamry przy każdym bloku

Ź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));
    }
}
Ćwiczenie Wartości graniczne do sprawdzenia Oczekiwany wynik
1. Czas odpowiedzi 200, 201, 1000, 1001, oraz -1 szybko, akceptowalnie, akceptowalnie, za wolno, wyjątek
2. Blokada wydania (1, 0, 0.9), (0, 3, 0.8), (0, 4, 0.9), (0, 0, 0.79) true, false, true, true
3. Rabat 199.99, 200, 499.99, 500 0, 5, 5, 10

Ź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.

Zobacz szkolenie z Javy

Poprzednia lekcja8. Operatory logiczneSpis kursuWszystkie lekcje kursu JavyNastępna lekcja10. Pętle w Javie

Powiązane na blogu Quality Island

Pełna lista źródeł

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
Szkolenie AI Act w wyrobach medycznych: testy i dokumentacja AI pod MDR
AI Act w wyrobach medycznych
AI Act w wyrobach medycznych: testy i dokumentacja AI pod MDR

1690,00 PLN

30.11.26, 18.01.27, 08.03.27
1 dzień
Szkolenie AI Act w bankowości i ubezpieczeniach: scoring kredytowy, wycena ubezpieczeń i FRIA
AI Act w bankowości i ubezpieczeniach
AI Act w bankowości i ubezpieczeniach: scoring kredytowy, wycena ubezpieczeń i FRIA

1690,00 PLN

27.11.26, 15.01.27, 05.03.27
1 dzień
Szkolenie Testowanie systemów AI wysokiego ryzyka pod AI Act: dokumentacja testowa i QMS dla dostawców
Testowanie systemów AI wysokiego ryzyka pod AI Act
Testowanie systemów AI wysokiego ryzyka pod AI Act: dokumentacja testowa i QMS dla dostawców

2390,00 PLN

26.11.26, 14.01.27, 04.03.27
2 dni
Popularne artykuły
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania, grafika Quality Island
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania
Smoke test: co to jest i czym różni się od sanity testu, grafika Quality Island
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, grafika Quality Island
Jak zostać testerem oprogramowania: ścieżka krok po kroku bez dyplomu informatyka
Najnowsze artykuły
Agentic testing w pracy zespołu QA, co się zmienia w testowaniu z agentami AI
Agentic testing: czy Wasza praca właśnie się zmienia, czy znika
Ile kosztują testy oprogramowania, cennik i czynniki ceny, Quality Island
Ile kosztują testy oprogramowania: cennik i czynniki ceny
Strategia testowania w organizacji, strategia jakości na przykładzie case PKO BP, Quality Island
Strategia testowania w organizacji: czego uczy nas case PKO BP
Popularne kategorie