MVVM vs The Composable Architecture (TCA)
Classic MVVM versus Point-Free's TCA: state management, testability, complexity tradeoffs, and which architecture fits your iOS app scale.
Hız, izolasyon ve güvenilir geri bildirim döngüsü
Gerçek bileşen etkileşimi, uçtan uca doğrulama
// 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
}
}// 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)
}
}Test piramidi: çok sayıda unit test (%70), az sayıda integration test (%20), minimum UI/E2E test (%10). Unit testler hızlı geri bildirim için, integration testler kritik akışları doğrulamak için birlikte kullanın. Birini diğerinin yerine koymak değil, tamamlayıcı olarak kullanmak doğru strateji.
Ücretsiz Danışmanlık AlBu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.
Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.
Sihirli bir rakam yok. %80+ iş mantığı coverage'ı iyi bir hedef. Ancak coverage yüzdesi değil, doğru şeyleri test etmek önemli. %100 coverage'a körü körüne ulaşmaya çalışmak zararlı.