QA Interview Hub · База знань · Тести

Java: основи

ООП у фреймворку

BaseTest, BasePage і межі наслідування

У типовому фреймворку є два «базові» класи:

  • BaseTest — спільне налаштування й прибирання: читання конфігурації, створення та закриття драйвера чи клієнта, підготовка даних. Його успадковують тестові класи.
  • BasePage — те, що потрібно кожній сторінці: посилання на драйвер, очікування, спільні допоміжні методи.

Базовий клас зручний, але має межу. Наслідування моделює «є різновидом» (is-a), і глибокі ланцюжки LoginPage → AuthPage → CommonPage → BasePage швидко стають крихкими: зміна в предку ламає всіх нащадків, а клас успадковує методи, які йому не потрібні.

Композиція моделює «має» (has-a): сторінка містить компонент HeaderComponent чи Table як поле й делегує йому роботу. Один компонент використовують різні сторінки без спільного предка.

class OrdersPage extends BasePage {
    private final HeaderComponent header = new HeaderComponent(driver);
    private final DataTable table = new DataTable(driver);
}

Практичні правила:

  • В Java клас має лише одного предка, тому «базу» не варто розтягувати: тримайте її тонкою.
  • Перевіряє (assert) тест, а не сторінка; сторінка надає дії й стан.
  • Локатори й внутрішні допоміжні методи — private, дії для тестів — public, те, що потрібне нащадкам, — protected.
  • Метод, який сторінка повертає після дії, може віддавати наступну сторінку (fluent): loginPage.login(u).openOrders().

Інтерфейси, final, static і чому статичний драйвер ламає паралельний запуск

Інтерфейс — контракт без стану: що вміє об’єкт, без прив’язки до реалізації. Клас може реалізувати кілька інтерфейсів, але успадковувати лише один клас. У фреймворку інтерфейси доречні там, де є взаємозамінні реалізації: DriverFactory для локального запуску й Grid/хмари, TestDataProvider із файлу чи з API, Reporter. Тест залежить від інтерфейсу, а вибір реалізації робить конфігурація. Метод default в інтерфейсі дає типову реалізацію (Java 8+).

Абстрактний клас доречний, коли потрібен спільний стан і частина готового коду (наприклад, BaseTest).

final має три значення:

  • змінна — присвоїти можна лише раз; для посилання це забороняє перепризначення, але не змінює вміст (final List можна доповнювати);
  • метод — не можна перевизначити;
  • клас — не можна успадкувати (String — final).

Модифікатори доступу: private — лише клас; без модифікатора (package-private) — пакет; protected — пакет і нащадки; public — усі. Чим вужчий доступ, тим вільніше змінювати реалізацію.

static належить класу, а не об’єкту, тому спільний для всіх потоків. Класична помилка:

public class BaseTest {
    protected static WebDriver driver;   // одне поле на весь JVM
    void setUp() { driver = new ChromeDriver(); }
}

За паралельного запуску кожен тест перезаписує driver іншого, закриває чужий браузер чи клікає не на тій сторінці; збої нестабільні й важко відтворюються. Рішення: нестатичне поле (коли раннер створює окремий екземпляр класу на кожен тест, як JUnit 5 за замовчуванням) або ThreadLocal (див. розділ про паралельність). Ту саму проблему дають статичні змінні зі станом: поточний користувач, токен, лічильники. Статичні утиліти без стану (наприклад, форматування дати) безпечні.