Ответ
Наследование — это фундаментальный принцип объектно-ориентированного программирования, который позволяет одному классу (дочернему классу, подклассу) перенимать (наследовать) поля и методы другого класса (родительского класса, суперкласса). Это создает иерархию «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-инженера:
- Устранение дублирования: Общий код (инициализация драйвера, логин, фикстуры) пишется один раз в базовом классе.
- Согласованность: Все тесты запускаются в одинаковых предварительных условиях.
- Упрощение поддержки: Изменение в общей логике (например, способа инициализации драйвера) вносится в одном месте — базовом классе.
- Создание специализированных иерархий: Можно создать
BaseAPITest,BaseMobileTest, наследуя от них более конкретные тестовые классы.
Важно: Следует отдавать предпочтение композиции перед наследованием, если отношения между классами не строго подчиняются принципу «является». В тестировании наследование уместно именно для организации фреймворка и общих фикстур.