UI-testning kontrollerar korrekt visning och interaktion av element i användargränssnittet för en mobilapp — knappar, textfält, listor och navigeringskomponenter. Till skillnad från enhetstester som kontrollerar affärslogik, emulerar UI-tester användaråtgärder: tryckningar, svep, textinmatning och kontrollerar gränssnittets reaktion. Enligt forskning från Android Developers, 2024, täcker UI-testning 70% av kritiska användarscenarier och möjliggör upptäckt av layoutdefekter som inte är tillgängliga för logiska kontroller.
Huvudpunkter
UI-testning är en typ av automatiserad kontroll där testkoden interagerar med appens grafiska gränssnitt precis som en riktig användare skulle göra. Testet hittar ett element på skärmen — knapp, textfält, lista — utför en åtgärd på det och kontrollerar gränssnittets förväntade reaktion. Till exempel, efter att ha angett ett felaktigt lösenord kontrollerar UI-testet om ett felmeddelande med korrekt text visas på skärmen.
Den största skillnaden mellan UI-tester och andra typer av automatisering är att de fungerar genom operativsystemets Accessibility-lager, inte genom appens interna API:er. Detta innebär att UI-tester ser gränssnittet precis som användaren och skärmläsningssystemet. Tack vare detta kontrollerar UI-tester inte bara funktionalitet utan också tillgängligheten av element — överensstämmelse med WCAG-kraven.
Enligt JetBrains Developer Ecosystem 2023-undersökningen använder 58% av mobilteamen UI-tester i sin CI/CD-pipeline. Genomsnittlig täckning av UI-tester i kommersiella projekt är 30–40% av appens skärmar. Projekt med UI-tester får 25% färre negativa recensioner i appbutiker relaterade till gränssnittskrascher.
Den största skillnaden mellan UI-tester och enhetstester är abstraktionsnivån. Enhetstester arbetar med enskilda klasser och funktioner, isolerade från Android- eller iOS-ramverket. De körs på JVM (för Android) utan att starta emulatorn och tar millisekunder. UI-tester körs på en verklig enhet eller emulator, interagerar med systemtjänster och tar sekunder eller minuter för ett scenario.
Även testens målgrupp skiljer sig. UI-tester kontrollerar end-to-end användarscenarier — registrering, beställning, sökning. Enhetstester täcker affärslogik: beräkningar, validering, datatransformering. Ett UI-test kontrollerar inte korrektheten av en skatteberäkning — det kontrollerar om slutbeloppet visas på skärmen. Själva beräkningen kontrolleras av ett enhetstest.
Enligt Google Testing Blog (2020) bör den optimala fördelningen av tester i ett projekt följa testpyramidens regel: 70% enhetstester, 20% integrationstester och 10% UI-tester. Brott mot denna fördelning till förmån för UI-tester leder till ökad exekveringstid och skörhet i testpaketet, eftersom UI-tester är känsliga för förändringar i skärmlayouten.
För Android är det dominerande ramverket Espresso — ett bibliotek från Google inbäddat i AndroidX Test. Espresso synkroniserar automatiskt med UI-tråden och väntar på slutförandet av animationer och bakgrundsuppgifter innan nästa kontroll utförs. För Jetpack Compose används tillägget Compose UI Test, som fungerar genom semantiska noder istället för traditionella vyidentifierare.
För iOS är huvudverktyget XCUITest, som är en del av Xcode. Tester skrivs i Swift och använder Accessibility-identifierare för att hitta element. XCUITest stöder inspelning av tester via record-funktionen och integration med CI-system via xcodebuild. För plattformsoberoende projekt används Appium, baserat på WebDriver-protokollet, som gör det möjligt att köra samma tester på Android och iOS med minimala ändringar i koden.
Espresso arbetar med det traditionella View-systemet via onView och resurs-id-identifierare. Compose UI Test använder ett semantiskt lager, vilket gör tester mindre beroende av vyhierarkin. Till exempel, att hitta en knapp i Espresso: onView(withId(R.id.submit)), i Compose: onNodeWithTag(“submit”). Compose-tester hanterar automatiskt rekomposition och kräver ingen explicit väntan på viloläge.
XCUITest använder XCUIApplication som ingångspunkt. Varje gränssnittselement hittas via Accessibility-egenskaper: accessibilityIdentifier för programmatisk åtkomst och accessibilityLabel för VoiceOver. Ramverket stöder inspelning av tester via record-funktionen i Xcode — utvecklaren utför åtgärder på simulatorn och Xcode genererar testkoden. Färdiga tester körs via xcodebuild test.
Appium är baserat på WebDriver-protokollet och stöder alla språk: Java, Python, JavaScript. För att hitta element används strategierna id, xpath, class name och accessibility id. Appium kräver installation av en server och konfiguration av Desired Capabilities — platformName, deviceName, appPackage. Ett alternativ är Maestro, som använder YAML-scenarier och inte kräver kompilering av testkod.
Låt oss titta på UI-tester för samma scenario — inloggning i appen — i tre olika ramverk: Espresso för Android, XCUITest för iOS och Appium för den plattformsoberoende metoden. Scenario: ange användarnamn och lösenord, tryck på inloggningsknappen, kontrollera att välkomstmeddelandet visas.
Testet i Espresso använder onView för att hitta elementet med identifierare och perform för att utföra åtgärden. Metoden check med matchern isDisplayed bekräftar att elementet är synligt på skärmen.
@RunWith(AndroidJUnit4::class)
class LoginUiTest {
@Rule
@JvmField
val composeTestRule = createComposeRule()
@Test
fun login_withValidCredentials_showsWelcome() {
composeTestRule
.onNodeWithTag("emailField")
.performTextInput("user@example.com")
composeTestRule
.onNodeWithTag("passwordField")
.performTextInput("secret123")
composeTestRule
.onNodeWithTag("loginButton")
.performClick()
composeTestRule
.onNodeWithText("Välkommen, Användare!")
.assertIsDisplayed()
}
}
XCUITest använder XCUIApplication för åtkomst till gränssnittselement via Accessibility-identifierare. Metoderna tap() och exists ger interaktion och kontroll.
class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launch()
}
func testLogin_withValidCredentials_showsWelcome() {
app.textFields["emailField"].tap()
app.textFields["emailField"].typeText("anvandare@example.com")
app.secureTextFields["passwordField"].tap()
app.secureTextFields["passwordField"].typeText("hemligt123")
app.buttons["loginButton"].tap()
XCTAssertTrue(app.staticTexts["Welcome, User!"].exists)
}
}
Första principen — använd Accessibility-identifierare istället för textetiketter för att hitta element. Knapptexten kan ändras vid lokalisering, medan identifieraren förblir stabil. I Android är detta egenskapen contentDescription, i iOS — accessibilityIdentifier. Detta tillvägagångssätt gör tester oberoende av gränssnittsspråket och minskar underhållskostnaderna vid ändring av copywriting.
Undvik sleep() och fasta fördröjningar — använd ramverkets inbyggda väntemekanismer. Espresso väntar automatiskt på slutförandet av animationer och bakgrundsuppgifter. XCUITest tillhandahåller XCTAssertTrue med timeout. Explicita pauser saktar ner tester och gör dem mindre stabila, särskilt på långsamma enheter i CI-miljö.
Gruppera tester efter kritikalitet: smoke-tester (3–5 nyckelscenarier) körs vid varje commit, hela UI-testpaketet — före lansering. Enligt Google Testing Blog (2022) minskar UI-tester som tar mer än 30 minuter i CI körfrekvensen med 40%, vilket minskar deras effektivitet som verktyg för tidig upptäckt av regressioner.
UI-tester har ett antal begränsningar. Känslighet för layoutändringar: en ändring av identifieraren, hierarkin eller elementtypen bryter testet även med oförändrad funktionalitet. Lösning — användning av Page Object-mönstret, som centraliserar elementväljare i separata klasser. Vid layoutändring korrigeras en Page Object-fil, inte dussintals tester.
Exekveringstid: körning på en verklig enhet eller emulator tar 10–50 gånger längre än ett enhetstest. Lösning — parallellkörning av UI-tester på flera enheter via Firebase Test Lab eller AWS Device Farm. Instabilitet (flakiness) — ett vanligt problem vid CI-körningar, orsakat av animationer, nätverksfördröjningar eller emulatorns tillstånd. För att bekämpa flakiness används automatisk omkörning av misslyckade tester och stabilitetsanalys av varje testscenario.
Vanliga frågor
För en genomsnittlig skärm räcker 3–5 UI-tester: happy path, felvalidering, tomt tillstånd, ändring av orientering och Accessibility-kontroll. Komplexa skärmar med flera tillstånd — beställningsformulär, inställningar — kan kräva 10–15 tester för fullständig täckning av nyckelscenarier.
Ja, Appium och Maestro gör det möjligt att köra samma scenarier på båda plattformarna. Dock ger inbyggda ramverk — Espresso och XCUITest — bättre stabilitet, hastighet och tillgång till plattformsfunktioner som inte är tillgängliga via WebDriver-proxy.
För Compose används biblioteket Compose UI Test med semantiska matchers: onNodeWithText, onNodeWithTag, onNodeWithContentDescription. Compose semantiska lager abstraherar vyhierarkin, vilket gör tester mindre sköra jämfört med traditionellt Espresso för View-systemet.
Grundkörning av UI-tester utförs på emulatorer i CI — det är snabbt och billigt. Slutlig verifiering före lansering rekommenderas att göras på fysiska enheter via Firebase Test Lab för att ta hänsyn till egenskaperna hos riktig hårdvara: olika upplösningar, OS-versioner och prestanda.
Använd parallellkörning på flera enheter, stäng av animationer på emulatorn via Developer Options, bygg en modulär testarkitektur och kör smoke-paketet vid varje commit och den fullständiga regressionen enligt schema eller före lansering.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också