Android UI Testlerine Giriş 📱

Gorkem KARA
6 min readAug 4, 2024

--

Merhaba arkadaşlar! Bu yazı serisinde sizlere Android üzerinde profesyonelce UI testleri nasıl yazabileceğinizi öğreteceğim. Bu serinin sonunda, siz de profesyonelce UI testleri yazabilecek seviyeye geleceksiniz. İlk makalemizde, UI testlerine giriş yaparak temel kavramları ve neden önemli olduklarını inceleyeceğiz. Hazırsanız başlayalım!

UI Testleri Nedir ve Neden Önemlidir?

UI testleri, bir uygulamanın kullanıcı arayüzünü test etmek için kullanılan testlerdir. Bu testler, kullanıcıların uygulama ile nasıl etkileşime geçtiğini simüle eder ve uygulamanın doğru çalışıp çalışmadığını kontrol eder. UI testleri, uygulamanın kullanıcı deneyimini iyileştirmeye yardımcı olur ve olası hataları erken tespit eder.

UI Testleri ile Unit Testlerin Farkları

UI Testleri: Uygulamanın kullanıcı arayüzünü test eder. Kullanıcıların uygulama ile nasıl etkileşime geçtiğini simüle eder.

Unit Testleri: Uygulamanın küçük ve bağımsız parçalarını test eder. Genellikle fonksiyonlar ve metodlar üzerinde çalışır.

Android’de UI Test Araçları

Android’de UI testleri yazmak için kullanılan iki ana araç vardır:

1. Espresso: Google tarafından geliştirilen ve Android uygulamalarında UI testleri yazmak için kullanılan bir framework.

2. UI Automator: Farklı uygulamalar ve sistem UI’si üzerinde çalışabilen bir framework.

Jetpack Compose ile UI Testlerinin Avantajları

Jetpack Compose, Android’de modern bir UI oluşturma kütüphanesidir ve test yazarken de birçok avantaj sağlar:

Deklaratif UI: Compose ile UI’yi deklaratif olarak tanımlarsınız. Bu, testlerinizin de daha okunabilir ve yazılabilir olmasını sağlar.

Test Edilebilirlik: Compose’un modüler yapısı sayesinde, UI bileşenlerini kolayca izole edebilir ve test edebilirsiniz.

Hızlı Test Yazma: Compose’un sağladığı araçlarla, test senaryolarını hızlıca yazabilir ve çalıştırabilirsiniz.

Entegrasyon Kolaylığı: Compose, UI testleri için sağladığı composeTestRule gibi araçlarla, testlerinizi kolayca entegre etmenize olanak tanır.

Test Senaryosu Oluşturma ve Temel Kavramlar

Test Mimarisine Giriş

Test mimarisi, testlerin organize edilmesi ve yönetilmesi için kullanılan yapıdır. İyi bir test mimarisi, testlerin kolayca yazılabilmesini, anlaşılabilmesini ve bakımının yapılabilmesini sağlar.

Basit Bir Uygulama: Login Ekranı

İlk testimizi yazmadan önce basit bir uygulama oluşturalım. Bu uygulama, kullanıcıların e-posta ve şifre ile giriş yapabileceği basit bir login ekranı olacak.

Proje Yapılandırması

build.gradle (Project Level)

// Jetpack Compose, Dagger Hilt ve Test bağımlılıklarını ekleyin
buildscript {
ext {
compose_version = '1.4.0'
hilt_version = '2.45'
}
dependencies {
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
}
}

build.gradle (App Level)

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

android {
compileSdk 33

defaultConfig {
applicationId "com.example.ui_test_demo"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
compose true
}

composeOptions {
kotlinCompilerExtensionVersion compose_version
}
}

dependencies {
implementation "androidx.core:core-ktx:1.9.0"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.activity:activity-compose:1.7.0"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

// Test bağımlılıkları
androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
}

Login Ekranı

app/src/main/java/com/example/ui_test_demo/ui/LoginScreen.kt

package com.example.ui_test_demo.ui

import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel

@Composable
fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsState()

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// E-posta girişi için TextField
TextField(
value = state.email,
onValueChange = { viewModel.onEmailChange(it) },
label = { Text("Email") }
)
Spacer(modifier = Modifier.height(8.dp))

// Şifre girişi için TextField
TextField(
value = state.password,
onValueChange = { viewModel.onPasswordChange(it) },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(8.dp))

// Doğum tarihi girişi için TextField
TextField(
value = state.birthdate,
onValueChange = { viewModel.onBirthdateChange(it) },
label = { Text("Birthdate") }
)
Spacer(modifier = Modifier.height(8.dp))

// Telefon numarası girişi için TextField
TextField(
value = state.phone,
onValueChange = { viewModel.onPhoneChange(it) },
label = { Text("Phone") }
)
Spacer(modifier = Modifier.height(16.dp))

// Giriş butonu
Button(onClick = { viewModel.onLoginClick() }) {
Text("Login")
}
}
}

app/src/main/java/com/example/ui_test_demo/ui/LoginViewModel.kt

package com.example.ui_test_demo.ui

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

@HiltViewModel
class LoginViewModel @Inject constructor() : ViewModel() {
// UI durumunu tutan StateFlow
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState

// E-posta değişikliklerini işleyen fonksiyon
fun onEmailChange(email: String) {
_uiState.value = _uiState.value.copy(email = email)
}

// Şifre değişikliklerini işleyen fonksiyon
fun onPasswordChange(password: String) {
_uiState.value = _uiState.value.copy(password = password)
}

// Doğum tarihi değişikliklerini işleyen fonksiyon
fun onBirthdateChange(birthdate: String) {
_uiState.value = _uiState.value.copy(birthdate = birthdate)
}

// Telefon numarası değişikliklerini işleyen fonksiyon
fun onPhoneChange(phone: String) {
_uiState.value = _uiState.value.copy(phone = phone)
}

// Giriş butonuna tıklanınca çalışacak fonksiyon
fun onLoginClick() {
// Giriş işlemleri burada yapılacak
}

// E-posta formatını doğrulayan fonksiyon
private fun isEmailValid(email: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}

// Şifre uzunluğunu doğrulayan fonksiyon
private fun isPasswordValid(password: String): Boolean {
return password.length >= 6
}

// Doğum tarihi formatını doğrulayan fonksiyon (MM/DD/YYYY)
private fun isBirthdateValid(birthdate: String): Boolean {
return birthdate.matches("\\d{2}/\\d{2}/\\d{4}".toRegex())
}

// Telefon numarası formatını doğrulayan fonksiyon
private fun isPhoneValid(phone: String): Boolean {
return phone.matches("\\d{10}".toRegex())
}
}

// UI durumunu temsil eden veri sınıfı
data class LoginUiState(
val email: String = "",
val password: String = "",
val birthdate: String = "",
val phone: String = ""
)

Unit Testler

Unit testler, uygulamanın küçük ve bağımsız parçalarını test etmek için kullanılır. Bu testler, fonksiyonların doğru çalışıp çalışmadığını kontrol eder. Testler, belirli bir fonksiyonun veya sınıfın beklenen çıktıyı verdiğinden emin olmak için kullanılır.

Test Fonksiyonlarının Yapısı

Her test fonksiyonu, belirli bir test senaryosunu temsil eder. Test fonksiyonları, genellikle aşağıdaki yapıya sahiptir:

@Test: Bu anotasyon, fonksiyonun bir test fonksiyonu olduğunu belirtir.

assertEquals: Bu fonksiyon, beklenen değer ile gerçek değerin eşit olup olmadığını kontrol eder.

assertTrue/assertFalse: Bu fonksiyonlar, belirli bir koşulun doğru veya yanlış olup olmadığını kontrol eder.

Unit Testlerin Yazılması

Aşağıda, e-posta, şifre, doğum tarihi ve telefon numarası format kontrollerini içeren unit testleri görebilirsiniz.

app/src/test/java/com/example/ui_test_demo/LoginViewModelTest.kt

package com.example.ui_test_demo

import com.example.ui_test_demo.ui.LoginUiState
import com.example.ui_test_demo.ui.LoginViewModel
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test

class LoginViewModelTest {

private lateinit var viewModel: LoginViewModel

@Before
fun setUp() {
viewModel = LoginViewModel()
}

@Test
fun `email format is invalid`() {
viewModel.onEmailChange("invalid-email")
assertEquals("invalid-email", viewModel.uiState.value.email)
// E-posta formatı geçerli değilse assertTrue ile testin başarısız olmasını sağlıyoruz
assertTrue("Email formatı geçersiz", !viewModel.uiState.value.email.contains("@"))
}

@Test
fun `password is too short`() {
viewModel.onPasswordChange("123")
assertEquals("123", viewModel.uiState.value.password)
// Şifre uzunluğu 6 karakterden kısaysa assertTrue ile testin başarısız olmasını sağlıyoruz
assertTrue("Şifre uzunluğu yetersiz", viewModel.uiState.value.password.length < 6)
}

@Test
fun `birthdate format is invalid`() {
viewModel.onBirthdateChange("01-01-1990")
assertEquals("01-01-1990", viewModel.uiState.value.birthdate)
// Doğum tarihi formatı geçerli değilse assertTrue ile testin başarısız olmasını sağlıyoruz
assertTrue("Doğum tarihi formatı geçersiz", !viewModel.uiState.value.birthdate.matches("\\d{2}/\\d{2}/\\d{4}".toRegex()))
}

@Test
fun `phone number format is invalid`() {
viewModel.onPhoneChange("123456")
assertEquals("123456", viewModel.uiState.value.phone)
// Telefon numarası formatı geçerli değilse assertTrue ile testin başarısız olmasını sağlıyoruz
assertTrue("Telefon numarası formatı geçersiz", !viewModel.uiState.value.phone.matches("\\d{10}".toRegex()))
}
}

UI Testler

UI testleri, uygulamanın kullanıcı arayüzünü test etmek için kullanılır. Bu testler, kullanıcıların uygulama ile nasıl etkileşime geçtiğini simüle eder ve uygulamanın doğru çalışıp çalışmadığını kontrol eder.

Espresso ile UI Testleri

Espresso, Android uygulamalarında UI testleri yazmak için kullanılan popüler bir test framework’üdür. Aşağıda basit bir UI testi yazacağız.

Hilt Kullanımı ve Testler

Hilt, Dagger’a dayalı bir bağımlılık enjeksiyon (DI) kütüphanesidir. Testlerde Hilt kullanmak, testlerin bağımlılıklarını kolayca yönetmeyi sağlar. Hilt testleri için bazı özel yapılandırmalar gereklidir:

@HiltAndroidTest: Test sınıfının Hilt kullanacağını belirtir.

HiltAndroidRule: Testten önce Hilt’i başlatmak için kullanılır.

@Before: Testten önce çalıştırılacak işlemleri tanımlar. Hilt bağımlılıklarını enjekte etmek için kullanılır.

Neden Rule’lere Order Veriyoruz?

Rule’lere order vermek, hangi rule’un önce çalışacağını belirlemek için kullanılır. Örneğin, Hilt’i başlatmak için kullanılan HiltAndroidRule ilk sırada olmalıdır, çünkü diğer rule’lar Hilt’e bağımlı olabilir.

app/src/androidTest/java/com/example/ui_test_demo/LoginScreenTest.kt

package com.example.ui_test_demo

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.example.ui_test_demo.ui.LoginScreen
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@HiltAndroidTest
@UninstallModules(AppModule::class)
class LoginScreenTest {

// Hilt'i başlatmak için kullanılan rule
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)

// Compose testleri için kullanılan rule
@get:Rule(order = 1)
val composeTestRule = createComposeRule()

@Before
fun setUp() {
// Hilt bağımlılıklarını enjekte eder
hiltRule.inject()
}

@Test
fun testLoginScreen() {
// Login ekranını yükle
composeTestRule.setContent {
LoginScreen()
}

// E-posta girişi testi
composeTestRule.onNodeWithText("Email")
.performTextInput("test@example.com")

// Şifre girişi testi
composeTestRule.onNodeWithText("Password")
.performTextInput("password123")

// Doğum tarihi girişi testi
composeTestRule.onNodeWithText("Birthdate")
.performTextInput("01/01/1990")

// Telefon numarası girişi testi
composeTestRule.onNodeWithText("Phone")
.performTextInput("5539890976")

// Giriş butonuna tıklama testi
composeTestRule.onNodeWithText("Login")
.performClick()

// Giriş sonrası kontrol edilecek durumlar burada olacak
}
}

Sonuç

Bu makalede, Android UI testlerine giriş yaparak temel kavramları ve neden önemli olduklarını inceledik. Ayrıca, basit bir login ekranı oluşturduk ve hem unit testlerde hem de UI testlerde bu ekranın doğrulama kontrollerini gerçekleştirdik. Jetpack Compose’un test yazma sürecindeki avantajlarını da gördük.

Gelecek Makaleler

Sonraki Makale -> Espresso ile İleri Düzey UI Testleri 📈

3. UI Automator ile Gelişmiş UI Testleri 🔍

4. Unit Testlerin Gücü 💪

5. En İyi Uygulamalar ve İpuçları 🎯

6. Projeyi Tamamlama ve Sonuç 🏁

Bu makale serisi boyunca, her adımı detaylı bir şekilde açıklayarak, sizlere profesyonelce UI testleri yazmayı öğreteceğim. Umarım bu makale sizin için faydalı olmuştur. Herhangi bir sorunuz veya geri bildiriminiz olursa, lütfen yorum yapmaktan çekinmeyin. Bir sonraki makalede görüşmek üzere! 🚀

--

--

Gorkem KARA
Gorkem KARA

No responses yet