В чем разница между абстрактным классом и интерфейсом?

Ответ

В контексте автоматизации тестирования на Java (или C#) я использую и абстрактные классы, и интерфейсы для построения гибкой и поддерживаемой фреймворка.

Абстрактный класс — это класс, который может содержать как абстрактные методы (без реализации), так и конкретные методы с реализацией, а также поля с данными.

  • Зачем в тестах: Для создания базового класса теста, который содержит общую логику (настройку/сброс драйвера, логирование), которую наследуют все конкретные тест-классы.

Интерфейс — это контракт, который определяет только сигнатуры методов (и, начиная с Java 8, default и static методы), но не их реализацию. Класс может реализовывать несколько интерфейсов.

  • Зачем в тестах: Для определения общего поведения (например, интерфейс WebActions с методами click(), typeText()), которое по-разному реализуется для Web, Mobile или API-тестов, обеспечивая полиморфизм.

Пример в Java для фреймворка тестирования:

// Интерфейс для поведения драйвера
interface BrowserActions {
    void openUrl(String url);
    WebElement findElement(By locator);
}

// Абстрактный класс с общей инфраструктурой
abstract class BaseTest {
    protected WebDriver driver;

    @BeforeEach
    public void setUp() {
        driver = new ChromeDriver(); // Общая настройка
    }

    protected void takeScreenshot(String name) {
        // Общая реализация скриншота
    }

    @AfterEach
    public void tearDown() {
        driver.quit(); // Общая очистка
    }
}

// Конкретный тест-класс
class LoginTest extends BaseTest implements BrowserActions {
    // Должен реализовать методы из BrowserActions
    public void openUrl(String url) { driver.get(url); }
    public WebElement findElement(By locator) { return driver.findElement(locator); }

    @Test
    public void testValidLogin() {
        openUrl("https://example.com/login");
        // ... логика теста, использующая методы из BaseTest и BrowserActions
    }
}

Ответ 18+ 🔞

Смотри, объясню на пальцах, как я это в своих фреймворках делаю, а то народ путается, как будто на дворе 2002-й год. Терпения ноль ебать, когда слышу, что это одно и то же.

Абстрактный класс — это типа такого папаши-основателя, который уже половину дел за тебя сделал. У него могут быть и пустые обещания (абстрактные методы), и реальные дела (реализованные методы), и даже своё барахло (поля).

  • Зачем в тестах? Да чтобы не писать одно и то же в каждом тесте! Создаёшь BaseTest, кидаешь туда настройку драйвера (@BeforeEach), сброс (@AfterEach), общие утилиты вроде скриншотов — и все твои конкретные тест-классы просто наследуются от него. Удобно, чё.

Интерфейс — это чистый контракт, ёпта. Голый список того, что ты ОБЯЗАН уметь делать. Никакой своей реализации, только подписи. С Java 8, правда, можно default методы впихнуть, но это уже такие костыли...

  • Зачем в тестах? Чтобы определить общее поведение для абсолютно разных сущностей. Ну вот смотри: делаешь интерфейс WebActions с методами click() и typeText(). Его может реализовать и класс для веб-тестов, и класс для мобильных тестов, и даже для API какая-нибудь заглушка. Полиморфизм, детка! Главное — одну кнопку нажать по-разному.

Пример на Java, чтобы совсем мозг не взорвался:

// Интерфейс — контракт на действия в браузере
interface BrowserActions {
    void openUrl(String url);
    WebElement findElement(By locator);
}

// Абстрактный класс — наша общая тестовая казарма
abstract class BaseTest {
    protected WebDriver driver;

    @BeforeEach
    public void setUp() {
        driver = new ChromeDriver(); // Все здесь получают хромдрайвер
    }

    protected void takeScreenshot(String name) {
        // Здесь реальный код для скриншота, чтоб его не копипастить
    }

    @AfterEach
    public void tearDown() {
        driver.quit(); // Прибраться за всеми — тоже здесь
    }
}

// А вот конкретный тест — наследует казарму и подписывает контракт
class LoginTest extends BaseTest implements BrowserActions {
    // Контракт обязывает! Реализуем методы:
    public void openUrl(String url) { driver.get(url); }
    public WebElement findElement(By locator) { return driver.findElement(locator); }

    @Test
    public void testValidLogin() {
        openUrl("https://example.com/login"); // Метод из интерфейса
        // ... а тут используем и логику из BaseTest, и всё остальное
        takeScreenshot("after_login"); // Метод из абстрактного класса
    }
}

Короче, абстрактный класс — это чтобы не повторяться, выносить общую логику. А интерфейс — это чтобы гарантировать поведение для разных, но в чём-то похожих сущностей. Используешь оба — получаешь фреймворк, который не стыдно в резюме написать. Сам от себя охуевал, когда первый раз до этого додумался.

Видео-ответы