Communitygithub.com

swift-protocol-di-testing

프로토콜 기반 의존성 주입을 통해 Swift 코드를 테스트 가능하게 만듭니다 — 파일 시스템, 네트워크, 외부 API를 작은 프로토콜과 Swift Testing으로 모킹합니다. Swift 코드가 테스트를 필요로 하고 파일 시스템, 네트워크, 외부 API를 모킹해야 할 때 사용하세요.

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

이 스킬은 Swift 코드에서 파일 시스템, 네트워크, iCloud와 같은 외부 의존성을 추상화하는 프로토콜 기반 의존성 주입 패턴을 제공합니다. 주요 목적은 I/O 없이 결정론적 테스트를 가능하게 하는 것이며, 특히 Swift Testing 프레임워크와 함께 사용하기에 적합합니다. 작은 범위의 프로토콜(FileSystemProviding, FileAccessorProviding 등)을 정의하고, 프로덕션 구현(DefaultFileSystemProvider 등)과 모의 구현(MockFileAccessor 등)을 분리하여 실제 의존성 없이도 다양한 오류 상황을 테스트할 수 있습니다. 이 패턴은 Swift 동시성(actor, Sendable) 환경에서도 안전하게 사용할 수 있으며, 기본 매개변수를 통해 프로덕션 코드는 실제 구현을 사용하고 테스트 코드에서만 모의 객체를 주입하는 방식으로 동작합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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()
    }
}

모범 사례

  • 단일 책임: 각 프로토콜은 하나의 관심사만 처리해야 합니다. 여러 메서드를 가진 "신 프로토콜(god protocol)"을 만들지 마세요.
  • 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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬