Stub (taklit, test dublörü) — gerçek uygulama yerine metot çağrılarına önceden tanımlanmış yanıtlar döndüren bir test nesnesidir. Mobil geliştirmede, stub'lar test edilen modülü ağ isteklerinden, veritabanından ve dosya sisteminden ayırarak ortam kurulumu olmadan mantığın doğrulanmasını sağlar. Mock'tan farklı olarak, stub davranışı doğrulamaz — yalnızca veri sağlar. Ayrıntılar için Martin Fowler'ın test dublörleri hakkındaki makalesine bakın.
Önemli Noktalar
Stub, bir testte gerçek bir bağımlılığı değiştiren ve belirli çağrılar için önceden tanımlanmış değerler döndüren bir test dublörü nesnesidir. Terim, Gerard Meszaros (2007) tarafından «xUnit Test Patterns» kitabındaki sınıflandırmada tanıtılmıştır. Stub, test dublörleri kategorisine aittir — test sırasında gerçek bileşenleri değiştiren nesneler. Bir stub'ın temel amacı, test edilen birime öngörülebilir veriler sağlayarak dış sistemlerin belirsizliğini ortadan kaldırmaktır.
Çalışma Prensibi — test, yürütmeden önce stub'ı yapılandırır: «getUsers() metodu çağrıldığında, bu kullanıcı listesini döndür». Stub iş mantığı içermez, çağrı sırasını doğrulamaz ve çağrı geçmişini kaydetmez. Sadece gerçek bileşenin yerine geçer ve kendisine söyleneni döndürür. Android testi bağlamında bu şu anlama gelir: OkHttp istemcisi sunucuya gerçek bir istek göndermez, bunun yerine stub olarak yapılandırılmış MockWebServer'dan yanıt alır.
Ne Zaman Kullanılır — stub'lar UI katmanını (ViewModel, Presenter) ve iş mantığını (UseCase, Interactor) test etmek için idealdir. Belirli verilere (boş liste, sunucu 500 hatası, süresi dolmuş token) verilen tepkinin doğrulanması gerektiğinde kullanılır. Testin belirli bir giriş durumu gerektirdiği her durum stub'ın işidir. Her test senaryosu kendi stub yapılandırmasını alır, bu da testleri okunabilir ve öngörülebilir kılar.
Gerard Meszaros (2007) «xUnit Test Patterns» kitabında beş tür test dublörü belirlemiştir: dummy, stub, spy, mock, fake. Her tür kendi sorununu çözer. Dummy — iletilir ancak kullanılmaz. Stub — veri döndürür. Spy — çağrıları kaydeder. Mock — davranışı doğrular. Fake — basitleştirilmiş mantık içerir. Bu sınıflandırmayı anlamak, geliştiricinin her test senaryosu için doğru aracı seçmesine yardımcı olur.
Ağ istekleri — stub kullanımının en yaygın senaryosudur. Uygulama API'ye HTTP çağrıları yapar ve testin farklı yanıtlara (başarılı JSON, 401 hatası, zaman aşımı, boş dizi) verilen tepkiyi doğrulaması gerekir. Android'de MockWebServer (OkHttp) ve URLProtocol (iOS), gerçek sunucu bağlantısı olmadan önceden tanımlanmış HTTP yanıtları döndüren stub'lar olarak çalışır. Bu, testleri saniyelerden milisaniyelere hızlandırır.
Veritabanı — Room (Android) ve CoreData (iOS) bellek içi varyantlara sahiptir, ancak bunları kurmak yine de zaman alır. Depo yerine Stub, veritabanına dokunmadan önceden hazırlanmış Entity listeleri döndürür. Bu, özellikle sıralama, filtreleme veya veri dönüşümünün doğrulanması gereken ViewModel testi için etkilidir. Test, veri hacminden bağımsız olarak milisaniyeler içinde çalışır.
Sistem servisleri — LocationManager, SensorManager, SharedPreferences gerçek bir cihaz veya emülatör gerektirir. LocationProvider için Stub önceden tanımlanmış koordinatları, SensorManager için sabit ivmeölçer değerlerini döndürür. iOS'ta eşdeğeri, test temsilcisi uygulamasına sahip CLLocationManager'dır. Stub'lar olmadan, bu testler belirli koşullara sahip fiziksel bir cihaz gerektirir.
Dosya sistemi ve önbellek — görüntü yükleme, yanıt önbellekleme, yapılandırma dosyalarıyla çalışma — tüm bu işlemler disk durumuna bağlıdır. FileManager veya ImageCache için Stub, gerçek dosyaları okumadan başarı/hata döndürür. Bu, farklı geliştirici makinelerinde yol uyumsuzlukları veya erişim hakları nedeniyle oluşan yanlış test başarısızlıklarını ortadan kaldırır.
Sorumluluk ayrımı — üç tür test dublörü farklı görevleri çözer. Stub: «bana veri ver». Mock: «çağrıldığımı doğrula». Fake: «gerçek gibi çalışırım, sadece daha basit». Fark, test okunabilirliği için kritiktir: bir test stub gereken yerde mock kullanırsa, test edilen senaryoyla ilgisi olmayan verify çağrılarıyla aşırı yüklenir.
| Özellik | Stub | Mock | Fake |
|---|---|---|---|
| Amaç | Veri sağlamak | Etkileşimi doğrulamak | Basitleştirilmiş uygulama |
| Mantık | Yok | Yok | Var (ancak basitleştirilmiş) |
| Doğrulama | Yok | Var (verify) | Dolaylı (durum üzerinden) |
| Esneklik | Düşük — sabit yanıtlar | Orta | Yüksek — mantık uyum sağlar |
| Hız | Maksimum | Yüksek | Orta |
| Örnek | MockWebServer JSON döndürür | Mockito.verify(repository).save() | HashMap ile InMemoryRepository |
Pratik kural — test, test edilen bileşenin hangi verileri aldığını kontrol ediyorsa stub kullanın. Test, bileşenin bağımlılık metodunu doğru argümanlarla çağırıp çağırmadığını kontrol ediyorsa mock kullanın. Bir veritabanını basitçe bir karma tabloyla değiştirmek istiyorsanız — bu fake'tir. Türleri tek bir testte karıştırmak testi kırılgan yapar: uygulama değiştiğinde, hem stub hem de verify mantığını yeniden yazmanız gerekir.
Verify ile Stub — geliştiricinin bir stub yapılandırıp ardından verify(stub).method() eklemesi yaygın bir hatadır. Stub tanım gereği doğrulanmamalıdır — bunun için mock vardır. Bir metodun belirli argümanlarla çağrıldığını doğrulamanız gerekiyorsa, Mockito.stub() yerine Mockito.mock() kullanın. Bu ayrım, testin amacını diğer geliştiriciler için net tutar.
MockWebServer — Android ve JVM'de HTTP stub'ları oluşturmak için bir OkHttp kütüphanesi. Belirtilen bir bağlantı noktasında, OkHttp istemci isteklerini yakalayan ve önceden tanımlanmış yanıtlar döndüren yerel bir HTTP sunucusu başlatır. Kurulum üç satır sürer: sunucuyu oluştur, yanıtı sıraya ekle, başlat. Test, sayfalama veya yeniden deneme senaryoları için sırayla birden çok yanıtı sıraya ekleyebilir.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — coroutine'ler, genişletme fonksiyonları ve sealed sınıflar için birinci sınıf destek sunan Kotlin için bir Mockito alternatifidir. MockK'daki stub'lar coEvery (suspend fonksiyonlar için) ve every (normal fonksiyonlar için) aracılığıyla oluşturulur. MockWebServer'dan farklı olarak MockK, tüm HTTP katmanı yerine tek tek bağımlılık metotlarını stub'lar. Bu, bağımlılıkların depo soyutlamaları olduğu UseCase veya Interactor birim testleri için kullanışlıdır.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
En iyi uygulama — entegrasyon testleri için MockWebServer (gerçek HTTP'yi yakalar), birim testleri için MockK (arabirimleri stub'lar) kullanın. Test etmediğiniz şeyi stub'lamayın: test Repository'yi doğruluyorsa, içindeki OkHttp istemcisini stub'lamayın — HTTP düzeyinde gerçek bir MockWebServer kullanın. Bu kural testleri alakalı tutar ve yeniden düzenleme sırasında kırılganlığı azaltır.
Swift protokolleri stub olarak — iOS yerel yaklaşımında stub, bağımlılık protokolüne uyan bir test yapısının değiştirilmesiyle uygulanır. Gerçek NetworkService yerine test, sabit veriler döndüren bir StubNetworkService alır. Swift statik tipli bir dildir, bu nedenle stub gerçek hizmetle aynı protokole uymalıdır. Derleyici, stub'ın gerekli tüm metotları uygulamasını garanti eder.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
Objective-C için OCMock — eski iOS projelerinde stub ve mock oluşturmak için bir kütüphane. OCMock, argümanlar ve dönüş değerleriyle stub metotlarını destekler. Modern Swift projeleri, manuel stub'larla protokol tabanlı bir yaklaşımı tercih eder — bu, her metot üzerinde kontrol sağlar ve harici bağımlılık gerektirmez. OCMock, tüm bağımlılıkları protokole dönüştürmenin ekonomik olarak uygun olmadığı projeler için bir seçenek olarak kalır.
HTTP stub'ları için URLProtocol — URLProtocol'un bir alt sınıfı aracılığıyla ağ isteklerini yakalamak için iOS'ta bir sistem mekanizması. Test, URLSession'ı yakalayan ve stub yanıtları döndüren özel bir URLProtocol kaydeder. Manuel stub'lara göre avantajı: uygulama mimarisini değiştirmeniz gerekmez — URLSession gerçek kalır, ancak veriler protokol düzeyinde değiştirilir. Dezavantajı: açık bir stub hizmetinden daha zor hata ayıklanır.
Sıkça Sorulan Sorular
Stub önceden tanımlanmış verileri döndürür ve bir çağrının gerçekleşip gerçekleşmediğini kontrol etmez. Mock ayrıca metodun doğru argümanlarla çağrıldığını doğrular (verify). Stub «ne döndüreceğim» sorusuna yanıt verir, Mock «çağrı yapıldı mı» sorusuna. Durum doğrulaması için stub, etkileşim doğrulaması için mock kullanın.
Fake, testin çalışan (basitleştirilmiş de olsa) bir uygulama gerektirdiğinde kullanılır — örneğin, Room yerine bellek içi bir veritabanı. Stub, önceden tanımlanmış verilerle tek senaryolar için uygundur. Aynı stub'ı 10 testte tekrarlıyorsanız — muhtemelen bir Fake'e ihtiyacınız var. Fake, mantık tek bir sınıfta yaşadığı için tekrarı azaltır.
Android'de — MockK, Kotlin nesneleri (object) için mockkObject()'i destekler ve mockkStatic() aracılığıyla Java sınıflarının statik metotlarını içerir. iOS'ta — Swift statik metotları doğrudan stub'lanamaz; static çağrıyı bir protokolün örnek metoduyla değiştirmek için protokoller ve DI kullanın. Statik stub'lar teknik borçtur ve yeni kodlarda kaçınılmalıdır.
MockWebServer (OkHttp) kullanın — yanıtları sıraya koyan yerel bir HTTP sunucusu gibi çalışır. Retrofit için, temel URL'yi localhost:8080 ile değiştirmeniz yeterlidir. Ktor için MockEngine kullanın — HttpStatement'i değiştirmek için yerleşik bir mekanizma. Her iki yaklaşım da gerçek internet olmadan çalışır ve durum kodu, gövde ve yanıt başlıkları üzerinde tam kontrol sağlar.
Spy gerçek bir nesneyi sarar ve çağrıları kaydederken, Stub nesneyi sabit yanıtlarla tamamen değiştirir. Spy, gerçek uygulamanın kısmi kullanımına izin verir (diğer metotlar olduğu gibi çalışır), stub ise izin vermez. Bir metodun çağrıldığını doğrulamanız ancak mantığın bir kısmının yürütülmesi gerekiyorsa — stub değil, spy kullanın.
Ö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