Communitygithub.com

swift-protocol-di-testing

Swift 코드를 테스트 가능하게 만드는 프로토콜 기반 의존성 주입 패턴 — 파일 시스템, 네트워크, 외부 API를 작고 집중된 프로토콜로 추상화하고 Swift Testing으로 모의 객체를 사용합니다. Swift 코드를 테스트해야 하고 파일 시스템, 네트워크 또는 외부 API를 모의 객체로 대체해야 할 때 사용하세요.

swift-protocol-di-testing란 무엇인가요?

이 기술은 Swift 코드에서 파일 시스템, 네트워크, iCloud와 같은 외부 의존성을 작고 집중된 프로토콜로 추상화하여 테스트 가능하게 만드는 패턴을 제공합니다. 프로덕션 코드에서는 기본 구현체를 사용하고, 테스트에서는 모의 객체를 주입하여 실제 I/O 없이도 결정론적인 테스트를 수행할 수 있습니다. Swift Testing 프레임워크와 함께 사용하여 오류 처리 경로, 경계 조건, 비동기 동작을 안정적으로 검증할 수 있습니다. 특히 Swift 동시성(actor, Sendable)과 결합하여 견고하고 테스트 가능한 아키텍처를 설계하는 데 핵심적인 역할을 합니다. SwiftUI 프리뷰, 앱, 테스트 타겟 등 다양한 환경에서 동일한 모듈을 재사용할 수 있게 해주며, 의존성 주입의 일반적인 안티 패턴과 모범 사례를 함께 제시합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/swift-protocol-di-testing

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

테스트를 위한 Swift 프로토콜 기반 의존성 주입

파일 시스템, 네트워크, iCloud와 같은 외부 의존성을 작고 집중된 프로토콜 뒤로 추상화하여 Swift 코드를 테스트 가능하게 만드는 패턴입니다. I/O 없이 결정론적인 테스트를 가능하게 합니다.

활성화 시점

  • 파일 시스템, 네트워크 또는 외부 API에 접근하는 Swift 코드를 작성할 때
  • 실제 실패를 트리거하지 않고 오류 처리 경로를 테스트해야 할 때
  • 여러 환경(앱, 테스트, SwiftUI 프리뷰)에서 작동하는 모듈을 구축할 때
  • Swift 동시성(actor, Sendable)으로 테스트 가능한 아키텍처를 설계할 때

핵심 패턴

1. 작고 집중된 프로토콜 정의

각 프로토콜은 정확히 하나의 외부 관심사를 처리합니다.

// 파일 시스템 접근
public protocol FileSystemProviding: Sendable {
    func containerURL(for purpose: Purpose) -> URL?
}

// 파일 읽기/쓰기 작업
public protocol FileAccessorProviding: Sendable {
    func read(from url: URL) throws -> Data
    func write(_ data: Data, to url: URL) throws
    func fileExists(at url: URL) -> Bool
}

// 북마크 저장 (예: 샌드박스 앱용)
public protocol BookmarkStorageProviding: Sendable {
    func saveBookmark(_ data: Data, for key: String) throws
    func loadBookmark(for key: String) throws -> Data?
}

2. 기본 (프로덕션) 구현체 생성

public struct DefaultFileSystemProvider: FileSystemProviding {
    public init() {}

    public func containerURL(for purpose: Purpose) -> URL? {
        FileManager.default.url(forUbiquityContainerIdentifier: nil)
    }
}

public struct DefaultFileAccessor: FileAccessorProviding {
    public init() {}

    public func read(from url: URL) throws -> Data {
        try Data(contentsOf: url)
    }

    public func write(_ data: Data, to url: URL) throws {
        try data.write(to: url, options: .atomic)
    }

    public func fileExists(at url: URL) -> Bool {
        FileManager.default.fileExists(atPath: url.path)
    }
}

3. 테스트용 모의 구현체 생성

public final class MockFileAccessor: FileAccessorProviding, @unchecked Sendable {
    public var files: [URL: Data] = [:]
    public var readError: Error?
    public var writeError: Error?

    public init() {}

    public func read(from url: URL) throws -> Data {
        if let error = readError { throw error }
        guard let data = files[url] else {
            throw CocoaError(.fileReadNoSuchFile)
        }
        return data
    }

    public func write(_ data: Data, to url: URL) throws {
        if let error = writeError { throw error }
        files[url] = data
    }

    public func fileExists(at url: URL) -> Bool {
        files[url] != nil
    }
}

4. 기본 매개변수로 의존성 주입

프로덕션 코드는 기본값을 사용하고, 테스트는 모의 객체를 주입합니다.

public actor SyncManager {
    private let fileSystem: FileSystemProviding
    private let fileAccessor: FileAccessorProviding

    public init(
        fileSystem: FileSystemProviding = DefaultFileSystemProvider(),
        fileAccessor: FileAccessorProviding = DefaultFileAccessor()
    ) {
        self.fileSystem = fileSystem
        self.fileAccessor = fileAccessor
    }

    public func sync() async throws {
        guard let containerURL = fileSystem.containerURL(for: .sync) else {
            throw SyncError.containerNotAvailable
        }
        let data = try fileAccessor.read(
            from: containerURL.appendingPathComponent("data.json")
        )
        // 데이터 처리...
    }
}

5. Swift Testing으로 테스트 작성

import Testing

@Test("동기화 관리자가 컨테이너 누락을 처리함")
func testMissingContainer() async {
    let mockFileSystem = MockFileSystemProvider(containerURL: nil)
    let manager = SyncManager(fileSystem: mockFileSystem)

    await #expect(throws: SyncError.containerNotAvailable) {
        try await manager.sync()
    }
}

@Test("동기화 관리자가 데이터를 올바르게 읽음")
func testReadData() async throws {
    let mockFileAccessor = MockFileAccessor()
    mockFileAccessor.files[testURL] = testData

    let manager = SyncManager(fileAccessor: mockFileAccessor)
    let result = try await manager.loadData()

    #expect(result == expectedData)
}

@Test("동기화 관리자가 읽기 오류를 적절히 처리함")
func testReadError() async {
    let mockFileAccessor = MockFileAccessor()
    mockFileAccessor.readError = CocoaError(.fileReadCorruptFile)

    let manager = SyncManager(fileAccessor: mockFileAccessor)

    await #expect(throws: SyncError.self) {
        try await manager.sync()
    }
}

모범 사례

  • 단일 책임: 각 프로토콜은 하나의 관심사만 처리해야 합니다 — 여러 메서드를 가진 '갓 프로토콜'을 만들지 마세요
  • Sendable 준수: 프로토콜이 actor 경계를 넘어 사용될 때 필요합니다
  • 기본 매개변수: 프로덕션 코드는 실제 구현체를 기본값으로 사용하게 하고, 테스트만 모의 객체를 지정하세요
  • 오류 시뮬레이션: 실패 경로 테스트를 위해 구성 가능한 오류 속성을 가진 모의 객체를 설계하세요
  • 경계만 모의 처리: 외부 의존성(파일 시스템, 네트워크, API)만 모의 처리하고 내부 타입은 모의 처리하지 마세요

피해야 할 안티 패턴

  • 모든 외부 접근을 포괄하는 단일 대규모 프로토콜 생성
  • 외부 의존성이 없는 내부 타입 모의 처리
  • 적절한 의존성 주입 대신 #if DEBUG 조건문 사용
  • actor와 함께 사용할 때 Sendable 준수 누락
  • 과도한 엔지니어링: 외부 의존성이 없는 타입에는 프로토콜이 필요하지 않습니다

사용 시기

  • 파일 시스템, 네트워크 또는 외부 API에 접근하는 모든 Swift 코드
  • 실제 환경에서 트리거하기 어려운 오류 처리 경로 테스트
  • 앱, 테스트, SwiftUI 프리뷰 컨텍스트에서 작동해야 하는 모듈 구축
  • 테스트 가능한 아키텍처가 필요한 Swift 동시성(actor, 구조적 동시성)을 사용하는 앱

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬