Unit Test vs Integration Test Vergleich

Geschwindigkeit, Isolation und zuverlässiger Feedback-Loop

VS
Integration Test

Reales Zusammenspiel von Komponenten, End-to-End-Verifikation

8 Min. LesezeitiOS

Punktevergleich

Diagramm wird geladen...

Detaillierte Bewertung

Detaillierte Bewertung: Unit Test und Integration Test — Bewertungen pro Kategorie auf einer Skala von 1 bis 10
KategorieUnit TestIntegration Test
Performance
10/10
4/10
Erlernbarkeit
8/10
6/10
Ökosystem
9/10
8/10
Community
9/10
8/10
Arbeitsmarkt
9/10
8/10
Zukunftssicherheit
9/10
9/10

Vor- und Nachteile

Unit Test

Vorteile

  • Extrem schnell – läuft in Millisekunden, in der CI in Sekunden
  • Isoliert – externe Abhängigkeiten per Mock/Stub unter Kontrolle
  • Zuverlässig – minimales Flaky-Test-Risiko (kein Netzwerk, keine DB)
  • Regressionserkennung – erfasst kleine Codeänderungen sofort
  • Dokumentation – gut geschriebene Unit-Tests zeigen, wie der Code zu nutzen ist
  • TDD steigert die Design-Qualität
  • Parallel ausführbar – nutzt Mehrkernprozessoren effizient

Nachteile

  • Mocks bilden die Realität möglicherweise nicht exakt ab – Risiko falscher Positiv-/Negativergebnisse
  • Erkennt keine Integrationsprobleme – oft das Szenario 'Unit-Tests grün, Produktion kaputt'
  • Gefahr des Over-Mocking – Tests können zu stark an die Implementierung gebunden sein
  • Testen privater Methoden kann Design-Änderungen erfordern
  • Zeigt nicht, wie unabhängige Module in der realen Welt zusammenpassen

Am besten geeignet für

Validierung der GeschäftslogikTests reiner Funktionen und AlgorithmenViewModel-/Reducer-State-ÜbergängeParser-, Formatter- und Validator-TestsBibliotheks- und Framework-Entwicklung

Integration Test

Vorteile

  • Verifiziert das tatsächliche Zusammenspiel von Komponenten – keine Mock-Verzerrung
  • Erkennt Integrationsfehler – testet, ob Units zusammen funktionieren
  • Ermöglicht echte Tests von Datenbank- und Netzwerkschichten
  • End-to-End-Validierung kritischer Abläufe
  • Refactoring-Sicherheit – erkennt Interface-Änderungen
  • Näher an realen Nutzerszenarien

Nachteile

  • Langsam – Netzwerk, Datenbank und Dateisystem laufen in Echtzeit
  • Risiko flakiger Tests – externe Dienste können unzuverlässig sein
  • Komplexität bei Setup und Teardown
  • Schwer, die Ursache fehlgeschlagener Tests zu finden
  • Parallele Ausführung kann State-Konflikte erzeugen
  • Zusätzliche Infrastruktur für nicht gemockte Umgebungen nötig

Am besten geeignet für

Datenbank-CRUD-Operationen (Core Data, SwiftData, SQLite)Validierung der API-Client-SchichtZusammenspiel mehrerer DiensteValidierung kritischer Pfade (Zahlung, Auth-Flow)Tests für Daten-Migration und -Transformation

Code-Vergleich

Unit Test
// Swift - Unit test örnekleri (XCTest + Swift Testing)
import Testing
import Foundation
@testable import MyApp

// Swift Testing framework (iOS 17+, WWDC 2024)
struct PriceFormatterTests {

    @Test("Türk lirası formatlaması doğru olmalı")
    func turkishLiraFormat() {
        let formatter = PriceFormatter(locale: Locale(identifier: "tr_TR"))
        #expect(formatter.format(1234.5) == "₺1.234,50")
        #expect(formatter.format(0) == "₺0,00")
        #expect(formatter.format(-50) == "-₺50,00")
    }

    @Test("Geçersiz fiyat negatif olmamalı",
          arguments: [-1.0, -100.0, -0.01])
    func negativePriceValidation(price: Double) {
        let validator = PriceValidator()
        #expect(!validator.isValid(price))
    }
}

struct CartViewModelTests {
    var sut: CartViewModel!
    var mockRepository: MockCartRepository!

    @Test("Ürün eklenince toplam fiyat güncellenmeli")
    mutating func addProductUpdatesTotalPrice() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)

        #expect(sut.totalPrice == 75000)
        #expect(sut.itemCount == 1)
    }

    @Test("Aynı ürün iki kez eklenince miktar artmalı")
    mutating func addSameProductIncreasesQuantity() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)
        await sut.addProduct(product)

        #expect(sut.items.count == 1)
        #expect(sut.items.first?.quantity == 2)
        #expect(sut.totalPrice == 150000)
    }
}

// Mock implementasyonu
class MockCartRepository: CartRepositoryProtocol {
    var savedItems: [CartItem] = []

    func save(_ item: CartItem) async throws {
        savedItems.append(item)
    }

    func fetchAll() async throws -> [CartItem] {
        return savedItems
    }
}
Integration Test
// Swift - Integration test örnekleri
import XCTest
@testable import MyApp

// In-memory SQLite ile integration test
class UserRepositoryIntegrationTests: XCTestCase {
    var repository: UserRepository!
    var database: TestDatabase!

    override func setUp() async throws {
        // Gerçek SQLite in-memory database kullanıyoruz
        database = try await TestDatabase.inMemory()
        repository = UserRepository(database: database)
    }

    override func tearDown() async throws {
        try await database.cleanup()
        database = nil
        repository = nil
    }

    func testCreateAndFetchUser() async throws {
        // Create
        let userId = try await repository.createUser(
            name: "Ahmet Yılmaz",
            email: "[email protected]"
        )

        // Fetch
        let fetchedUser = try await repository.fetchUser(id: userId)

        XCTAssertNotNil(fetchedUser)
        XCTAssertEqual(fetchedUser?.name, "Ahmet Yılmaz")
        XCTAssertEqual(fetchedUser?.email, "[email protected]")
    }

    func testDeleteUserCascadesToPosts() async throws {
        let userId = try await repository.createUser(name: "Test", email: "[email protected]")
        let postRepo = PostRepository(database: database)
        _ = try await postRepo.createPost(title: "Post 1", userId: userId)
        _ = try await postRepo.createPost(title: "Post 2", userId: userId)

        // Kullanıcıyı sil
        try await repository.deleteUser(id: userId)

        // Kullanıcının postları da silinmeli (cascade)
        let posts = try await postRepo.fetchPosts(userId: userId)
        XCTAssertTrue(posts.isEmpty, "Kullanıcı silinince postları da silinmeli")
    }
}

// Gerçek URLSession ile network integration test
class APIClientIntegrationTests: XCTestCase {
    func testFetchPublicAPIData() async throws {
        // Bu test gerçek ağ bağlantısı kullanıyor
        // CI'da sadece network bağlantılı ortamlarda çalıştırılmalı
        try XCTSkipUnless(ProcessInfo.processInfo.environment["INTEGRATION_TESTS"] == "1")

        let client = APIClient(baseURL: URL(string: "https://jsonplaceholder.typicode.com")!)
        let posts: [Post] = try await client.request(path: "/posts")
        XCTAssertFalse(posts.isEmpty)
        XCTAssertEqual(posts.count, 100)
    }
}

Fazit

Testpyramide: viele Unit-Tests (70%), wenige Integrationstests (20%), minimale UI-/E2E-Tests (10%). Unit-Tests liefern schnelles Feedback, Integrationstests verifizieren kritische Abläufe – am besten kombiniert einsetzen. Es geht nicht darum, das eine durch das andere zu ersetzen, sondern beide als sich ergänzende Strategie zu nutzen.

Kostenlose Beratung erhalten
FAQ

Häufig gestellte Fragen

Es gibt keine magische Zahl. 80%+ Coverage der Geschäftslogik ist ein gutes Ziel. Wichtig ist jedoch nicht der Prozentsatz, sondern die richtigen Dinge zu testen. Blind 100% Coverage anzustreben ist eher schädlich.

Verwandte Blogartikel

Alle Artikel ansehen

Verwandte Projekte

Alle Projekte ansehen
Alle Vergleiche