Test Doubles, birim testlerde gerçek bağımlılıklar yerine kullanılan yedek nesnelerdir. Terim, Gerard Meszaros tarafından “xUnit Test Patterns” (2007) kitabında Mock, Stub, Fake, Spy ve Dummy için genel bir kavram olarak tanıtılmıştır. Martin Fowler (2024)'a göre, Test Doubles test edilen bileşeni ortamından izole ederek testlerin deterministik, hızlı ve harici hizmetlerden bağımsız olmasını sağlar.
Önemli Noktalar
Test Doubles otomotiv endüstrisinden (dublör) yazılım geliştirmeye aktarılmış bir terimdir. Bir dublörün tehlikeli bir sahnede aktörün yerini alması gibi, Test Double da bir test senaryosunda gerçek bileşenin yerini alır. Bu, gerçek bağımlılık kullanılamadığında, yavaş olduğunda, deterministik olmadığında veya yan etkileri olduğunda gereklidir.
Test Double kavramı beş belirli türü kapsar ve her biri kendi görevini çözer. Meszaros tipolojisi kanoniktir ve tüm modern test kılavuzlarında kullanılır. Türler arasındaki fark, kontrol ve doğrulama derecesinde yatar: basit parametre doldurmadan (Dummy) tam çağrı sırası doğrulamasına (Mock) kadar.
Test Doubles'ın ana amacı, test edilen modülün izole edilmesidir. Mobil geliştirmede, gerçek bağımlılıklar API sunucuları, veritabanları, dosya sistemi, cihaz sensörleri ve sistem hizmetlerini (LocationManager, Camera, Bluetooth) içerir. Bu bileşenleri doğrudan kullanmak testleri yavaş, kırılgan ve ortama bağımlı hale getirir. Google Testing Blog (2023)'e göre, iyi izole edilmiş birim testleri milisaniyeler içinde çalışırken, entegrasyon testleri saniyeler ve dakikalar içinde çalışır.
Gerard Meszaros sınıflandırması, davranış ve amaç bakımından farklılık gösteren beş Test Doubles türünü içerir. Aralarındaki farkı anlamak, yetkin birim testlerin temelidir.
Dummy, test edilen metoda iletilen ancak asla kullanılmayan bir nesnedir. Dummy yalnızca metot imzasını karşılamak için gereklidir. Kotlin'de bu genellikle null, emptyList() veya stub'lı bir nesnedir. Dummy hiçbir mantık içermemelidir — çağrılırsa test başarısız olmalıdır.
Fake, bir arayüzün basitleştirilmiş ancak çalışan bir uygulamasıdır. Mock ve Stub'un aksine, Fake gerçek iş mantığı içerir, ancak basitleştirilmiş biçimde. Klasik bir örnek, verileri bir veritabanı yerine HashMap'te saklayan InMemoryUserRepository'dir. Fake, gerçek altyapının yükü olmadan duruma bağlı mantığı test etmeniz gerektiğinde kullanılır.
| Tür | Amaç | Örnek |
|---|---|---|
| Dummy | Parametre doldurma | null, boş nesne |
| Fake | Çalışan basitleştirilmiş uygulama | InMemoryRepository |
| Stub | Sabit değer döndürme | when(api.getUser()).thenReturn(user) |
| Spy | Doğrulama için çağrıları kaydetme | verify(spy).save(user) |
| Mock | Etkileşimi doğrulama | verify(mock).sendEmail(email) |
Stub, belirli çağrılar için önceden tanımlanmış değerler döndürür. Stub çağrılıp çağrılmadığını kontrol etmez — yalnızca veri sağlar. Mockito'da Stub, when(method).thenReturn(value) ile oluşturulur. Stub, bir bağımlılığın belirli bir değer döndürmesi gerektiğinde ancak çağrının kendisinin önemli olmadığı testler için idealdir.
Spy, sonraki doğrulama için tüm çağrıları kaydeden gerçek bir nesnenin etrafındaki bir sarmalayıcıdır. Mock'un aksine, Spy çağrıları gerçek nesneye devreder ancak gerçekleştiklerini doğrulamaya izin verir. Mockito'da Spy, spy(realObject) ile oluşturulur. Spy, gerçek bir nesne kullanmak ancak bazı çağrıları doğrulamak istediğinizde kısmi mocking için kullanışlıdır.
Mock, önceden tanımlanmış çağrı beklentilerine sahip bir nesnedir. Mock, belirli metotların belirli argümanlarla ve belirli bir sırayla çağrıldığını doğrular. Stub'un aksine Mock, veri döndürmekten ziyade davranış doğrulamasına odaklanır. Mock, mobil geliştirmede en güçlü ve en sık kullanılan Test Double türüdür.
Mock ve Stub arasındaki fark, deneyimli geliştiriciler arasında bile sıklıkla karışıklığa neden olur. Temel fark amaçtadır: Stub durum doğrulaması (state verification) yapar, Mock davranış doğrulaması (behavior verification) yapar.
Stub şu soruyu yanıtlar: “kod doğru sonucu döndürdü mü?”. Mock şu soruyu yanıtlar: “kod doğru argümanlarla doğru metotları çağırdı mı?”. Mobil geliştirmede, Stub sonucun önemli olduğu durumlarda (ör., bir depodan veri) kullanılırken, Mock yan etkilerin önemli olduğu durumlarda (ör., e-posta gönderme, veritabanına yazma) kullanılır.
// Stub: durum doğrulaması
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: davranış doğrulaması
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
MockK kullanarak Kotlin'deki beş Test Doubles türünün tümüne dair pratik örnekler — Android projeleri için en popüler mocking kütüphanesi.
class InMemoryUserRepository : UserRepository {
private val store = mutableMapOf<String, User>()
override fun save(user: User) {
store[user.email] = user
}
override fun findByEmail(email: String): User? {
return store[email]
}
}
class RegisterUseCaseTest {
private val api = mockk<AuthApi>()
private val repo = spyk(InMemoryUserRepository())
private val useCase = RegisterUseCase(api, repo)
fun `register user successfully`() = runTest {
// Stub: sabit API yanıtı döndür
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: kullanıcının kaydedildiğini doğrula
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Context, Logger içinde kullanılmaz
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
Test Double türünün seçimi, tam olarak neyin test edildiğine bağlıdır: durum, davranış veya entegrasyon. Android ve iOS mobil geliştirmede aşağıdaki öneriler oluşturulmuştur.
ViewModel test edilirken, yan etki üreten bağımlılıklar (depolar, analitik, navigasyon) için Mock kullanın ve veri döndüren bağımlılıklar (API istemcileri, ContentProvider) için Stub kullanın. Bu, ViewModel'in hem başarı hem de hata senaryolarını doğru şekilde işlediğini doğrulamaya olanak tanır.
Repository düzeyinde, Fake (bellek içi veritabanı uygulamaları) ve Stub (sabit API yanıtları) tercih edin. Fake, SQLite kurmadan önbellek mantığını ve çevrimdışı modu test etmeye olanak tanır. Stub, çeşitli HTTP durumlarını simüle eder: 200, 404, 500, timeout.
Test Doubles'ın yanlış kullanımı, her yeniden düzenlemede bozulan kırılgan testlerin en yaygın nedenlerinden biridir.
En yaygın hata, her şeyi mocklamaktır. Bir testteki her bağımlılık Mock ile değiştirilirse, test gerçek davranışı doğrulamayı bırakır. Mock yalnızca dış bağımlılıklar için kullanılmalıdır (ağ, veritabanı, dosya sistemi, sistem hizmetleri). İç uygulama bileşenleri (Value Object, data class, basit yardımcı programlar) değiştirilmemelidir.
İkinci hata, beklentileri tanımlamadan Mock oluşturmaktır. Bir metot every / when olmadan çağrılırsa, Mock varsayılan bir değer döndürür (null, 0, false). Bu, Mock'un sessizce null döndürdüğü ve testin bunu doğru davranış olarak yorumladığı yanlış pozitif testlere yol açabilir.
Üçüncü hata, her Mock'un her çağrısını doğrulamaktır. Verify yalnızca iş mantığı açısından kritik öneme sahip çağrılar için kullanılmalıdır. Aşırı doğrulama testleri kırılgan hale getirir: üretim kodundaki çağrı sırasını değiştirmek, davranışı değiştirmeden testleri bozar.
Sıkça Sorulan Sorular
Stub veri döndürür ve durumu (ne döndürüldüğünü) doğrular, Mock ise davranışı bank (bank metotların çağrıldığını) doğrular. Stub = “X'i döndür”, Mock = “Y'nin Z argümanıyla çağrıldığını doğrula”. Gerçek testlerde, bir nesne genellikle aynı anda hem Stub hem de Mock olarak işlev görür.
Fake, duruma bağlı mantık test edilirken (önbellekleme, çevrimdışı mod, işlemler) Mock'tan tercih edilir. Fake (bellek içi uygulama), kırılgan verify çağrıları olmadan bu senaryoları test etmeye olanak tanır. Mock, veri göndermeyi doğrulamak için daha uygundur: analitik, push, e-posta.
Kotlin'deki Android projeleri için MockK önerilir. Ek yapılandırma gerektirmeden coroutine, suspend fonksiyonlar, sealed class ve extension fonksiyonları destekler. Java projeleri için Mockito standart olmaya devam etmektedir — kapsamlı dokümantasyona sahip en popüler kütüphane.
Kotlin Flow'yu test etmek için MockK ile birlikte Turbine kütüphanesini kullanın. Turbine, Flow emisyonlarını doğrulamayı basitleştirir: değerlerin sırasını, akış tamamlanmasını ve istisnaları doğrulayabilirsiniz. Flow için Stub flowOf(value) döndürür, Mock Flow'un toplandığını doğrular.
Evet, ancak API yanıtları düzeyinde, UI bileşenleri düzeyinde değil. MockWebServer (OkHttp) ve WireMock kütüphaneleri, UI testlerinde HTTP yanıtlarını simüle etmeye olanak tanır. UI bileşenlerinin kendileri (Compose, SwiftUI Views) değiştirilmemelidir — davranışları ekran görüntüsü testleri ve Espresso ile test edilir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun