Зачем нативные фреймворки тестирования?

Хотя Appium обеспечивает кроссплатформенное тестирование с единым API, нативные фреймворки — XCUITest для iOS и Espresso для Android — дают значительные преимущества в скорости, надёжности и интеграции с рабочим процессом разработки. Они работают внутри процесса платформы, получая прямой доступ к UI-потоку и устраняя сетевые накладные расходы, вносимые внешними инструментами.

Нативные фреймворки — это инструменты тестирования первого класса от Apple и Google соответственно. Они обновляются вместе с ОС и SDK платформы, обеспечивая совместимость с новейшими функциями. Многие команды разработки используют нативные фреймворки для основных тестовых наборов и оставляют Appium для кроссплатформенных smoke-тестов.

Espresso для Android

Что такое Espresso?

Espresso — это фреймворк UI-тестирования Google для Android. Он работает в том же процессе, что и приложение, получая прямой доступ к UI-потоку и фоновым задачам. Ключевой отличительной особенностью является автоматическая синхронизация — Espresso ждёт, пока UI-поток станет свободным, все операции AsyncTask завершатся и все анимации закончатся, прежде чем выполнить следующее действие теста.

Эта синхронизация устраняет подавляющее большинство нестабильных тестов, вызванных проблемами таймингов. Не нужно добавлять sleep-операторы или ручные ожидания.

Настройка Espresso

// build.gradle (Module: app)
dependencies {
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation 'androidx.test:runner:1.5.2'
    androidTestImplementation 'androidx.test:rules:1.5.0'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}

android {
    defaultConfig {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

Основной API: onView, perform, check

Тесты Espresso следуют трёхшаговому паттерну:

// Найти представление → Выполнить действие → Проверить результат
onView(matcher)          // Найти представление
    .perform(action)     // Сделать что-то с ним
    .check(assertion);   // Проверить результат

Написание тестов Espresso

@RunWith(AndroidJUnit4.class)
public class LoginTest {

    @Rule
    public ActivityScenarioRule<LoginActivity> activityRule =
        new ActivityScenarioRule<>(LoginActivity.class);

    @Test
    public void loginWithValidCredentials_showsDashboard() {
        onView(withId(R.id.email_input))
            .perform(typeText("admin@example.com"), closeSoftKeyboard());

        onView(withId(R.id.password_input))
            .perform(typeText("password123"), closeSoftKeyboard());

        onView(withId(R.id.login_button))
            .perform(click());

        onView(withId(R.id.welcome_text))
            .check(matches(withText("Welcome, Admin")));
    }

    @Test
    public void loginWithInvalidPassword_showsError() {
        onView(withId(R.id.email_input))
            .perform(typeText("admin@example.com"), closeSoftKeyboard());

        onView(withId(R.id.password_input))
            .perform(typeText("wrong"), closeSoftKeyboard());

        onView(withId(R.id.login_button))
            .perform(click());

        onView(withId(R.id.error_message))
            .check(matches(isDisplayed()))
            .check(matches(withText("Invalid credentials")));
    }
}

Матчеры Espresso

// По ID
onView(withId(R.id.username));

// По тексту
onView(withText("Submit"));

// По content description (доступность)
onView(withContentDescription("Search button"));

// По тексту подсказки
onView(withHint("Enter email"));

// Комбинированные матчеры
onView(allOf(withId(R.id.button), withText("Save")));

Тестирование RecyclerView

// Клик на элемент в позиции 3
onView(withId(R.id.recycler_view))
    .perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));

// Скролл к элементу с текстом
onView(withId(R.id.recycler_view))
    .perform(RecyclerViewActions.scrollTo(
        hasDescendant(withText("Target Item"))
    ));

XCUITest для iOS

Что такое XCUITest?

XCUITest — нативный фреймворк UI-тестирования Apple, интегрированный прямо в Xcode. Он использует систему доступности для поиска и взаимодействия с элементами UI, что означает — любой элемент, доступный для VoiceOver, также доступен для тестов. Тесты запускаются как отдельный процесс, общающийся с приложением через фреймворк доступности.

Настройка XCUITest

  1. В Xcode перейдите File > New > Target > UI Testing Bundle
  2. Xcode создаст тестовый таргет с примером тестового файла
  3. Тесты пишутся на Swift (или Objective-C) с использованием фреймворка XCTest

Написание тестов XCUITest

import XCTest

class LoginTests: XCTestCase {
    let app = XCUIApplication()

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app.launch()
    }

    func testLoginWithValidCredentials() {
        let emailField = app.textFields["emailInput"]
        emailField.tap()
        emailField.typeText("admin@example.com")

        let passwordField = app.secureTextFields["passwordInput"]
        passwordField.tap()
        passwordField.typeText("password123")

        app.buttons["loginButton"].tap()

        let welcomeLabel = app.staticTexts["Welcome, Admin"]
        XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 5))
    }

    func testLoginWithInvalidPassword() {
        app.textFields["emailInput"].tap()
        app.textFields["emailInput"].typeText("admin@example.com")

        app.secureTextFields["passwordInput"].tap()
        app.secureTextFields["passwordInput"].typeText("wrong")

        app.buttons["loginButton"].tap()

        let errorMessage = app.staticTexts["errorMessage"]
        XCTAssertTrue(errorMessage.waitForExistence(timeout: 5))
        XCTAssertEqual(errorMessage.label, "Invalid credentials")
    }
}

Запросы элементов XCUITest

// По accessibility identifier (рекомендуется)
app.buttons["submitButton"]
app.textFields["emailInput"]
app.staticTexts["welcomeLabel"]

// По тексту label
app.buttons.matching(NSPredicate(format: "label == 'Submit'")).firstMatch

// По типу элемента
app.buttons.firstMatch
app.tables.cells.element(boundBy: 0)

// Проверка существования и свойств
XCTAssertTrue(app.buttons["submit"].exists)
XCTAssertTrue(app.buttons["submit"].isEnabled)
XCTAssertEqual(app.staticTexts["title"].label, "Dashboard")

Сравнение нативных и кроссплатформенных

ФункцияEspressoXCUITestAppium
ПлатформаТолько AndroidТолько iOSОбе
ЯзыкJava/KotlinSwift/ObjCЛюбой
СкоростьОчень быстроБыстроМедленнее
НадёжностьОчень высокая (авто-синхронизация)ВысокаяСредняя
НастройкаВстроен в Android StudioВстроен в XcodeОтдельная установка
ОтладкаОтладчик Android StudioОтладчик XcodeОграниченная

Когда использовать

Espresso/XCUITest: Основной регрессионный набор, критичные пользовательские потоки, тесты с максимальной надёжностью, команды с платформенными разработчиками.

Appium: Кроссплатформенные smoke-тесты, команды с единой кодовой базой тестов, проекты где QA-инженеры не являются платформенными специалистами.

Гибридный подход (рекомендуется): Используйте Espresso/XCUITest для глубокого, всестороннего набора тестов на каждой платформе. Используйте Appium для меньшего кроссплатформенного smoke-набора, валидирующего одинаковые основные потоки на обеих платформах.

Упражнения

Упражнение 1: Поток логина с Espresso

  1. Создайте тестовый проект Android с зависимостями Espresso
  2. Напишите тесты для экрана логина: валидный логин, неверный пароль, валидация пустого email
  3. Используйте подходящие матчеры (withId, withText, withContentDescription)
  4. Проверьте навигацию на дашборд после успешного логина

Упражнение 2: Поток регистрации с XCUITest

  1. Настройте UI Testing таргет в проекте Xcode
  2. Напишите тесты для формы регистрации: валидная регистрация, несовпадающие пароли, дублирующийся email
  3. Используйте accessibility identifiers для всех запросов элементов
  4. Обработайте алерт подтверждения после успешной регистрации

Упражнение 3: Отчёт сравнения

  1. Реализуйте одни и те же 3 сценария с Espresso, XCUITest и Appium
  2. Измерьте время выполнения тестов для каждого фреймворка
  3. Намеренно внесите баг, зависящий от таймингов, и наблюдайте, какой фреймворк справляется лучше
  4. Напишите рекомендацию для команды на основе результатов