Unit Testlerin Gücü 💪

Gorkem KARA
4 min readAug 5, 2024

--

Merhaba arkadaşlar! Bir önceki makalede, UI Automator kullanarak gelişmiş UI testleri yazmayı öğrendik. Bu makalede ise, unit testlerin gücünü ve nasıl yazılacağını keşfedeceğiz. Hazırsanız başlayalım!

Unit Test Nedir?

Unit testler, yazılımın en küçük parçalarını, yani birimlerini test etmek için kullanılan testlerdir. Bu testler, bir fonksiyonun veya metodun beklenen çıktıyı verip vermediğini kontrol eder. Unit testler, kodun doğru çalıştığını ve beklenmedik hataların önüne geçildiğini garanti etmek için önemlidir.

Unit Testlerin Avantajları

Hızlı ve Verimli: Unit testler hızlı çalışır ve yazılımdaki hataları erken aşamada tespit eder.

Kolay Bakım: Kodun bakımını kolaylaştırır ve refactoring işlemlerini güvenle yapmayı sağlar.

Daha Az Hata: Kodun daha az hatalı olmasını sağlar ve yazılımın güvenilirliğini artırır.

Test Ortamının Hazırlanması

Proje Yapılandırması

Unit testler için projemize gerekli bağımlılıkları ekleyelim.

build.gradle (App Level)

dependencies {
...
// Unit test bağımlılıkları
testImplementation "junit:junit:4.13.2"
testImplementation "org.mockito:mockito-core:3.11.2"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1"
}

Unit Test Yazma

Kullanıcı Sınıfı ve UserRepository

Öncelikle, User sınıfımızı ve ilgili UserRepository sınıfını genişletelim. Sonrasında bu sınıflar için kapsamlı unit testler yazalım.

app/src/main/java/com/example/ui_test_demo/User.kt

package com.example.ui_test_demo

// Kullanıcı veri sınıfı
data class User(val id: String, val name: String, val email: String, val age: Int) {
// Kullanıcının yetişkin olup olmadığını kontrol eden fonksiyon
fun isAdult(): Boolean {
return age >= 18
}
}

app/src/main/java/com/example/ui_test_demo/UserRepository.kt

package com.example.ui_test_demo

// Kullanıcı verilerini almak ve güncellemek için kullanılan repository sınıfı
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): User {
return api.getUser(id)
}

fun updateUserEmail(user: User, newEmail: String): User {
return user.copy(email = newEmail)
}

fun isUserAdult(user: User): Boolean {
return user.isAdult()
}
}

// Kullanıcı API arayüzü
interface UserApi {
suspend fun getUser(id: String): User
}

UserRepository Testleri

Kullanıcı bilgileriyle oynayarak unit testler yazalım ve daha kapsamlı assert durumlarını içeren testler oluşturalım.

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

package com.example.ui_test_demo

import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.junit.Assert.*

class UserRepositoryTest {

private lateinit var api: UserApi
private lateinit var repository: UserRepository

@Before
fun setUp() {
// UserApi mock objesini oluştur
api = mock(UserApi::class.java)
repository = UserRepository(api)
}

@Test
fun `getUser returns user`() = runBlocking {
// Test verisini hazırla
val user = User("1", "John Doe", "john@example.com", 25)

// Mock objenin getUser fonksiyonu çağrıldığında döneceği değeri ayarla
`when`(api.getUser("1")).thenReturn(user)

// Repository üzerinden getUser fonksiyonunu çağır ve sonucu kontrol et
val result = repository.getUser("1")
assertEquals(user, result)
}

@Test
fun `updateUserEmail updates email correctly`() {
// Test verisini hazırla
val user = User("1", "John Doe", "john@example.com", 25)
val newEmail = "new.email@example.com"

// Email güncelleme fonksiyonunu çağır
val updatedUser = repository.updateUserEmail(user, newEmail)

// Güncellenmiş kullanıcı verisini kontrol et
assertNotNull(updatedUser)
assertEquals(newEmail, updatedUser.email)
assertEquals(user.id, updatedUser.id)
assertEquals(user.name, updatedUser.name)
assertEquals(user.age, updatedUser.age)
}

@Test
fun `isUserAdult returns true for users older than 18`() {
// Test verilerini hazırla
val adultUser = User("1", "John Doe", "john@example.com", 25)
val minorUser = User("2", "Jane Doe", "jane@example.com", 17)

// Yetişkinlik durumunu kontrol et
assertTrue(repository.isUserAdult(adultUser))
assertFalse(repository.isUserAdult(minorUser))
}

@Test
fun `isUserAdult returns false for users younger than 18`() {
// Test verisini hazırla
val user = User("3", "Jack Doe", "jack@example.com", 15)

// Yetişkinlik durumunu kontrol et
val result = repository.isUserAdult(user)
assertFalse(result)
}

@Test
fun `getUser throws exception for invalid user`() = runBlocking {
// Mock objenin getUser fonksiyonu çağrıldığında exception fırlatacağını ayarla
`when`(api.getUser("invalid")).thenThrow(RuntimeException("User not found"))

// Exception durumunu kontrol et
try {
repository.getUser("invalid")
fail("Expected an exception to be thrown")
} catch (e: Exception) {
assertTrue(e is RuntimeException)
assertEquals("User not found", e.message)
}
}
}

Coroutine Testleri

Kotlin Coroutines kullanıyorsanız, coroutine’lerin doğru çalıştığını test etmek önemlidir. Bunun için kotlinx.coroutines-test kütüphanesini kullanacağız.

LoginViewModel’e Coroutine Ekleme

Öncelikle, LoginViewModel’e bir coroutine ekleyelim.

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

package com.example.ui_test_demo.ui

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

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

fun onEmailChange(email: String) {
_uiState.value = _uiState.value.copy(email = email)
}

fun onPasswordChange(password: String) {
_uiState.value = _uiState.value.copy(password = password)
}

fun onBirthdateChange(birthdate: String) {
_uiState.value = _uiState.value.copy(birthdate = birthdate)
}

fun onPhoneChange(phone: String) {
_uiState.value = _uiState.value.copy(phone = phone)
}

fun onLoginClick() {
// Giriş işlemleri burada yapılacak
}

// Coroutine ile veri çekme işlemi
fun fetchUserData() {
viewModelScope.launch {
_uiState.value = _uiState.value.copy(isLoading = true)
delay(2000) // Veri çekme işlemi simülasyonu
_uiState.value = _uiState.value.copy(
isLoading = false,
userData = "User data fetched"
)
}
}

private fun isEmailValid(email: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}

private fun isPasswordValid(password: String): Boolean {
return password.length >= 6
}

private fun isBirthdateValid(birthdate: String): Boolean {
return birthdate.matches("\\d{2}/\\d{2}/\\d{4}".toRegex())
}

private fun isPhoneValid(phone: String): Boolean {
return phone.matches("\\d{10}".toRegex())
}
}

data class LoginUiState(
val email: String = "",
val password: String = "",
val birthdate: String = "",
val phone: String = "",
val isLoading: Boolean = false,
val userData: String? = null
)

Coroutine Testi Yazma

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

package com.example.ui_test_demo

import com.example.ui_test_demo.ui.LoginViewModel
import com.example.ui_test_demo.ui.LoginUiState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.advanceTimeBy
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestWatcher
import org.junit.runner.Description

@ExperimentalCoroutinesApi
class LoginViewModelTest {

private lateinit var viewModel: LoginViewModel
private val testDispatcher = TestCoroutineDispatcher()

@get:Rule
val coroutineRule = CoroutineTestRule(testDispatcher)

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

@Test
fun `fetchUserData updates UI state correctly`() = runBlockingTest {
viewModel.fetchUserData()

// Coroutine'in çalışmasını simüle ediyoruz
advanceTimeBy(2000)

val uiState = viewModel.uiState.value
assertEquals(false, uiState.isLoading) // isLoading false olmalı
assertEquals("User data fetched", uiState.userData) // userData doğru olmalı
}
}

@ExperimentalCoroutinesApi
class CoroutineTestRule(
private val dispatcher: TestCoroutineDispatcher
) : TestWatcher(), TestCoroutineScope by TestCoroutineScope(dispatcher) {

override fun starting(description: Description) {
super.starting(description)
}

override fun finished(description: Description) {
super.finished(description)
cleanupTestCoroutines()
}
}

Özet ve Sonuç

Bu makalede, kullanıcı bilgileriyle oynayarak unit testler yazmayı öğrendik. Kullanıcı bilgilerini güncelleme, yetişkinlik durumu kontrolü ve hata durumlarını test ettik. Ayrıca, LoginViewModel’e eklediğimiz coroutine fonksiyonunun doğru çalıştığını test ettik. Unit testlerin gücünü ve kapsamlı assert durumları kullanarak test yazmanın önemini vurguladık. Bir sonraki makalede, en iyi uygulamalar ve ipuçları ile test yazma sürecini daha da iyileştireceğiz. Takipte kalın!

Gelecek Makaleler

1. Android UI Testlerine Giriş 📱
2. Espresso ile İleri Düzey UI Testleri 📈
Önceki Makale -> UI Automator ile Gelişmiş UI Testleri 🔍
Sonraki Makale -> 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 unit 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! 🚀

--

--