---
title: Sahteleme
source: 'https://academia.sh/tr/kurslar/android-uretim/sahteleme'
course: 'Test, Başarım ve Dağıtım'
language: tr
updated: '2026-10-21T05:02:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Sahte bağımlılık, sahte nesne, güdük, casus nesne ve kukla nesne kavramlarını görevlerine göre ayırır'
    - 'Casus nesnenin çağrı sayısını bütçeyle karşılaştırıp aşımı sınıflar'
    - 'Güdüğün ağ çağrısını sıfıra indirdiğini ve bunun gerçek ağ davranışını ölçmediğini açıklar'
    - 'Çağrı doğrulamasının sayıyı sabitleyen bir iddia olduğunu gösterir'
---

# Sahteleme

Sahteleme bir testte gerçek bağımlılığın yerine sahtesini koyar. Ders bir deponun her işlemde ağ kaynağına kaç kez gittiğini sayar ve sahte bağımlılığın neyi ölçtürüp neyi ölçtürmediğini gösterir.

Önceki derste ölçülen işlevler hiçbir dış kaynağa dokunmuyordu: girdi gelir, adım sayısı çıkardı. Gerçek bir depo ise bir ağ kaynağına gider ve birim testi o yolculuğu istemez. Test ağa çıkarsa yavaşlar, ağın durumuna bağlı kalır ve aynı girdiyle iki kez farklı sonuç verebilir. Çözüm bağımlılığı sahtesiyle değiştirmektir. Bu derste kursun sorusu aynı kalıyor: **bu ölçüm bütçesini kaç örnekte aşıyor?** Dört yanıt da aynı: içinde, azında, çoğunda, hepsinde. Ölçülen iş birimi bu derste bir ağ kaynağına giden çağrı sayısıdır.

Çekirdek önceki dersle aynıdır. `Budget` bir bütçenin adını ve sınırını taşır, `Overrun` bir ölçümün etiketini, bütçesini ve örnek listesini alır; sınırı kesin olarak aşan örnekleri sayıp aşımı seçer.

```kotlin
// Overrun.kt — bir olcumun butcesini kac ornekte astigini siniflandirir
class Budget(val name: String, val limit: Int)

class Overrun(val label: String, val budget: Budget, samples: List<Int>) {
    val total: Int = samples.size
    val over: Int = samples.count { it > budget.limit }
    val verdict: String = when {
        over == 0 -> "icinde"
        over == total -> "hepsinde"
        over * 2 < total -> "azinda"
        else -> "cogunda"
    }

    fun line() {
        println("$label: butce=${budget.name}<=${budget.limit} | asan=$over/$total -> $verdict")
    }
}

fun countOverruns(overruns: List<Overrun>) {
    for (name in listOf("icinde", "azinda", "cogunda", "hepsinde")) {
        println("$name: ${overruns.count { it.verdict == name }}")
    }
}
```

## Sahte Bağımlılığın Dört Türü

Test edilen sınıfın dışarıya bağlandığı her yere bağımlılık denir. Testte bu bağımlılığın yerine konan nesneye sahte bağımlılık (test double) deriz. Sahte bağımlılığın görevine göre dört türü vardır ve her biri ayrı bir adla anılır.

Kukla nesne (dummy) yalnız bir parametreyi doldurur; çağrılmaz, hiçbir yanıt vermez. Güdük (stub) önceden belirlenmiş bir yanıt verir: kime ne sorulursa sorulsun hazır yanıtı döner. Casus nesne (spy) çağrıları kaydeder ve sayar; gerçek davranışı sürdürebilir ya da hazır yanıt verebilir. Sahte nesne (mock) ise beklenen çağrıları baştan bilir ve beklenen çağrı gelmezse ya da fazla gelirse testi kırar. Bu dersin modelinde casus nesneyi ve güdüğü kuracağız; sahte nesneyi ise platform parçasında göreceğiz.

Kavramlar iç içe geçtiği için sınır koymak yararlıdır: güdük yanıtı belirler, casus nesne çağrıyı sayar. Güdük bir soruya "ne cevap verildi" yanıtını, casus nesne "kaç kez soruldu" yanıtını taşır. Bu derste ölçülen sayı ikincisidir: bir deponun ağ kaynağına kaç kez gittiği.

## İki Çağrı, Bir Bütçe

Bir sipariş deposu düşünelim. Depo bir siparişi okurken ağ kaynağına önce bir tazelik denetimi için, sonra asıl veriyi almak için olmak üzere iki kez gider. Bu davranışın bir hata olup olmadığını ürün kararı verir; model davranışı olduğu gibi alır ve sayar. Ağ kaynağı bir arayüzdür. Üretimde ağa giden bir sınıf onu uygular; testte ise çağrıları sayan bir casus nesne onun yerine geçer.

```kotlin
// Source.kt — ag kaynagi sozlesmesi, cagrilari sayan casus nesne ve hazir yanit veren gudukleyen onbellek
interface RemoteSource {
    fun fetch(key: String): String
}

class SpySource(private val replies: Map<String, String>) : RemoteSource {
    var requestCount: Int = 0
        private set

    override fun fetch(key: String): String {
        requestCount++
        return replies[key] ?: "yok"
    }
}

class CacheStub(private val entries: Map<String, String>) {
    fun lookup(key: String): String? = entries[key]
}
```

`SpySource` her `fetch` çağrısında `requestCount` sayacını bir artırır; sayaç dışarıdan okunur ama yalnız sınıfın içinde yazılır. `CacheStub` ise yalnız hazır yanıtlarını döndürür ve hiçbir çağrıyı saymaz. Şimdi depoyu yazalım.

```kotlin
// OrderRepository.kt — her islemde ag kaynagini iki kez cagirir: biri tazelik denetimi
class OrderRepository(
    private val source: RemoteSource,
    private val cache: CacheStub = CacheStub(emptyMap()),
) {
    fun load(key: String): String {
        val cached = cache.lookup(key)
        if (cached != null) return cached
        source.fetch(key)
        return source.fetch(key)
    }
}
```

Depo önce önbelleğe bakar. Önbellek bir yanıt verirse ağa gitmez; vermezse iki kez gider. Varsayılan önbellek boştur, yani hiç yanıt vermez. Ölçüm işlevi de bir yardımcı dosyaya konur: her işlem için ayrı bir casus nesne kurar, depoyu çalıştırır ve o işlemde sayılan çağrıyı döndürür.

```kotlin
// Probe.kt — her islem icin ag kaynagina giden cagri sayisini olcer
fun callsPerOperation(keys: List<String>, stub: CacheStub = CacheStub(emptyMap())): List<Int> =
    keys.map { key ->
        val spy = SpySource(mapOf(key to "agdan"))
        OrderRepository(spy, stub).load(key)
        spy.requestCount
    }
```

Bu işlev bir girdi listesinden bir sayı listesi üretir; örnekleri `Overrun`'a verecek olan dönüş budur. Sekiz sipariş okunacak ve bütçe işlem başına bir çağrıdır. Bu bütçe, ekranın bir siparişi açarken ağa bir kez gitmesini isteyen bir ürün kararının modeldeki karşılığıdır; platform ya da ağ sağlayıcısı böyle bir sınır koymaz.

**TA1.** Model, sahteleme kitaplığını koşturmaz; bu ortamda yoktur. Casus nesne ve güdük elle yazılmış sınıflardır, kitaplığın ürettiği nesneler değildir. Bütçe olan işlem başına 1 çağrı modelin girdisidir.

```kotlin
// Main.kt
fun main() {
    val keys = listOf(
        "siparis-1", "siparis-2", "siparis-3", "siparis-4",
        "siparis-5", "siparis-6", "siparis-7", "siparis-8",
    )
    val calls = callsPerOperation(keys)
    println("cagri sayilari: $calls")

    val overrun = Overrun("depo-cagrisi", Budget("cagri", 1), calls)
    overrun.line()
    countOverruns(listOf(overrun))
}
```

```text
cagri sayilari: [2, 2, 2, 2, 2, 2, 2, 2]
depo-cagrisi: butce=cagri<=1 | asan=8/8 -> hepsinde
icinde: 0
azinda: 0
cogunda: 0
hepsinde: 1
```

Sekiz işlemin sekizi de iki çağrı yaptı ve her biri bütçeyi bir çağrıyla aştı. Ölçüm hepsindedir. Hepsinde, işlevin bütçenin dışında olduğunu söyler: girdiyi değiştirmek sonucu değiştirmez, çünkü aşımın kaynağı bir girdinin özelliği değil deponun kendi düzenidir. Kural büyüklüğü görmez, burada fark bir çağrıdır; düzyazıda söyleriz ki bu bir çağrı bir ağ yolculuğuna eşittir.

## Çağrı Doğrulaması

Aynı sayıyı bir birim testi de sabitleyebilir. Casus nesne çağrıyı sayar ve test, sayacın beklenen değerde olduğunu doğrular. Sahte nesne kuran bir kitaplıkla bu şöyle yazılır.

```kotlin
// Platform kodu: OrderRepositoryTest
class OrderRepositoryTest {
    @Test
    fun loadAsksSourceOnce() {
        val source = mock<RemoteSource>()
        whenever(source.fetch("siparis-1")).thenReturn("tamam")

        OrderRepository(source).load("siparis-1")

        verify(source, times(1)).fetch("siparis-1")
    }
}
```

Parça iki iş yapar. `whenever` çağrısı bir güdük kurar: `fetch("siparis-1")` her sorulduğunda `"tamam"` döner. `verify` çağrısı bir çağrı doğrulamasıdır: kaynak tam bir kez sorulmuş olmalı. Depo kaynağı iki kez sorduğu için bu test kırılır. Kitaplığın ürettiği sahte nesne, modeldeki `SpySource` ve `requestCount`'ın platform karşılığıdır, ama aynı şey değildir. Model, bu ölçümün kuralını kurar; model aracın kendisi değildir. Testin kırılması bir aşımdır: beklenen sayı bir, gerçek sayı iki.

Kuralı bir iddiayla karşılaştırırken bir fark var. İddia tek bir işlem için kırılır ya da kırılmaz; kural ise sekiz işlemin kaçında kırıldığını söyler. İddia hata verir, kural yaygınlığı verir. Bir testte hata görüp onu ölçmek istemeyen biri aynı testi sekiz girdide koşar ve aşımı okur.

## Kitaplığın Satırları Ne Yapar

Platform parçasındaki beş satırın her biri ayrı bir iş yapar ve hangisinin hangi sahte bağımlılık türüne karşılık geldiğini bilmek, testin neyi sınadığını anlamanın yoludur.

`mock<RemoteSource>()` arayüzün çalışma anında üretilmiş sahte bir uygulamasını kurar. Bu nesnede hiçbir yöntem gerçek iş yapmaz; her çağrı karşılıksız döner ve kaydedilir. Modelde bunun yerini elle yazdığımız `SpySource` tutar, ama onun gibi sayaç değil, bir kayıt defteri taşır.

`whenever(source.fetch("siparis-1")).thenReturn("tamam")` satırı güdük kurar. İlk yarısı hangi çağrıdan söz edildiğini belirtir, ikinci yarısı o çağrıya hangi yanıtın verileceğini. Güdük bir kez kurulunca aynı çağrı kaç kez gelirse gelsin aynı yanıtı döner.

`OrderRepository(source).load("siparis-1")` test edilen sınıfın kendisidir. Sahte nesne kurucuya bağımlılık olarak verilir; depo bunun sahte olduğunu bilmez. Bağımlılığın dışarıdan verilmesi, yani kurucudan enjekte edilmesi, sahteleme için gerekli koşuldur. Depo kaynağı kendi içinde kursaydı testin araya girecek yeri olmazdı.

`verify(source, times(1)).fetch("siparis-1")` doğrulama satırıdır. Kitaplık kayıt defterine bakar ve `fetch("siparis-1")` çağrısının tam bir kez yapılıp yapılmadığını denetler. `times(1)` yerine başka bir sayı yazılsaydı beklenti de değişirdi. Bu satırın başarısızlığı, depodaki iki çağrının doğrudan sonucudur.

## Sahte Nesnenin Sınırı

Çağrı doğrulaması güçlü bir araçtır ve tam bu yüzden dikkat ister. Test, deponun kaynağı hangi sırayla ve kaç kez çağırdığını sabitlediğinde davranışı değil uygulamayı sabitlemiş olur. Depo bir gün tazelik denetimini bırakır ve çağrı sayısını bire indirirse, davranış değişmemiştir ama iki çağrı bekleyen bir test kırılır. Test ekibi bu kırılmayı bir hata sanıp testi onarmak zorunda kalır.

Bu derste bütçeyi bire koymamızın nedeni tersine bir değişikliğe dayanır: ürün kararı bir çağrıyı istiyor ve test o kararı sabitliyor. Fark kararın kimden geldiğindedir. Bir bütçe ürün kararına dayanıyorsa aşımı bir bulgudur; yalnızca mevcut uygulamayı kopyalıyorsa aşımı bir gürültüdür. Ölçüm ikisini birbirinden ayıramaz, ayıran ekiptir.

## Hangi Bağımlılık Hangi Türle Sahtelenir

Sahte bağımlılığın türünü seçerken sorulacak soru, testin o bağımlılıktan ne beklediğidir. Test yalnız bir yanıta ihtiyaç duyuyorsa güdük yeter: önbellek bir değer döndürsün, depo da o değeri iletsin. Test bir çağrının yapılıp yapılmadığını sınamak istiyorsa casus nesne ya da sahte nesne gerekir: ağa gidildi mi, kaç kez gidildi. Bağımlılık yalnız kurucunun imzasını doldurmak için varsa kukla nesne yeter ve hiçbir davranış yazılmaz.

Seçim yanlış yapılırsa test ya gereksiz yere karmaşıklaşır ya da yetersiz kalır. Çağrı sayısını sınamak isteyen bir test güdük kullanırsa sayıyı göremez; yalnız yanıt arayan bir test sahte nesne kurarsa, ilgisiz çağrıların doğrulamasıyla kırılır. Bu derste iki tür kullandık: ağ kaynağı çağrıyı saymak için casus nesneyle, önbellek yanıt vermek için güdükle sahtelendi. Her bağımlılık kendi görevine uygun türle sahtelendi.

## Yaygın Yanılgılar

İlk yanılgı, "her bağımlılık sahtelenmelidir" düşüncesidir. Sahteleme bağımlılığı gerçek davranıştan koparır. Bağımlılık hızlı, deterministik ve yan etkisizse, örneğin saf bir hesap işlevi, onu sahtelemek testi yalnız uzatır ve gerçek davranışı sınamaktan alıkoyar.

İkinci yanılgı, "sahte nesne gerçek nesneden hızlıdır, o hâlde daima iyidir" düşüncesidir. Sahte nesne hızlıdır, ama sınadığı şey sahte nesnenin davranışıdır. Güdüğün eski bir yanıt verdiği ölçümde bunu gördük: çağrı sayısı sıfırdı, yanıt yanlıştı.

Üçüncü yanılgı, "çağrı sayısı başarımın ölçüsüdür" düşüncesidir. Çağrı sayısı bir ağ yolculuğunun sayısıdır; her yolculuğun süresi aynı değildir. İki çağrı bir bütçeyi aşabilir ve toplam süre yine de kısa kalabilir. Sayı bu derste bir iş birimi olarak kullanılıyor, süre olarak değil.

## Sınırlayıcı Ölçüm

Bu ölçümün sınırlayıcısı güdüklü önbellektir. Gerekçesi şudur: ilk ölçümün hepsinde çıkması aşımın girdiden değil düzenden geldiğini gösterdi. Bunu doğrulamanın yolu düzeni değiştirip aynı girdiyi yeniden ölçmektir. Girdi, bütçe ve örnek sayısı sabit kalır; yalnız önbellek değişir. Böylece sonuçtaki fark başka bir etkene yüklenemez.

Şimdi aynı sekiz işlemi bir güdükle okuyalım. Güdük önbellek her anahtar için hazır bir yanıt verir. Depo önbellekte yanıt bulunca ağa gitmez.

```kotlin
// Main.kt — gudukleyen onbellekle ag cagrisi sifira iner
fun main() {
    val keys = listOf(
        "siparis-1", "siparis-2", "siparis-3", "siparis-4",
        "siparis-5", "siparis-6", "siparis-7", "siparis-8",
    )
    val stub = CacheStub(keys.associateWith { "onbellekten" })
    val calls = callsPerOperation(keys, stub)
    println("cagri sayilari: $calls")

    val overrun = Overrun("gudukle-depo", Budget("cagri", 1), calls)
    overrun.line()
    countOverruns(listOf(overrun))
}
```

```text
cagri sayilari: [0, 0, 0, 0, 0, 0, 0, 0]
gudukle-depo: butce=cagri<=1 | asan=0/8 -> icinde
icinde: 1
azinda: 0
cogunda: 0
hepsinde: 0
```

Aynı depo, aynı sekiz anahtar, aynı bütçe. Ağ çağrısı sekizde de sıfıra indi ve ölçüm hepsindeden içinde yönüne geçti. Burada deponun davranışı değişmedi: depo hâlâ önbellek yanıt vermezse iki kez gider. Değişen, testin deponun o yoluna hiç girmemesidir.

Bu, sahteleme ölçümünün sınırıdır. Güdük yanıtı belirler, yani test gerçek ağın değil güdüğün yanıtını ölçer. Sıfır çağrı ağın hızlı olduğunu ya da deponun iyi çalıştığını söylemez; yalnız güdüğün her soruya cevap verdiğini söyler. Bunu bir ölçümle görelim: güdüğe eski bir değer koyalım, ağın ise yeni bir değer verdiğini bilelim.

```kotlin
// Main.kt — gudukle ag arasindaki fark kuralin gormedigi seydir
fun main() {
    val keys = listOf("siparis-1", "siparis-2", "siparis-3", "siparis-4")
    val stale = CacheStub(keys.associateWith { "eski" })
    val fresh = keys.associateWith { "yeni" }

    val fromStub = keys.map { OrderRepository(SpySource(fresh), stale).load(it) }
    val fromNetwork = keys.map { OrderRepository(SpySource(fresh)).load(it) }
    println("gudukten gelen yanitlar: $fromStub")
    println("ag yanitlari: $fromNetwork")

    val calls = callsPerOperation(keys, stale)
    println("cagri sayilari: $calls")
    val overrun = Overrun("gudukle-bayat", Budget("cagri", 1), calls)
    overrun.line()
    countOverruns(listOf(overrun))
}
```

```text
gudukten gelen yanitlar: [eski, eski, eski, eski]
ag yanitlari: [yeni, yeni, yeni, yeni]
cagri sayilari: [0, 0, 0, 0]
gudukle-bayat: butce=cagri<=1 | asan=0/4 -> icinde
icinde: 1
azinda: 0
cogunda: 0
hepsinde: 0
```

Güdük dört işlemde de `eski` döndürdü; ağ aynı anahtarlarda `yeni` söylüyordu. Çağrı sayısı yine sıfır, ölçüm yine içinde. Kural çağrıyı sayar, yanıtın doğru olup olmadığına bakmaz. Bu bir hata değil, kuralın amacıdır; ama güdükle yazılmış bir testin yeşil olması, gerçek ağın aynı yanıtı vereceği anlamına gelmez. Bunu kapatan şey başka bir test türüdür: gerçek bağımlılığa yaklaşan bir sonraki derste göreceğimiz aygıt üstü testler.

**TA2.** Model güdüğü bir eşleme olarak kurar; gerçek bir sahteleme kitaplığı güdüğe çağrı sırasına göre farklı yanıtlar da verebilir, modelde bu yok.

## Özet

- Bu dersin aşımı: hepsinde. Depo her işlemde ağ kaynağına iki kez gitti ve sekiz işlemin sekizi bir çağrılık bütçeyi aştı.
- Dersin ölçütü, bir işlemde ağ kaynağına giden çağrı sayısı: casus nesne sayar, güdük yanıtı belirler, kukla nesne yalnız parametreyi doldurur, sahte nesne beklenen çağrıyı doğrular.
- Güdüklenen önbellekle aynı sekiz işlemde çağrı sıfıra indi ve ölçüm hepsindeden içinde yönüne geçti; depo değişmedi, testin girdiği yol değişti.
- Güdük kendi yanıtını ölçtürür: ağ yeni değeri söylerken güdük eskiyi döndürdü ve çağrı sayısı yine sıfırdı.
- Çağrı doğrulaması bir işlem için kırılır ya da kırılmaz; kural aşımın kaç işlemde olduğunu söyler.

## Sonraki Adım

Bu derste ağ kaynağının yerine geçen nesneler elle yazılmış sınıflardı ve hiçbiri bir aygıtta koşmadı. Aygıt üstü testler bunu tersine çevirir: sahte bağımlılık yerine gerçek bir ekran ve gerçek bir sistem kullanır, ama her test o ekranı baştan kurar. Sıradaki ders, `Aygıt Üstü Testler`, bu kurulumun bütçeyi kaç testte aştığını ölçer.
