Зачем нативные фреймворки тестирования?
Хотя 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
- В Xcode перейдите File > New > Target > UI Testing Bundle
- Xcode создаст тестовый таргет с примером тестового файла
- Тесты пишутся на 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")
Сравнение нативных и кроссплатформенных
| Функция | Espresso | XCUITest | Appium |
|---|---|---|---|
| Платформа | Только Android | Только iOS | Обе |
| Язык | Java/Kotlin | Swift/ObjC | Любой |
| Скорость | Очень быстро | Быстро | Медленнее |
| Надёжность | Очень высокая (авто-синхронизация) | Высокая | Средняя |
| Настройка | Встроен в Android Studio | Встроен в Xcode | Отдельная установка |
| Отладка | Отладчик Android Studio | Отладчик Xcode | Ограниченная |
Когда использовать
Espresso/XCUITest: Основной регрессионный набор, критичные пользовательские потоки, тесты с максимальной надёжностью, команды с платформенными разработчиками.
Appium: Кроссплатформенные smoke-тесты, команды с единой кодовой базой тестов, проекты где QA-инженеры не являются платформенными специалистами.
Гибридный подход (рекомендуется): Используйте Espresso/XCUITest для глубокого, всестороннего набора тестов на каждой платформе. Используйте Appium для меньшего кроссплатформенного smoke-набора, валидирующего одинаковые основные потоки на обеих платформах.
Упражнения
Упражнение 1: Поток логина с Espresso
- Создайте тестовый проект Android с зависимостями Espresso
- Напишите тесты для экрана логина: валидный логин, неверный пароль, валидация пустого email
- Используйте подходящие матчеры (withId, withText, withContentDescription)
- Проверьте навигацию на дашборд после успешного логина
Упражнение 2: Поток регистрации с XCUITest
- Настройте UI Testing таргет в проекте Xcode
- Напишите тесты для формы регистрации: валидная регистрация, несовпадающие пароли, дублирующийся email
- Используйте accessibility identifiers для всех запросов элементов
- Обработайте алерт подтверждения после успешной регистрации
Упражнение 3: Отчёт сравнения
- Реализуйте одни и те же 3 сценария с Espresso, XCUITest и Appium
- Измерьте время выполнения тестов для каждого фреймворка
- Намеренно внесите баг, зависящий от таймингов, и наблюдайте, какой фреймворк справляется лучше
- Напишите рекомендацию для команды на основе результатов