Skip to content

[Setting] AI가 틀렸을 때 즉시 실패하는 검증 체계 구축 (지도 이슈) #205

Description

@Naknakk

Issue Title

AI가 짠 코드를 사람 눈이 아니라 자동 검사로 걸러내는 체계를 만든다. AI를 더 잘 시키는 것보다, AI가 틀렸을 때 CI가 바로 빨간불이 뜨게 하는 게 목표다.

📌 이 이슈는 실제로 작업하는 이슈가 아니라 전체 계획을 모아둔 목차 이슈다.
실제 작업은 아래 항목마다 이슈를 따로 만들어서 하고, 그 번호를 여기에 링크로 채운다.


Issue Content

왜 하는가

이 레포는 문서·스킬·리뷰 에이전트로 규칙을 잘 세워놨다. 그런데 그 규칙을 마지막에 확인하는 게 거의 다
"읽는 사람"이거나 "자기가 한 일을 자기가 보고하는 AI"
다. AI가 "빌드 됩니다 / 테스트 통과했습니다"라고 하면,
그게 진짜인지 확인해주는 장치가 없다.

이게 괜한 걱정이 아니라는 증거가 이미 레포에 있다 (docs/BUILD_AND_TEST.md 함정 10):

build_sim이 내가 지정한 스킴을 무시하고 엉뚱한 스킴을 빌드해놓고 "성공"이라고 끝낸다.
그래서 내가 바꾼 코드는 컴파일조차 안 됐는데 통과한 줄 알고 넘어간다(#181에서 실제로 겪음).

그래서 하려는 것: AI 말만 믿어야 하는 지점을 하나씩 자동 검사로 바꾼다.
그러면 덤으로 리뷰 에이전트(wss-pr-reviewer·wss-feature-reviewer)가 자동으로 걸리는 건 안 봐도 되니까,
사람 판단이 필요한 것만 보게 된다. 리뷰가 더 빠르고 정확해진다.

지금 상태

이미 잘 되어 있는 것

  • Domain 테스트 문화가 자리잡음 — Swift Testing, 명세형 테스트 이름, Given-When-Then, Mock 호출 추적 (docs/TESTING.md)
  • ModuleType.swift가 모듈 목록을 한곳에서 관리함
  • XcodeBuildMCP가 .mcp.json으로 팀에 공유돼 있고, Demo 실행·snapshot_ui·tap 흐름이 함정까지 문서화돼 있음
  • NovelReviewViewModelTests가 좋은 예시임 — 비동기 순서 꼬임, 늦게 도착한 응답, 닫기 처리 같은
    "AI가 놓치기 쉬운 상태 변화"를 실제로 테스트하고 있음
  • docs/TODO.md 4번에 앱 교체(cutover) 체크리스트가 이미 자세히 있음 (Bundle ID·서명 팀·Kakao 키해시 등)

비어 있는 것

항목 상태
Swift 6 앱·모듈 어디에도 SWIFT_VERSION/SWIFT_STRICT_CONCURRENCY 설정이 하나도 없음. (Tuist/Package.swift가 swift-tools-version: 6.0이긴 한데, 그건 Tuist 설정용 패키지 얘기지 실제 앱 타깃과는 상관없음)
SwiftLint / SwiftFormat 없음
아키텍처 검사기 없음. 의존성 규칙을 어겨도 자동으로 막아주는 게 하나도 없음
CI .github/workflows/test.yml 하나뿐. PR에 /domain-test 댓글 달아야만 도는 수동 방식, Domain만 검사
CI가 안 하는 것 PR 자동 실행 ❌ · 전체 빌드 ❌ · lint ❌ · Data/Feature/UI 테스트 ❌
Feature 테스트 Tests 폴더 5개 중 4개가 빈 껍데기(3개는 15줄 placeholder, NovelDetail은 #expect(Bool(true)) 컴파일 확인용). 실물은 NovelReviewViewModelTests 하나
Mock Domain에 Mock~Repository는 16개 있는데 Mock~UseCase는 0개
tuist build (전체 빌드) 지금 항상 실패함 — Core/Logger/Demo/Demo.swift가 주석만 있는 빈 파일이라 LoggerDemo 앱에 @main이 없어서 빌드가 깨짐

진행 순서

막힌 것 먼저 뚫기 → PR 자동 CI → 아키텍처 검사기 → 개발 방식 바꾸기(계약 먼저) → SwiftLint → Swift 6 → QA 자동화 → V1 비교

축 A — 자동으로 막는 장치들

A0. 먼저 뚫어야 할 것 (이게 안 되면 아래가 시작이 안 됨)

  • Core/Logger의 Demo 빈 파일 채우기 — Demo/Demo.swift가 주석만 있어서 @main이 없고 빌드가 깨진다.
    그래서 tuist build(전체 빌드)가 여기서 멈추고, 그 뒤 모듈들은 검사도 못 한 채 넘어간다(BUILD_AND_TEST.md 함정 11).
    → 전체 빌드 CI(A1)를 하려면 이거부터 고쳐야 함.
  • 모듈 생성 함수에 testDependencies 넣기
    Tuist/ProjectDescriptionHelpers/Project+Templates.swift에서 testDependencies: []로 박혀 있어서,
    Feature 테스트가 Domain의 ~DomainTesting(Mock)을 못 가져다 쓴다.
    그래서 지금 NovelReviewViewModelTests가 SuspendedLoadNovelReviewDraftUseCase 같은 가짜를 테스트 파일 안에서 직접 만들어 쓰고 있다.
    (createDataModule·createCoreModule·createUIModule도 똑같이 막혀 있음)
  • Domain에 Mock~UseCase 만들기 — 지금은 Mock~Repository만 있음.
    Feature의 ViewModel은 UseCase를 주입받는데, 그걸 흉내 낼 Mock이 없다. Feature TDD(B2)를 하려면 필요함.
    Mock 만드는 방식은 docs/TESTING.md에 있는 그대로.
  • 모듈마다 빌드 설정을 넣을 수 있게 만들기 — 지금은 env.baseSetting이 비어 있어서 전 모듈이 Xcode 기본값을 씀.
    Swift 6를 모듈 하나씩 천천히 켜려면(A4) 모듈별로 설정을 넣을 자리가 있어야 하는데 지금은 없다.

A1. PR 올리면 자동으로 도는 CI — 빠른 검사 / 전체 검사 두 단계

  • PR을 올리면 자동으로 도는 빠른 검사 (지금은 댓글 달아야만 돌아감)
    → 아키텍처 검사 → lint → 이번에 바뀐 모듈만 빌드·테스트
  • 전체 검사 (수동으로 돌리거나 라벨 붙이면 실행) — 모든 모듈 빌드 + 모든 레이어 테스트
  • ⚠️ 모듈별로 나눠서 돌리고, 하나 실패해도 나머지는 계속 (fail-fast: false)
    — "상관없는 모듈 하나 때문에 전체가 멈추는" 함정을 이미 겪었다(a80bdb2). 어느 모듈이 깨졌는지 한 번에 다 보여야 함.
  • 테스트를 Domain 밖으로 넓히기 — Data / Feature / UI도 검사. 지금 폴더 스캔 방식을 레이어별로 일반화.
  • 바뀐 파일에 맞는 테스트만 골라서 돌리기 —
    Domain 바뀜 → 그 Domain 테스트 / Data 매퍼 바뀜 → Data 테스트 / Feature ViewModel 바뀜 → ViewModel 테스트 /
    UI 바뀜 → Demo QA / Project.swift·Plugins/ 바뀜 → 아키텍처 검사
  • 가끔 실패하는 테스트 잡아내는 검사 — xcodebuild -run-tests-until-failure -test-iterations N으로
    같은 테스트를 여러 번 돌려본다. 비동기·페이지네이션·낙관적 업데이트처럼 순서 꼬임이 있을 수 있는 테스트에 유용함.
  • /domain-test 댓글 방식을 계속 둘지 없앨지 결정 (자동 CI랑 겹침)

A2. 아키텍처 검사기 — 구조 규칙을 어기면 CI에서 실패시키기

SwiftLint랑 따로 만든다. SwiftLint는 코딩 스타일 잡는 도구라서, 여기 규칙들(어떤 걸 import하면 안 된다 같은
구조 판단)을 SwiftLint의 정규식으로 억지로 만들면 엉뚱한 걸 잡거나 진짜 위반을 놓친다.
대신 Tools/ArchitectureCheck에 SwiftSyntax 기반 작은 실행 프로그램으로 만들면,
앱이나 테스트에는 아무것도 안 붙어서 "외부 라이브러리 안 쓴다" 원칙과도 안 부딪힌다.
(다른 방법으로 Harmonize라는 게 있는데, 이건 규칙을 테스트처럼 짜는 방식 — 대신 테스트에 외부 라이브러리가 붙음. 뒤에서 다시 논의)

import 방향 검사

  • Feature의 Sources에서 import ~Data 금지 ← Tuist 설정으로는 못 막는 위반 (Demo는 Data를 써도 되니까 경로로 구분해야 함)
  • Domain의 Sources에서 import SwiftUI · Combine · ~Data · ~Feature 금지
  • Feature끼리 직접 의존 금지

ViewModel 규칙 검사 (지금은 Projects/Feature/CLAUDE.md에 글로만 적혀 있음)

  • ObservableObject · @Published · @StateObject 쓰면 실패
  • ViewModel은 반드시 @MainActor @Observable final class
  • 상태는 private(set) var state 하나로만 — 밖에서 못 바꾸게
  • 밖에서 들어오는 입력은 handle(_:) 하나로만

동시성 예외 문법 통제

  • @unchecked Sendable · @preconcurrency · Task.detached · nonisolated는 허용 목록에 없으면 실패
    (Swift 6 작업할 때 "일단 이거 붙여서 경고 끄자"가 퍼지는 걸 막으려는 것)

"통과 계층에 로직 금지" 검토 (계층 책임을 자동 검사로 — 다만 한계가 분명함)

  • Data의 Service처럼 통과만 해야 하는 계층에 분기(if/switch/?:)가 있으면 실패하는 규칙 검토
    - 실제 표본: DefaultFeedService.getMyFeeds가 visibilityType == "PUBLIC" ? true : nil로 요청 파라미터 매핑을
    Service에서 하고 있다
    (원래 Repository/FeedMapper 소관). enum을 문자열로 낮춰 넘겨서 이중 변환이 되고,
    switch 누락 검사도 컴파일러가 못 한다. Service 단위 테스트가 없어 어느 테스트도 이 변환을 안 지나감 → docs/TODO.md 8번.
    - ⚠️ 한계: "이 매핑이 올바른 계층인가"는 의미 판단이라 린터로 못 잡는다. 근본 예방은 타입을 좁히는 것
    (Service가 String 대신 완성된 Query를 받으면 변환할 거리가 없어짐 = leak이 문법적으로 불가능). "분기 금지"는
    그 아래 깔아두는 싼 그물이고, 남는 판단은 리뷰어(B4) 몫이다.

모듈 설정 자체를 검사 (ModuleType.swift 기준으로 — 폴더 훑는 방식 ❌)

  • ModuleType.swift에 등록된 모듈은 실제 폴더가 있어야 함

  • 등록 안 된 정식 모듈 폴더가 있으면 실패
    ← 지금 test.yml은 폴더를 훑는 방식이라, 쓰다 남은 빈 폴더(Projects/Domain/Novel·Comment·Feed 같은
    접미사 없는 잔재)까지 테스트 대상에 넣어버린다. docs/TESTING.md에 경고만 있고 막는 건 없음.

  • Domain 모듈은 .sources + .testing + .tests 타깃이 다 있어야 함

  • Domain 모듈은 Tests 폴더에 @Test가 최소 1개는 있어야 함

  • Feature 모듈은 .demo가 있어야 함

  • ViewModel이 있는 Feature인데 테스트가 빈 껍데기뿐이면 실패 (새 모듈부터 적용, 기존은 봐줌)

  • Project.swift의 의존성이 레이어 방향을 어기면 실패

  • 외부 라이브러리를 추가할 때 허용 목록을 안 고치면 실패 (CLAUDE.md 5번 "외부 라이브러리 안 쓴다" 원칙을 자동 검사로)

  • 의존성 그래프를 파일로 저장해두고 CI에서 변화를 비교 (지금 graph.dot/graph.png는 손으로 뽑은 거라 아무도 안 봄)

  • 검사기 자체도 테스트를 붙인다 — 규칙이 코드가 되면 그 규칙도 테스트할 수 있음

A3. SwiftLint / SwiftFormat — 스타일만

도입 판단: 이건 앱에 들어가는 라이브러리가 아니라 개발 도구라서 "외부 라이브러리 안 쓴다" 원칙과 무관하다고 봄.
.mise.toml로 버전을 고정해서 팀·CI가 같은 버전을 쓰게 함.

  • SwiftLint 설치(mise) + .swiftlint.yml + CI 잡
  • 규칙: 강제 언래핑(!) · 강제 try · 함수 복잡도 · 파일 길이 · 안 쓰는 import
  • 간단한 금지 패턴만 정규식으로: 색상 직접 입력(Color(red:) · .font(.system · print( · import XCTest
    (구조 판단이 필요한 규칙은 전부 A2로)
  • SwiftFormat 넣을지 말지 결정 (포맷 자동 통일 vs diff가 지저분해짐)
  • 기존 위반은 일단 봐주고 새 코드부터 실패시킴 — 도입하자마자 대규모 수정 안 하도록

A4. Swift 6 — 경고부터 모으고 천천히

애플 공식 가이드도 모듈 하나씩 천천히 켜기를 전제로 함. 한 번에 전부 켜면 위험함.

  • 1단계: CI에 SWIFT_STRICT_CONCURRENCY = complete 잡 추가 — Swift 5 상태 그대로, 경고만 모음
  • 2단계: Domain / Core부터 경고 없애기 (순수 데이터 타입이라 제일 쉬움)
  • 3단계: Feature ViewModel의 @MainActor 정리
  • 4단계: Data의 공유 상태 — actor / lock / Sendable 중 뭘 쓸지 정하기
  • 5단계: 예외 문법 허용 목록 강제 (A2랑 연결)
  • 6단계: 모듈 하나씩 SWIFT_VERSION = 6으로 올리기
  • SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor를 UI/Feature에 적용할지 검토 —
    ViewModel에 이미 @MainActor가 붙어 있어서 이걸 켜면 마이그레이션이 훨씬 수월함 (Xcode/Swift 버전별 설정 이름은 실제로 확인 필요)
  • Domain Entity를 Sendable로 맞추기 — 강제되면 오히려 좋음. 컴파일러가 값 전달을 검증해줌

축 B — 개발 방식을 "계약 먼저"로 바꾸기

B1. new-feature 스킬을 "계약 먼저"로 다시 짜기

지금 new-feature(148줄짜리 하나)는 View/ViewModel 뼈대를 먼저 만들고 나서 Figma·동작 확인으로 감.
이 순서를 바꿔서 뭘 만들지 먼저 정하고 코드는 나중에 쓰게 한다. 스킬도 단계별로 쪼개서 각 단계를 따로 실행할 수 있게 함.

0. V1 조사       (축 C — V1의 같은 화면이 어떻게 동작했는지 뽑고, 유지/개선/삭제 분류)
1. 화면 계약 작성 (화면 목적 · View가 넣는 입력 · 상태 · 액션 · 의존성 · 주요 시나리오 표)
2. 실패하는 테스트 먼저 작성 (상태 변화 · 비동기 순서 · 에러 처리 · 닫기/화면이동)
3. 구현          (테스트를 통과시키는 최소한만)
4. Demo 시나리오  (정상 / 빈 화면 / 에러 / 권한 / 페이지네이션)
5. XcodeBuildMCP로 QA (축 B3)
6. 리뷰어 통과
  • 단계별 스킬로 쪼개고 조합 방식 설계
  • "화면 계약" 문서 양식 정하기 — 기존 모듈 CLAUDE.md의 ## 화면 동작 계약이랑 겹치지 않게 관계 정리
  • TDD 전용 스킬 만들기 (계약 → 실패 테스트 → 구현 순서를 강제)
  • 새 Feature부터 적용, 기존은 나중에 손볼 때 따라오게 — 지금 있는 걸 전부 다시 만들진 않음

B2. Feature ViewModel TDD

이 레포에서 특히 잘 맞는 이유: Feature ViewModel이 이미 State / Action / handle(.액션) 구조라,
"액션 넣으면 상태가 이렇게 바뀐다"를 거의 함수처럼 테스트할 수 있음. 궁합이 이미 맞음.

  • docs/TESTING.md에 "Feature ViewModel 테스트" 부분 추가 (지금은 Domain만 다룸)
    NovelReviewViewModelTests를 기준 예시로 삼기 — 특히 비동기 순서 꼬임 테스트가 좋은 본보기임
  • ⚠️ "실패를 실제로 눈으로 봤다"를 규칙으로 못 박기.
    AI가 "테스트 통과"라는 말을 못 믿겠으면, 실패하는 것도 실제로 봐야 함.
    처음부터 통과하는 테스트는 검증이 아니라 그냥 자기 말 반복임. 실패 로그를 증거로 남긴다.
  • 빈 껍데기 테스트 4개 진짜로 채우기 — LibraryFeatureTests · HomeFeatureTests · NotificationFeatureTests · NovelDetailFeatureTests
  • 커버리지 몇 % 채우기보다 "중요한 경로에 테스트가 있는지"를 검사 —
    커버리지 목표는 의미 없는 테스트만 잔뜩 만드는 부작용이 있음.
    UseCase / Entity / ViewModel마다 핵심 동작 테스트가 있는지가 더 유용함
  • Data 레이어를 실제 응답 예시(fixture)로 검증 — JSON 디코딩 · DTO→Entity 변환 · 에러 변환 ·
    빈 값/필드 누락/서버 에러 케이스
  • @Observable 상태 변화를 중간중간 관찰하는 헬퍼(withObservationTracking 기반)가 필요한지 판단 — 지금은 최종 상태만 확인 가능

B3. XcodeBuildMCP QA 자동화

핵심은 도구가 아니라 "시나리오 파일"이다. 지금 docs/BUILD_AND_TEST.md는 도구 쓰는 법은 잘 적혀 있는데,
QA가 뭘 기준으로 통과/실패를 판단할지가 없음.

  • Feature마다 QA 시나리오 파일 만들기 — Projects/Feature/<Module>/QA/<Screen>QA.md
    - 실행 스킴 · Demo 앱 ID · Mock 시나리오 목록
    - 처음 화면에 뭐가 보여야 하는지 · 사용자 동작 · 그 다음 기대 상태
    - 스크린샷 확인 포인트 · 접근성 식별자 목록
    - ⚠️ 손으로만 확인할 수 있는 항목 ← 이 칸이 꼭 있어야 함. BUILD_AND_TEST.md 함정 9(가장자리 스와이프 뒤로가기는
    자동화로 확인 불가)처럼, 도구 한계인 걸 코드 문제로 착각하지 않게 미리 적어둠
  • 접근성 식별자(accessibilityIdentifier) 규칙 만들기 — DesignSystem / WSSComponent 컴포넌트에.
    BUILD_AND_TEST.md 함정 4: 별점 같은 커스텀 그림은 자동 탭 대상으로 안 잡혀서 좌표로 탭해야 하는데, 그러면 쉽게 깨짐.
    (겸사겸사 VoiceOver도 좋아짐)
  • QA 실행 순서 정하기
    session_set_defaults(스킴·시뮬레이터·앱ID 고정) → build_sim → 설치/실행 →
    snapshot_ui로 요소 확인 → tap/type → 주요 상태 스크린샷 →
    실패하면 .xcresult + 스크린샷 + 콘솔 로그 + 재현 순서 리포트
    - ⚠️ session_set_defaults가 꼭 필요한 이유: BUILD_AND_TEST.md 함정 10 — 인자로 준 스킴이 무시되고
    엉뚱한 스킴이 "성공"으로 끝남. 이걸 모르면 QA 결과 전체가 거짓말이 됨
  • snapshot_ui(텍스트 트리)를 1순위로, screenshot은 보조로 —
    텍스트라서 비교(diff)가 되니까 회귀 테스트가 됨. 스크린샷 눈으로 비교하는 건 매번 결과가 달라서 주된 방법이 못 됨
  • 모듈 Demo만 따로 띄워서 검사 — Demo가 이미 11개 Feature 전부에 있음. 앱 전체 띄우는 것보다 빠르고 독립적임

B4. 리뷰어 결과 형식 통일

  • wss-pr-reviewer · wss-feature-reviewer 결과 형식 정하기 —
    Blocker / Warning / Nit · 파일:줄 · 재현 조건 · 고칠 방향 · 확인 명령어
    → 리뷰어끼리 결과를 비교할 수 있게 되고, make-PR의 "Blocker·Warning 0까지 고치기"를 자동으로 판정 가능
  • docs/DEFINITION_OF_DONE.md 중에서 자동으로 확인 가능한 항목을 make-PR 사전 점검 스크립트로 빼기

축 C — V1을 검증 재료로 쓰기

V2는 지금 운영 중인 V1(Team-WSS/WSS-iOS, UIKit·RxSwift)을 완전히 대체할 코드베이스임.
V1은 실제 운영으로 검증된 동작 기준이지 구조의 정답은 아님.

⚠️ 이 구분은 이미 실제로 겪었음 — Projects/Domain/NovelDomain/CLAUDE.md:41:
"옛날 앱은 연재상태를 여러 개 고를 수 있게 해놨는데, 서버 쿼리는 isCompleted: Bool? 하나뿐이라
둘 다 고르면 필터가 통째로 무시됐다. V1이 다중선택이라는 이유로 따라 하지 말 것."

V1에서 가져올 것 V1에서 따라 하면 안 되는 것
실제 사용자 흐름 RxSwift 기반 상태 구조
서버 요청 형식·파라미터 비대해진 ViewModel
기존에 저장하던 데이터(키·구조) 양방향 의존성
로그인·푸시·딥링크 연속성 UI 구현 방식
놓치기 쉬운 예외 상황 버그였던 동작
운영하면서 이미 겪은 버그 서버 제약을 UI가 잘못 표현한 부분

V1 확보·참조 방식 (협업 제약) — V1은 레포 의존성이 아니라 추출 재료다. V1을 읽어 뽑은
결과물(동작 계약·parity 분류)은 V2 레포에 커밋하므로 협업자는 V1을 안 받아도 그 문서를 소비할 수
있다(V1 클론이 필요한 건 추출/갱신하는 사람뿐). V1 위치는 형제 폴더 클론 관례(기본 ../WSS-iOS)로
두고, 문서엔 **머신마다 다른 절대경로 대신 repo@commit + 레포 내부 경로**로 참조한다
(예: Team-WSS/WSS-iOS@<sha> WSSiOS/Source/Presentation/Library/…) — 내부 경로는 누구 클론이든 동일해 이식성이 있다.

C1. V1 동작 뽑아내기

  • ⚠️ 에이전트를 짓기 전에 파일럿 1개를 손으로 돌려 접근법을 검증한다(go/no-go).
    추천 대상 LibraryFeature — V1 소스 존재(WSSiOS/Source/Presentation/Library/),
    의도적 변경점이 풍부해 오탐 방지까지 확인 가능, 필터→서버쿼리·커서 페이지네이션이라 파라미터 비교가 구체적.
    문서에 없던 parity 발견이 1건이라도 나오면 → 에이전트화 착수, 전부 이미 문서화된 것뿐이면 접근법 재조정.
  • V1 조사 에이전트 만들기 — V1 코드를 읽고 화면마다 이걸 뽑음:
    진입 조건 · 사용자 동작 · API 호출 순서 · 요청 파라미터 · 성공/실패/빈 화면 처리 ·
    로딩 처리 · 토스트/알럿 문구 · Keychain/UserDefaults 사용 · 딥링크/푸시/로그인 특수 처리 · 기존 버그
  • 뽑은 걸 유지 / 일부러 바꿈 / 삭제로 분류하고 사용자한테 확인받는 절차
  • 뽑은 계약의 목적지는 각 Feature 모듈 CLAUDE.md의 ## 화면 동작 계약 절(이미 규약으로 존재 →
    Projects/Feature/CLAUDE.md:22) — 새 문서 포맷을 만들지 않는다.
  • new-feature의 0단계로 연결 (B1)

C2. V1과 V2 결과 비교 테스트

  • 의미로 비교함 — "필터 칩 3개 보인다"가 아니라 **"고른 필터가 서버 요청 파라미터로 이렇게 바뀐다"**로.
    화면 픽셀 비교보다 안정적임
  • 대상: 필터 정책 · 정렬 정책 · 읽기 상태 변화 · 리뷰 작성 정책 · 매퍼 ·
    API 요청 파라미터 생성 · 에러 변환 · UserDefaults/Keychain 저장 키 · 푸시 토큰/기기 ID 처리
  • V1 vs V2 API 요청 비교 — 주소 · 메서드 · 쿼리 · 바디 · 헤더 · 토큰 포함 여부 ·
    페이지네이션 방식 · 에러 응답 처리
    → V2의 Data/Repository 테스트에 바로 넣음
  • 차이 판정 규칙 정하기 — 4분류(추후 docs/PARITY.md로 정착 예정):
    Keep(유지) = V1과 같아야 함 → 다르면 회귀 후보 / Improve(개선) = V1 버그·모호성을 의도적으로 고침(근거 문서화) /
    Break(회귀) = V1 정상 동작을 실수로 깨뜨림 / Unknown(보류) = 사용자 확인.
    🔴 Blocker 2개: ① Keep인데 V2가 다름(누락된 유지), ② V1 버그를 그대로 답습(Improve했어야 하는데 안 함).
  • ⚠️ V1에 이미 죽은 코드가 있다는 걸 전제로 — 예: Projects/Data/NovelData/CLAUDE.md:19의
    "옛날 경로(/users/{id}/novels)는 첫 페이지 고정·필터 무시라서 이제 아무 데서도 안 씀"
  • ⚠️ 판정 전 필독 — 이미 의도적으로 바꾼 곳(오탐 방지): 아래를 안 읽으면 "이미 고친 것"을 Break로 오분류한다.
    NovelDomain/CLAUDE.md:41(연재상태 다중→단일) · NovelData/CLAUDE.md:19+LibraryFeature/CLAUDE.md:32(구 서재 경로 폐기) ·
    RecommendationDomain/CLAUDE.md:18(관심글 제거) · NovelDetailFeature/CLAUDE.md:72,81(장르 뱃지·오버레이 no-op) ·
    Feature/CLAUDE.md(탭 복귀 재조회=V1 viewWillAppear 계승) · WSSComponent/CLAUDE.md:56,65(엣지 스와이프백) ·
    README.md:24-42/ARCHITECTURE.md:3(As-Is/To-Be 대비표)

C3. 앱 교체(cutover) 연속성 검증

⚠️ 체크리스트는 이미 docs/TODO.md 4번에 자세히 있음. 새로 만드는 게 아니라 자동 검증만 붙이면 됨.
이건 "기능 QA"가 아니라 "기존 사용자가 안 끊기게 하는 QA" 라서 따로 다룸.

  • TODO 4번 항목들을 자동 검사로 바꾸기 —
    운영 Bundle ID · 애플 개발자 팀 ID(DEVELOPMENT_TEAM은 레포 어디에도 설정된 적이 없음) ·
    Kakao 앱 키/Bundle ID/키해시 · Keychain access group · UserDefaults 키 ·
    푸시 토큰 등록 방식 · App Store Connect 같은 앱 레코드
  • 앱 교체 리허설 절차를 스킬로 (실기기 확인 — TODO 4번이 "추측하지 말 것"이라 못 박은 부분)

아직 정하지 못한 것 (시작 전에 논의)

  • ViewModel 내부 작업을 함수 대신 enum으로 바꾸기 (제안자 표현: Input / Action 분리)
    ⚠️ 일단 보류를 권함. View 입력과 내부 작업은 이미 나뉘어 있음 — View가 부르는 건 Action enum,
    내부 작업은 private func(load()·requestClose()·close()).
    제안의 진짜 내용은 그 private 함수들을 enum 케이스로 바꾸자는 것임(TCA 리듀서 스타일).
    그러면 상태 바꾸는 부분이 비동기 없는 순수 함수가 돼서 테스트하기 편해짐.
    ① 근데 케이스가 2배로 늘고, close()처럼 4줄에 모여 있던 게 enum + switch로 흩어짐.
    ② 제일 어려운 부분(비동기 순서·늦게 온 응답)은 이미 지금 구조로 NovelReviewViewModelTests가 잡고 있음.
    바꿔서 뭘 더 잡을 수 있는지가 불분명함. 핵심은 "계약 먼저"(B1)이지 ViewModel 구조 쪼개기가 아님.
  • 스냅샷 테스트 넣을지 — swift-snapshot-testing은 앱엔 안 붙지만 테스트에는 외부 라이브러리가 붙음이라 애매함.
    대안(스크린샷 + AI 비교)은 매번 결과가 달라서 회귀 테스트로는 약함
  • 아키텍처 검사기를 Harmonize로 할지, 직접 만들지 — A2는 직접 만드는 걸로 적었는데, Harmonize처럼
    "규칙을 테스트처럼 짜는" 방식이 더 편할 수도 있음. 외부 라이브러리 붙는 것과 저울질해서 결정
  • 테스트 플랜(.xctestplan) 넣을지 — 레이어별로 테스트 묶음(개발 중엔 단위 테스트만 / 제출 전엔 전부)을 만들면
    CI랑 로컬이 명령어 하나로 통일되고 병렬 실행·순서 랜덤을 한곳에서 켤 수 있음. Tuist 스킴 생성이랑 잘 맞는지 확인 필요
  • SwiftFormat 넣을지 — 포맷 자동화 vs diff 지저분해짐

이 목차 이슈가 끝나는 기준

  • 위 항목들이 전부 개별 이슈로 만들어지고 번호가 링크됨
  • 그 이슈들이 다 닫히거나, 안 하기로 한 건 이유와 함께 docs/TODO.md로 옮겨짐

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions