---
title: 'Tümleşim Testleri'
source: 'https://academia.sh/tr/kurslar/java-ekosistem/tumlesim-testleri'
course: 'Derleme, Test ve Ekosistem'
language: tr
updated: '2026-08-23T14:25:06+00:00'
license: 'CC BY-SA 4.0'
---

# Tümleşim Testleri

Birim kurulumunda sahtelenen tedarikçi burada gerçek gerçekleştirimiyle, gerçek gerçekleştirimin bağlı olduğu ikinci bir tiple birlikte koşum sınıf yoluna geri konuyor; yolun içerdiği tip sayısı bir tipten ikiye çıkıyor. Aynı test gövdesi iki kurulumda koşturuluyor: birimde sınır aşımı sessizce geçiyor, tümleşimde aynı istek koşum hatası veriyor.

Önceki derste paylaşılan olan, bir test gövdesinin kendi kurduğu bir nesneydi; sınanan kodun
kendisi hiç değişmedi. Bu ders paylaşılan durumu sınanan kodun **dışına** taşıyor: ikinci
derste sahtelenen `Tedarikci` bağımlılığı burada gerçek gerçekleştirimiyle geri geliyor, ve
gerçek gerçekleştirimin kendi bağlı olduğu ikinci bir tip de sınıf yoluna eklenmiş oluyor.
Soru şu: aynı test gövdesi, içeriği büyüyen bir sınıf yoluna karşı koşturulduğunda, önceden
sessiz kalan bir sapma nereye düşüyor?

Dördüncü dersin ekseni, ikinci dersle aynı `Tedarikci` adı üzerinden ilerlese de farklı bir
soruya yanıt arıyor. İkinci ders "aynı ad, iki gövde" sorusunu sormuştu ve bir tipin
diğerinin yerine geçmesinin ne zaman sessiz, ne zaman koşum hatası verdiğini ölçmüştü. Bu
ders o sorunun bir adım gerisine gidiyor: gerçekleştirimlerden biri, ötekinin taşımadığı
bir bağımlılık taşıdığında, sınıf yolunun kendisi nasıl değişiyor ve o değişiklik hangi
kusuru görünür kılıyor?

Tümleşim testinin konu alanı — test ortamı yönetimi, dış servislerin sahtelenmesi — M21/K03
Tümleşim, Sözleşme ve Uçtan Uca Test kursunda kuruldu ve burada yeniden kurulmaz. Bu dersin
eklediği tek soru, testin gördüğü sınıf yolunun **büyüklüğü** ile kusurun düştüğü yer
arasındaki ilişkidir.

Birim ve tümleşim kurulumları arasındaki fark çoğunlukla kurulum süresi ya da dış sistemlere
erişim üzerinden anlatılır — biri hızlı ve yalıtılmış, öteki yavaş ve gerçek. Bu ders aynı
farkı sınıf yolu üzerinden okuyor: iki kurulum arasındaki gerçek fark, testin **derleme
zamanında hiç görmediği** ama **koşum zamanında karşılaştığı** tip kümesidir. Kurulumu
"birim" ya da "tümleşim" yapan şey bir etiket değil, o koşumda sınıf yoluna hangi tiplerin
konduğudur.

## Aynı Bağımlılık, Gerçek Gerçekleştirimiyle

- **TS16.** Dış sistem burada `Envanter` adlı, gerçek dünyadaki bir stok sınırını
  modelleyen sıradan bir Java sınıfı olarak kurulur. Hiçbir ürün, sürücü ya da protokol adı
  yazılmaz; gerçek bir ağ çağrısı, dosya sistemine erişim ya da dış süreç de yapılmaz.
- **TS17.** "Yolun içerdiği tip sayısı" burada derlenmiş `.class` dosyalarının sayımıdır —
  ilk derste kurulan ölçüm yöntemiyle aynı.

İkinci derste `Tedarikci` adlı bir tip, istenen miktarın tamamını karşılayan bir üretim
gerçekleştirimiyle ve isteneni bir eksik karşılayan bir sahte gerçekleştirimle vardı; test
ikisine de derleme zamanında bağlı değildi, yalnız adı yansımayla arıyordu. Bu ders aynı adı
tekrar kullanıyor, ama gerçek gerçekleştirimi bu kez **yalnız başına** değil, kendi bağlı
olduğu bir sınır denetimiyle birlikte kuruyor: `Envanter.sinirDenetle`, istenen miktar
100'ü aştığında bir istisna fırlatıyor. Birim kurulumundaki sahte gerçekleştirim böyle bir
denetim taşımıyor — her isteği sessizce karşılıyor.

Bu iki gerçekleştirimin farkı bilinçli bir tasarım kararı. Birim kurulumu bir sınır
denetimi taşımıyor, çünkü birim testinin amacı `Tedarikci`'yi çağıran kodun akışını
sınamaktır — sınırın kendisi `Envanter`'in sorumluluğundadır ve `Envanter`'in kendi testi
ayrı yazılır. Tümleşim kurulumu ise iki tipi birlikte çalıştırarak, aralarındaki gerçek
etkileşimi sınıyor: `Tedarikci` çağrıyı `Envanter`'e gerçekten iletiyor mu, `Envanter`'in
fırlattığı istisna gerçekten çağıranı buluyor mu? Bu soruların ikisi de birim kurulumunda
sorulamaz, çünkü `Envanter` orada hiç yok.

```java
// Buyuyen.java — tumlesim kurulumu kosum sinif yoluna gercek tipleri geri koyar, yolun icerdigi tip sayisi buyur
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class Buyuyen {

    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, List<Path> kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessizCikti = new StringWriter();
        return derleyici.run(new PrintWriter(sessizCikti), new PrintWriter(sessizCikti),
                arg.toArray(new String[0])) == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) {
        URL[] url = new URL[sinifYolu.size()];
        try {
            for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
            try (URLClassLoader yukleyici = new URLClassLoader(url, Buyuyen.class.getClassLoader().getPlatformClassLoader())) {
                Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
                sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
                return "tamam";
            }
        } catch (InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }

    static long tipSayisi(Path dizin) throws IOException {
        try (var akis = Files.walk(dizin)) {
            return akis.filter(p -> p.toString().endsWith(".class")).count();
        }
    }

    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("buyuyen");
        Path kaynak = kok.resolve("kaynak");
        Path testD = kok.resolve("test"), birimD = kok.resolve("birim"), tumlesimD = kok.resolve("tumlesim");

        Path sTest = yaz(kaynak, "TumlesimTest",
                "import java.lang.reflect.InvocationTargetException;\n"
              + "import java.lang.reflect.Method;\n"
              + "public class TumlesimTest {\n"
              + "  public static void main(String[] a) throws Throwable {\n"
              + "    Class<?> c = Class.forName(\"Tedarikci\");\n"
              + "    Object nesne = c.getDeclaredConstructor().newInstance();\n"
              + "    Method m = c.getMethod(\"dolum\", int.class);\n"
              + "    int[] istekler = {30, 150};\n"
              + "    for (int istek : istekler) {\n"
              + "      try {\n"
              + "        int sonuc = (int) m.invoke(nesne, istek);\n"
              + "        System.out.println(\"istek \" + istek + \" -> sonuc \" + sonuc);\n"
              + "      } catch (InvocationTargetException e) {\n"
              + "        throw e.getCause();\n"
              + "      }\n"
              + "    }\n"
              + "    System.out.println(\"tum istekler tamamlandi\");\n"
              + "  }\n"
              + "}\n");

        Path sBirim = yaz(kaynak.resolve("birim"), "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(int istenen) { return istenen; }\n"
              + "}\n");

        Path envDizin = kaynak.resolve("tumlesim");
        Path sEnvanter = yaz(envDizin, "Envanter",
                "public class Envanter {\n"
              + "  static void sinirDenetle(int istenen) {\n"
              + "    if (istenen > 100) throw new IllegalStateException(\"stok siniri asildi\");\n"
              + "  }\n"
              + "}\n");
        Path sGercek = yaz(envDizin, "Tedarikci",
                "public class Tedarikci {\n"
              + "  public int dolum(int istenen) { Envanter.sinirDenetle(istenen); return istenen; }\n"
              + "}\n");

        derle(testD, List.of(), List.of(sTest));
        derle(birimD, List.of(), List.of(sBirim));
        derle(tumlesimD, List.of(), List.of(sEnvanter, sGercek));

        System.out.println("-- yolun icerdigi tip sayisi --");
        System.out.println("birim kurulumu    : " + tipSayisi(birimD) + " tip");
        System.out.println("tumlesim kurulumu : " + tipSayisi(tumlesimD) + " tip");

        System.out.println("-- ayni test govdesi, iki kurulum --");
        System.out.println("birim kosum sonucu   : " + kos(List.of(testD, birimD), "TumlesimTest"));
        System.out.println("tumlesim kosum sonucu: " + kos(List.of(testD, tumlesimD), "TumlesimTest"));
    }
}
```

```
-- yolun icerdigi tip sayisi --
birim kurulumu    : 1 tip
tumlesim kurulumu : 2 tip
-- ayni test govdesi, iki kurulum --
istek 30 -> sonuc 30
istek 150 -> sonuc 150
tum istekler tamamlandi
birim kosum sonucu   : tamam
istek 30 -> sonuc 30
tumlesim kosum sonucu: IllegalStateException
```

Birim kurulumu tek bir `.class` dosyası taşıyor: sahte `Tedarikci`, başka hiçbir şeye bağlı
değil. Tümleşim kurulumu iki `.class` dosyası taşıyor: gerçek `Tedarikci` ve onun çağırdığı
`Envanter`. `TumlesimTest` üçüncü kez de aynı dosya, aynı bayt kodu — değişen yalnız hangi
dizinlerin sınıf yoluna verildiği. Yolun içeriği büyüyor çünkü gerçek gerçekleştirim, sahte
gerçekleştirimin hiç taşımadığı bir bağımlılığı kendisiyle birlikte getiriyor.

Bu büyüme, birinci derste sayılan büyümeden farklı bir yönde ilerliyor. Orada test sınıf
yolu, üretim sınıf yolunun **hiç tanımadığı** iki tip taşıyordu — çerçeve ve testin kendisi,
ikisi de üretim kodunun dışındaydı. Burada büyüyen taraf üretim tarafı: `Envanter`, üretim
kodunun **gerçek** bir parçasıdır, ama birim kurulumunda hiç sınıf yoluna girmedi. Bir sınıf
yolunun büyümesi tek bir anlama gelmiyor — kimi zaman test kendi araçlarını taşıdığı için
büyüyor, kimi zaman üretimin gerçek bir parçasını geri kattığı için. İkisi de aynı sayma
yöntemiyle — derlenmiş `.class` dosyalarını saymak — ölçülüyor, ama işaret ettikleri şey
ayrı.

## Aynı Test Gövdesi, İki Kurulum, İki Kusur Sınıfı

- **TS18.** Aynı istek dizisi — `30` ve `150` — hem birim hem tümleşim kurulumuna karşı,
  test yeniden derlenmeden koşturulur.
- **TS19.** Testin kendisi burada bir iddia taşımıyor, yalnız çağrıları yapıp sonucu
  yazdırıyor. Bu, "sessiz" sonucu daha da keskinleştiriyor: bir iddia eklenseydi bile o
  iddia yalnız dönüş değerine bakardı, sınır ihlalinden habersiz kalırdı.
- **TS20.** Gerçek gerçekleştirimin attığı istisna işaretlenmemiş (unchecked) bir
  istisnadır. Yansımalı çağrı bunu `InvocationTargetException` içine sarar; test kaynağı
  bu sarmalayıcıyı açıp asıl nedeni yeniden fırlatır, aksi hâlde harness yalnız sarmalayıcı
  sınıfının adını görürdü.

İki istek de aynı sırayla gönderiliyor: önce 30, sonra 150. Birim kurulumunda ikisi de
sorunsuz dönüyor — sahte gerçekleştirim hiçbir sınır tanımıyor, "istek 150 -> sonuc 150"
satırı diğerleriyle aynı biçimde yazılıyor ve program "tum istekler tamamlandi" diyerek
bitiyor. Sınırın var olduğu bilgisi hiçbir yerde görünmüyor; test geçti, çünkü test zaten
sınırdan habersizdi.

Tümleşim kurulumunda ilk istek (30) aynı şekilde dönüyor, ama ikinci istek (150) `Envanter`
sınırını aşıyor ve `Tedarikci.dolum` bir istisna fırlatıyor. Bu istisna testin kendi
kaynağında yakalanıp yeniden fırlatılıyor, sonra koşumu üstlenen yardımcı bunu yakalayıp
adını raporluyor: `IllegalStateException`. Aynı iki istek, aynı sırayla, aynı test dosyası
— fark yalnız hangi `Tedarikci`'nin ve hangi ek bağımlılığın sınıf yolunda durduğu.

Bu istisnanın testin kendi kaynağından çıkışı iki katmanlıdır ve bu ders bilerek bu iki
katmanı ayırıyor. `Tedarikci.dolum` bir `IllegalStateException` fırlatıyor; test bunu
`m.invoke(nesne, istek)` çağrısıyla yansıma üzerinden tetiklediği için, yansımanın kendisi
bu istisnayı önce bir `InvocationTargetException`'ın içine sarıyor. Test kaynağı bu
sarmalayıcıyı `catch (InvocationTargetException e)` ile yakalayıp `throw e.getCause()`
diyerek asıl istisnayı açığa çıkarıyor ve yeniden fırlatıyor. Bu adım atlansaydı, koşumu
üstlenen yardımcı yalnız "InvocationTargetException" adını raporlardı — doğru ama az şey
söyleyen bir isim. Sarmalayıcının testin kendi kaynağında açılması, raporlanan adın
`IllegalStateException` olmasını, yani neyin yanlış gittiğinin bir adım daha görünür
kalmasını sağlıyor.

Bu iki sonuç, kursun sınırlayıcı okumasının test konusundaki karşılığı: gerçek tipi geri
koymak testi **daha doğru** yapmıyor, yalnız **başka bir kusur sınıfını** görünür kılıyor.
Birim kurulumu zaten geçerli bir kurulumdu — hızlı, yalıtılmış, gerçek bir sınırın varlığından
bağımsız. Tümleşim kurulumu onu yanlışlamıyor, üstüne bir katman ekliyor: gerçek
gerçekleştirimin taşıdığı kısıtları da sınıf yoluna dahil ediyor. Birim kurulumunda "sessiz"
kalan sapma — sınırı aşan bir isteğin fark edilmeden geçmesi — burada bir istisnaya, yani
"koşum" kusuruna dönüşüyor. İkisi de aynı iki isteği, aynı sırayla işledi; aralarındaki
fark testin yazdığı iddialarda değil, testin koştuğu sınıf yolunun içeriğindeydi. İki
kurulum da doğru kurulmuş olabilir; ayırdıkları şey aynı
girdiye verdikleri yanıt değil, o girdinin **hangi gerçekliğe** karşı sınandığıdır.

Bu ayrımın pratik sonucu, hangi kurulumun "yeterli" olduğuna dair tek bir yanıt olmadığıdır.
Birim kurulumu 150 gibi bir isteğin normal akışta işlenip işlenmediğini hızlıca doğrular;
`Envanter`'in kendisi değiştiğinde ya da hiç var olmadığında bile çalışmaya devam eder,
çünkü ona hiç bağlı değildir. Tümleşim kurulumu ise `Envanter`'in gerçekten çağrıldığını ve
gerçekten bir sınırı uyguladığını doğrular, ama bunun bedeli sınıf yoluna ikinci bir tipi
dahil etmek ve o tipin davranışına bağımlı hâle gelmektir. Hangi sorunun sorulduğu, hangi
kurulumun seçileceğini belirliyor; ikisi de aynı `Tedarikci` adını taşıyan farklı
gerçeklikleri sınıyor.

İki kurulumun birbirinin yerini almadığını, ikisinin de gerekli kaldığını vurgulamak
gerekiyor. Birim kurulumu hâlâ
değerli: `Envanter` gibi ağır ya da yavaş bir bağımlılık her koşumda gerçekten kurulsaydı,
`Tedarikci`'yi çağıran kodun kendi mantığını sınamak her seferinde o bağımlılığın kurulma
maliyetini de taşırdı. Birim kurulumu bu maliyeti tamamen ortadan kaldırıyor ve karşılığında
yalnız `Envanter`'in var olmadığı bir dünyayı sınıyor. Tümleşim kurulumu ise o maliyeti
göze alıp iki tipin birlikte nasıl davrandığını gerçekten koşturuyor. Bir test takımı
genellikle ikisini bir arada tutar: çok sayıda birim kurulumu her değişiklikte hızlıca
koşar, daha az sayıda tümleşim kurulumu ise gerçek bağımlılıkların bir arada çalıştığını
daha seyrek doğrular. Bu dersin gösterdiği, iki katmanın **aynı davranışı iki kez
sınamadığı** — her biri kendi göremediği bir kusur sınıfını taşıdığıdır.

Bu noktada sınıf yolu ölçüsü somut bir karar aracına dönüşüyor: bir kurulumun "birim" mi
"tümleşim" mi olduğunu tartışmak yerine, o kurulumun sınıf yoluna hangi tiplerin girdiğine
bakmak yeterli. `Envanter` sınıf yolundaysa, `Envanter`'in davranışı sınanıyor demektir;
sınıf yolunda değilse, sınanmıyor demektir — aradaki fark bir niyet beyanı değil, derlenmiş
bir gerçektir ve `tipSayisi` gibi bir sayımla doğrudan okunabilir.

Bu ölçü aynı zamanda bir uyarı taşıyor: sınıf yoluna eklenen her gerçek tip, testin
gördüğü dünyayı üretimin gördüğü dünyaya biraz daha yaklaştırıyor, ama hiçbir zaman
tamamen eşitlemiyor. `Envanter`'in kendisi de başka bir şeye bağlı olabilirdi — bu derste
öyle kurulmadı, sınırın kendisi kendi içinde tam. Gerçek bir sistemde tümleşim kurulumu da
kendi sınırında durur; bir noktadan sonra sınanan sistemin tamamını ayağa kaldırmak
tümleşim testinin değil, uçtan uca testin işidir, ve o sınır M21/K03'ün konusudur.

## Özet

- Birim kurulumu tek bir tip taşıdı, tümleşim kurulumu gerçek gerçekleştirimle birlikte
  onun bağlı olduğu ikinci bir tipi de taşıdı; yolun içeriği bire karşı ikiye çıktı.
- Aynı derlenmiş test gövdesi, aynı iki istekle, iki ayrı koşum sınıf yoluna karşı yeniden
  derlenmeden koşturuldu.
- Birim kurulumunda sınırı aşan istek sessizce geçti ve test "tamamlandı" dedi; hiçbir
  yerde bir uyarı çıkmadı.
- Tümleşim kurulumunda aynı istek `IllegalStateException` fırlattı; kusur artık sessiz
  değil, koşum hatasıydı.
- Gerçek gerçekleştirimi geri koymak testi daha doğru yapmadı, yalnız birim kurulumunda
  görünmeyen bir kusur sınıfını görünür kıldı.

## Sonraki Adım

Bu derste test bir sayı gönderdi ve dönen değeri okudu; iddia gövdenin içinde, düzyazı
olmayan bir biçimdeydi. Bazı testler ise gövdeye hiç düzyazı olmayan bir kodla değil,
senaryo dilinde yazılmış bir metinle yaklaşır — adımları sıradan cümlelerle anlatır ve o
cümlelerin bir karşılığı olmasını bekler. Bu, testin gördüğü sınıf yolu sorusunu bir kez
daha başka bir yöne taşıyor: artık iki kod tabanı arasındaki bir uyumsuzluk değil, düzyazı
bir metin ile o metnin çalıştırılabilir karşılığı arasındaki uyumsuzluk aranıyor. Sıradaki
ders böyle bir metni bir bildirim olarak ele alıyor ve sorusu şu: metindeki her adımın
çalıştırılabilir bir karşılığı var mı, ve bazı adımların bu karşılığı bulunamadığında
sessizce mi atlandığı.
