"테스트 방식이 셋이어도, 각자 맡은 일이 있으면 난장판이 아니야."
우리 집 규칙: 목적에 맞게 테스트해
가족 앱들은 한때 세 방식을 거의 우연히 섞어 썼어. 지금 규칙은 방식마다 일을 하나씩 맡겨.
- SwiftPM 패키지 테스트는 Swift Testing.
@Test함수,#expect랑#require,@Suite타입,arguments:로 매개변수화한 테스트,.timeLimit(.minutes(1))같은 트레이트를 써.swift test로 돌고, 기본이 병렬이야. 새 테스트는 여기에 써. - Xcode가 호스팅하는 테스트는 XCTest. iOS나 워치 앱 호스트 안에서 도는 단위 테스트, 확장 테스트, XCUITest로 하는 UI 테스트가 여기 속하고, 전부 XcodeGen 타깃이야. Xcode 호스팅 테스트랑 업로드 파이프라인의 테스트 관문이 이걸 중심으로 짜여 있어.
- 자체 테스트 실행 파일은 Xcode 없는 Mac에서만. Command Line Tools만 있는 Mac엔 XCTest가 없고, 예전 도구엔 Testing 모듈도 없었어. 6.1 Command Line Tools로 재보니 Swift Testing 묶음은 거기서도 돌아. 가족의 받아쓰기 유틸리티랑 텍스트 변환 유틸리티는
-selftest실행 파일을 둬. 평범한 검사를 돌리다 실패하면 종료 코드 1로 끝나는 프로그램이야. 그런 Mac에서도 순수 로직을 확인하려는 거고, 레포에도 그 이유가 적혀 있어.
Swift 6.3에서 더해진 것
6.3의 Swift Testing으로는 이런 걸 할 수 있어. 테스트를 실패시키지 않고 보고만 하는 경고 이슈를 남길 수 있어(Issue.record("…", severity: .warning)). 의미 있게 돌 수 없는 테스트는 취소할 수 있고(try Test.cancel()), 결과에 이미지를 붙일 수도 있어. exit 테스트랑 첨부는 6.2에서 들어왔어.
가족이 비싸게 배운 함정들
- 호스팅된 테스트는 앱 전체를 띄워. iOS 단위 테스트 타깃은 앱 안에서 돌아서
Bundle.main은 맞아. 근데UserDefaults.standard도 앱의 것이고, 테스트와 함께 뜬 앱은 진짜 엔진 주소로 연결하려고 해. 그러니 기본값 저장소, 알림 센터, 전송 생성은 주입해서 써. - AppKit 테스트끼리는 포커스를 공유해. 창이랑 키 뷰를 만드는 테스트는 병렬로 돌면 서로 방해해. 그래서 가족 편집기는
swift test --no-parallel로 돌려. - 상관없는 편집에도 증상이 바뀌면 코드 버그가 아니야. 상관없는 단언 두 개짜리
@Suite를 파일 끝에 붙인 적이 있어. 그랬더니 같은 파일에 있던 실제 시계 테스트가 태스크 할당기 실패를 내면서 테스트 프로세스 전체를 죽였어. 여덟 번 돌리면 여덟 번 다. 새 스위트를 별도 파일로 옮기니까 풀렸어. 비쌌던 건 이분 탐색이야. 중간 결과마다 원인처럼 보여서, 조사하다가 멀쩡한 제품 코드를 두 번이나 다시 썼거든. 규칙은 이거야. "고침"이 증상을 바꾸는데 상관없는 편집도 증상을 바꾸면, 이분 탐색을 멈추고 설정마다 반복 횟수를 정해서 하나씩 지워나가. - 멈출 수 있는 async 테스트엔 시간 제한이 필요해.
.timeLimit트레이트가 없으면, 영영 재개 안 되는 회귀 하나가 테스트 하나를 실패시키는 대신 실행 전체를 멈춰버려.