Programowanie obiektowe (OOP, ang. object oriented programming) to sposób budowania programów z obiektów, które łączą dane i operacje na tych danych. Klasa jest szablonem, obiekt to konkretna rzecz zbudowana według tego szablonu. Klasa Uzytkownik mówi, że każdy użytkownik ma e-mail i rolę, a obiekt anna to jeden konkretny użytkownik z adresem anna@example.com.
To czternasta lekcja naszego kursu Javy dla testerów. Dostajecie w niej definicje, które zgadzają się z dokumentacją Oracle, przykłady kodu sprawdzone kompilatorem dla Javy 25 oraz odpowiedź na pytanie, które słyszymy na każdym szkoleniu: po co testerowi klasy i obiekty. Krótka odpowiedź: bez nich nie napiszecie czytelnego frameworka testów, bo wzorzec Page Object to programowanie obiektowe w czystej postaci.
Spis treści
- Czym jest programowanie obiektowe?
- Klasa a obiekt: jaka jest różnica?
- Jak napisać klasę i utworzyć obiekt w Javie?
- Stan, zachowanie i tożsamość obiektu
- Cztery filary programowania obiektowego
- Programowanie strukturalne a obiektowe
- Programowanie obiektowe w automatyzacji testów: Page Object
- Najczęstsze błędy początkujących
Czym jest programowanie obiektowe?
Programowanie obiektowe organizuje kod wokół obiektów, a nie wokół ciągu instrukcji. Obiekt ma pola (dane, które o sobie wie) i metody (czynności, które potrafi wykonać). Dokumentacja Oracle w samouczku Object-Oriented Programming Concepts opisuje to tak: obiekt przechowuje swój stan w polach i udostępnia zachowanie przez metody.
Java jest językiem obiektowym od pierwszej wersji i wciąż jednym z najpopularniejszych na świecie. Według indeksu TIOBE z września 2026 roku zajmuje 4. miejsce z udziałem 7,54 procent. W automatyzacji testów to nadal częsty wybór, szczególnie w bankach i dużych organizacjach, gdzie backend też jest w Javie. Jeśli zastanawiacie się nad językiem na start, porównujemy opcje w artykule jaki język programowania wybrać do automatyzacji testów.
Klasa a obiekt: jaka jest różnica?
Klasa to definicja: opisuje, jakie pola i metody będą miały obiekty. Obiekt to instancja klasy, czyli konkretny egzemplarz w pamięci, z własnymi wartościami pól. Z jednej klasy tworzycie dowolnie wiele obiektów. Klasa to przepis na sernik, obiekt to sernik. Przepisu nie da się zjeść, za to można z niego upiec dwanaście serników, każdy trochę inny.
Jak napisać klasę i utworzyć obiekt w Javie?
Poniższą klasę i program skompilowaliśmy poleceniem javac --release 25 i uruchomiliśmy, wynik działania jest w komentarzach. Klasa pilnuje poprawności danych już w konstruktorze, więc obiekt z błędnym e-mailem w ogóle nie powstanie.
Uzytkownik.java, Java 25
public class Uzytkownik {
private final String email;
private String rola;
private boolean aktywny = true;
public Uzytkownik(String email, String rola) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Niepoprawny e-mail: " + email);
}
this.email = email;
this.rola = rola;
}
public void zablokuj() {
aktywny = false;
}
public boolean czyMozeSieZalogowac() {
return aktywny;
}
public String getEmail() {
return email;
}
public String getRola() {
return rola;
}
}
Demo.java
public class Demo {
public static void main(String[] args) {
Uzytkownik anna = new Uzytkownik("anna@example.com", "admin");
Uzytkownik piotr = new Uzytkownik("piotr@example.com", "tester");
Uzytkownik kopiaAnny = new Uzytkownik("anna@example.com", "admin");
piotr.zablokuj();
System.out.println(anna.czyMozeSieZalogowac()); // true
System.out.println(piotr.czyMozeSieZalogowac()); // false
System.out.println(anna == kopiaAnny); // false: dwa różne obiekty
}
}
Trzy rzeczy, na które warto spojrzeć. Pola są private, więc z zewnątrz da się je tylko odczytać przez metody getEmail() i getRola(). Słowo new wywołuje konstruktor i tworzy obiekt, a o konstruktorach piszemy więcej w lekcji 15. Konstruktory. Obiekty anna i kopiaAnny mają identyczne dane, a mimo to == zwraca false, do czego zaraz wrócimy.
Dla obiektów, które tylko przenoszą dane, na przykład danych logowania w teście, Java ma od wersji 16 rekordy. Wprowadził je dokument JEP 395 z 2021 roku. Rekord sam generuje konstruktor, metody dostępu, equals, hashCode i toString:
DaneLogowania.java
public record DaneLogowania(String login, String haslo) {
public DaneLogowania {
if (login.isBlank()) {
throw new IllegalArgumentException("Login nie może być pusty");
}
}
}
RecordDemo.java
public class RecordDemo {
public static void main(String[] args) {
var a = new DaneLogowania("anna", "Haslo123!");
var b = new DaneLogowania("anna", "Haslo123!");
System.out.println(a.equals(b)); // true: rekord porównuje pola
System.out.println(a == b); // false: nadal dwa obiekty
}
}
Stan, zachowanie i tożsamość obiektu
Każdy obiekt ma trzy cechy. Stan to aktualne wartości pól, na przykład aktywny = false po zablokowaniu konta. Zachowanie to metody, które stan odczytują albo zmieniają, na przykład zablokuj(). Tożsamość to fakt, że obiekt jest odrębnym bytem w pamięci, nawet gdy ma takie same dane jak inny.
== porównuje tożsamość (czy to ten sam obiekt), metoda equals porównuje zawartość, jeśli klasa ją zdefiniuje.W testach różnica między == a equals to klasyczna pułapka. Asercja assertEquals w JUnit używa equals. Jeśli Wasza klasa go nie nadpisuje, dziedziczy wersję z klasy Object, która porównuje tożsamość, i test porównujący dwa „identyczne” obiekty obleje. Rekord rozwiązuje to automatycznie, co widać w przykładzie wyżej. Więcej o asercjach znajdziecie w tekście Asercja: co to jest i jak używać asercji w testach automatycznych.
Wbrew pozorom
Klasa, która nie ma słowa extends, też dziedziczy. Dokumentacja Javy 25 mówi wprost, że klasa Object jest korzeniem hierarchii klas i każda klasa ma ją jako nadklasę. Dlatego nawet pusta klasa class Pusta {} ma metody equals, hashCode i toString. Ten ostatni wypisze coś w rodzaju Pusta@1b6d3586, co w logach z testów wygląda jak szyfr, dopóki ktoś nie nadpisze toString.
Cztery filary programowania obiektowego
Programowanie obiektowe opiera się na czterech filarach. Warto znać je dokładnie, bo w rozmowach rekrutacyjnych na stanowiska automatyzujące padają prawie zawsze, a najczęstsze błędy to mylenie abstrakcji z hermetyzacją i przypisywanie dziedziczenia obiektom zamiast klasom.
Hermetyzacja
Hermetyzacja chroni obiekt przed wpisaniem do niego bzdur i jest fundamentem, na którym programowanie obiektowe buduje resztę filarów. Klasa Uzytkownik wyżej nie pozwala ustawić e-maila bez znaku @, a stan aktywny zmienia się tylko metodą zablokuj(). Szczegóły z modyfikatorami dostępu są w lekcji 17. Hermetyzacja.
Abstrakcja
Abstrakcja to nie to samo co ukrywanie danych. Hermetyzacja odpowiada na pytanie „kto może zmienić stan”, abstrakcja na pytanie „na jakim poziomie szczegółów o tym myślę”. Klasa abstrakcyjna Powiadomienie poniżej mówi, że każde powiadomienie ma treść i da się je wysłać, ale nie mówi, czy to e-mail, czy SMS. Kod, który wysyła powiadomienia, pracuje na tym pojęciu i nie musi znać szczegółów.
Powiadomienie.java, klasa abstrakcyjna
public abstract class Powiadomienie {
protected final String odbiorca;
protected Powiadomienie(String odbiorca) {
this.odbiorca = odbiorca;
}
public abstract String tresc();
public String wyslij() {
return "Do: " + odbiorca + " | " + tresc();
}
}
Dziedziczenie
Dziedziczenie to relacja między klasami, nie między obiektami. Klasa PowiadomienieEmail rozszerza klasę Powiadomienie słowem extends, przejmuje jej pole i metodę wyslij() i dopisuje własną treść. Obiekt podklasy jest jednocześnie instancją nadklasy, ale niczego nie „dziedziczy” od innego obiektu. Więcej przykładów w lekcji 18. Dziedziczenie.
PowiadomienieEmail.java
public class PowiadomienieEmail extends Powiadomienie {
public PowiadomienieEmail(String odbiorca) {
super(odbiorca);
}
@Override
public String tresc() {
return "Twoje zamówienie zostało wysłane (e-mail)";
}
}
Polimorfizm
Polimorfizm w czasie działania polega na tym, że wywołanie p.wyslij() na liście typu Powiadomienie uruchamia wersję metody z faktycznej klasy obiektu. Program poniżej wypisuje dwie różne wiadomości z jednej pętli:
PowiadomienieSms.java
public class PowiadomienieSms extends Powiadomienie {
public PowiadomienieSms(String odbiorca) {
super(odbiorca);
}
@Override
public String tresc() {
return "Twoje zamówienie zostało wysłane (SMS)";
}
}
PolimorfizmDemo.java
import java.util.List;
public class PolimorfizmDemo {
public static void main(String[] args) {
List<Powiadomienie> kolejka = List.of(
new PowiadomienieEmail("anna@example.com"),
new PowiadomienieSms("+48500100200"));
for (Powiadomienie p : kolejka) {
System.out.println(p.wyslij()); // ta sama metoda, różne zachowanie
}
}
}
Wszystkie cztery klasy skompilowaliśmy razem w JDK 25. Wynik:
Do: anna@example.com | Twoje zamówienie zostało wysłane (e-mail)
Do: +48500100200 | Twoje zamówienie zostało wysłane (SMS)
Przeciążanie metod (kilka metod o tej samej nazwie i różnych parametrach) bywa nazywane polimorfizmem czasu kompilacji. Metody statyczne nie biorą jednak udziału w polimorfizmie: według specyfikacji języka Java (JLS 25, punkt 8.4.8.2) metoda statyczna w podklasie ukrywa metodę nadklasy, a nie ją nadpisuje. Wersję wybiera kompilator na podstawie typu zmiennej, nie obiektu. Widać to w tym przykładzie:
Ukrywanie.java
class Rodzic {
static String statyczna() { return "Rodzic.statyczna"; }
String instancyjna() { return "Rodzic.instancyjna"; }
}
class Dziecko extends Rodzic {
static String statyczna() { return "Dziecko.statyczna"; } // ukrywa, nie nadpisuje
@Override
String instancyjna() { return "Dziecko.instancyjna"; } // nadpisuje
}
public class Ukrywanie {
public static void main(String[] args) {
Rodzic r = new Dziecko();
System.out.println(r.instancyjna()); // Dziecko.instancyjna
System.out.println(r.statyczna()); // Rodzic.statyczna
}
}
Programowanie strukturalne a obiektowe
W programowaniu strukturalnym program to ciąg procedur, które operują na wspólnych danych. Programowanie obiektowe zamyka dane i operacje na nich razem w obiektach. Java pozwala pisać funkcje i pętle, które znacie z lekcji 16. Funkcje, ale zawsze wewnątrz klas. Nawet zwarty plik źródłowy z Javy 25 (JEP 512), w którym metody piszecie bez deklaracji klasy, nie łamie tej zasady: klasę deklaruje za Was kompilator.
Z tym tematem wiążą się jeszcze dwa pojęcia, o które często pytacie. Kompozycja oznacza, że obiekt składa się z innych obiektów i odpowiada za ich życie: strona logowania ma pola formularza. Agregacja to luźniejsza relacja: obiekt korzysta z innych obiektów, które istnieją niezależnie od niego, jak zespół i jego członkowie. W praktyce frameworków testów kompozycja jest częściej lepszym wyborem niż głębokie dziedziczenie.
Programowanie obiektowe w automatyzacji testów: Page Object
Wzorzec Page Object to najczęstsze miejsce, w którym tester spotyka programowanie obiektowe w codziennej pracy. Dokumentacja Selenium opisuje Page Object jako klasę, która reprezentuje stronę albo jej fragment i ukrywa lokatory przed testami. Testy wołają metody w rodzaju zalogujJako(), a nie szukają elementów po identyfikatorach. Gdy zmieni się układ strony, poprawiacie jedną klasę, a nie sto testów.
Oba fragmenty poniżej skompilowaliśmy z biblioteką selenium-api 4.51.0. Widać w nich trzy filary naraz: abstrakcję (klasa BasePage), dziedziczenie (extends) i hermetyzację (prywatne lokatory).
BasePage.java, Selenium 4
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public abstract class BasePage {
protected final WebDriver driver;
protected BasePage(WebDriver driver) {
this.driver = driver;
}
protected WebElement element(By lokator) {
return driver.findElement(lokator);
}
public abstract boolean czyZaladowana();
}
LoginPage.java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage extends BasePage {
private final By poleEmail = By.id("email");
private final By poleHaslo = By.id("password");
private final By przyciskZaloguj = By.cssSelector("[data-testid=login-submit]");
public LoginPage(WebDriver driver) {
super(driver);
}
public void zalogujJako(String email, String haslo) {
element(poleEmail).sendKeys(email);
element(poleHaslo).sendKeys(haslo);
element(przyciskZaloguj).click();
}
@Override
public boolean czyZaladowana() {
return element(przyciskZaloguj).isDisplayed();
}
}
W naszych projektach automatyzacji ten układ to punkt wyjścia prawie każdego frameworka UI. Przy ponad 450 000 testów automatycznych napisanych w Quality Island (dane własne, 2026) widzimy też odwrotną stronę: hierarchie z pięcioma poziomami dziedziczenia stron, w których nikt nie wie, skąd bierze się metoda. Dlatego radzimy jedną bazową klasę i komponenty składane przez kompozycję. Więcej wzorców opisujemy w tekście Automatyzacja testów Selenium WebDriver: wzorce projektowe, a jeśli chcecie, żeby taki framework zbudował dla Was zespół z doświadczeniem, zobaczcie automatyzację testów UI.
Najczęstsze błędy początkujących
Z naszego doświadczenia ze szkoleń, przez które przeszło ponad 2 500 specjalistów, te pięć błędów wraca najczęściej. Każdy ma prostą poprawkę:
- Publiczne pola. Każdy test może wpisać do obiektu cokolwiek. Poprawka:
privatei walidacja w konstruktorze. - Porównywanie obiektów przez
==. Test obleje mimo identycznych danych. Poprawka:equalsalbo rekord. - Dziedziczenie dla wygody. Klasa testowa dziedziczy po klasie z narzędziami tylko po to, żeby mieć dostęp do metod. Poprawka: kompozycja albo klasa pomocnicza.
- Klasy „Boga”. Jedna klasa
Utilsna 2 000 linii. Poprawka: jedna odpowiedzialność na klasę. - Mylenie abstrakcji z hermetyzacją na rozmowie rekrutacyjnej. Poprawka: zapamiętajcie pytania „co robi” (abstrakcja) i „kto może zmienić stan” (hermetyzacja).
Jeśli wolicie przećwiczyć to z trenerem na żywym kodzie, mamy szkolenia Programowanie obiektowe w Java i Programowanie w języku Java dla testerów oprogramowania. Prowadzą je praktycy z co najmniej 5 latami doświadczenia w projektach. Jeśli szukacie pierwszej pracy w automatyzacji, profile kompetencyjne testerów publikuje QA Board, platforma łącząca specjalistów QA z firmami.
Co zabrać z tego artykułu
01Klasa to szablon, obiekt to jego instancja w pamięci. Z jednej klasy powstaje dowolnie wiele obiektów.
02Obiekt ma stan (pola), zachowanie (metody) i tożsamość. == porównuje tożsamość, equals zawartość.
03Abstrakcja to praca na pojęciu „co robi”, hermetyzacja to kontrola nad tym, kto zmienia stan. To dwa różne filary.
04Dziedziczenie łączy klasy, nie obiekty. Polimorfizm działa na metodach instancyjnych; metody statyczne są ukrywane, nie nadpisywane.
05Page Object w Selenium to programowanie obiektowe w praktyce testera: jedna klasa na stronę, lokatory prywatne.
Źródło: Oracle Java Tutorials, JLS Java SE 25, dokumentacja Selenium, JEP 395, TIOBE 09.2026, kod sprawdzony kompilatorem JDK 25 11.10.2026.
Budujecie framework testów w Javie i chcecie, żeby przetrwał kolejne wydania? Pomożemy zaprojektować strukturę klas i przenieść testy na Page Object.
Powiązane na blogu Quality Island
- 13. Tablice w Javie
- 15. Konstruktory
- 17. Hermetyzacja
- 18. Dziedziczenie
- Java dla testerów automatyzujących: jak zacząć naukę
Pełna lista źródeł
- Oracle, The Java Tutorials: Object-Oriented Programming Concepts, stan na 2026.
- Oracle, The Java Language Specification, Java SE 25, rozdział 8, 2025. Punkt 8.4.8.2 o ukrywaniu metod statycznych.
- Oracle, Dokumentacja klasy java.lang.Object, Java SE 25, 2025. Object jako korzeń hierarchii klas.
- OpenJDK, JEP 395: Records, 2021. Rekordy jako stała część języka od Javy 16.
- Selenium, Page Object Models, dokumentacja, stan na 2026.
- TIOBE, TIOBE Index, wrzesień 2026. Java na 4. miejscu, 7,54 procent.
- Dane własne Quality Island, 2026: ponad 450 000 napisanych testów automatycznych, ponad 2 500 przeszkolonych specjalistów, trenerzy z minimum 5 latami doświadczenia w projektach.



