Что такое наследование в ООП?

«Что такое наследование в ООП?» — вопрос из категории ООП, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Наследование — это фундаментальный принцип объектно-ориентированного программирования, который позволяет одному классу (дочернему классу, подклассу) перенимать (наследовать) поля и методы другого класса (родительского класса, суперкласса). Это создает иерархию «is-a» (является) и способствует повторному использованию кода (DRY — Don't Repeat Yourself).

Прямое применение в автоматизации тестирования: Наследование широко используется в тестовых фреймворках для создания базовых тестовых классов, которые содержат общую конфигурацию, утилиты и шаги подготовки данных.

Пример: Базовый класс для UI-тестов с использованием Selenium WebDriver

// Базовый (родительский) класс для всех UI-тестов.
public class BaseUITest {
    // Защищенное (protected) поле, чтобы наследники имели к нему доступ.
    protected WebDriver driver;
    protected WebDriverWait wait;

    // Метод, который будет выполнен перед каждым тестом в наследниках.
    @BeforeEach
    public void setUp() {
        // Инициализация драйвера (например, Chrome)
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    // Общий метод для логина, который могут использовать все тесты.
    protected void login(String username, String password) {
        driver.findElement(By.id("username")).sendKeys(username);
        driver.findElement(By.id("password")).sendKeys(password);
        driver.findElement(By.id("login-btn")).click();
    }

    // Общий метод для скриншота при падении.
    protected void takeScreenshot(String testName) {
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        try {
            FileUtils.copyFile(screenshot, new File("screenshots/" + testName + ".png"));
        } catch (IOException e) { /* обработка ошибки */ }
    }

    // Метод, который будет выполнен после каждого теста в наследниках.
    @AfterEach
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

// Дочерний (наследующий) класс для конкретного функционального теста.
// Он наследует ВСЕ non-private поля и методы BaseUITest.
class LoginPageTest extends BaseUITest { // Ключевое слово 'extends'

    @Test
    void testSuccessfulLogin() {
        // Arrange: Используем унаследованный драйвер и wait.
        driver.get("https://myapp.com/login");

        // Act: Используем унаследованный метод login().
        login("validUser", "validPass123");

        // Assert: Пишем специфичные для этого теста проверки.
        wait.until(ExpectedConditions.urlContains("/dashboard"));
        String welcomeText = driver.findElement(By.cssSelector(".welcome-msg")).getText();
        assertTrue(welcomeText.contains("validUser"));
    }

    @Test
    void testLoginWithInvalidPassword() {
        driver.get("https://myapp.com/login");
        login("validUser", "wrong");

        String errorMessage = driver.findElement(By.id("error")).getText();
        assertEquals("Invalid credentials", errorMessage);
    }

    // Мы можем ПЕРЕОПРЕДЕЛИТЬ (override) метод родителя, если нужно особое поведение.
    @Override
    @AfterEach
    public void tearDown() {
        takeScreenshot(this.getClass().getSimpleName()); // Сначала делаем скриншот
        super.tearDown(); // Затем вызываем оригинальный метод родителя для закрытия драйвера
    }
}

Преимущества для QA-инженера:

  1. Устранение дублирования: Общий код (инициализация драйвера, логин, фикстуры) пишется один раз в базовом классе.
  2. Согласованность: Все тесты запускаются в одинаковых предварительных условиях.
  3. Упрощение поддержки: Изменение в общей логике (например, способа инициализации драйвера) вносится в одном месте — базовом классе.
  4. Создание специализированных иерархий: Можно создать BaseAPITest, BaseMobileTest, наследуя от них более конкретные тестовые классы.

Важно: Следует отдавать предпочтение композиции перед наследованием, если отношения между классами не строго подчиняются принципу «является». В тестировании наследование уместно именно для организации фреймворка и общих фикстур.