본문 바로가기
C.W.K.
Lesson 03 of 06 · published

랩과 테스트용 앱

~15 min · lab, fixtures, integration-tests, gui-testing, fail-closed

Level 0Idle Hands
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"아빠 Mac은 어느 것도 테스트 대상이 아냐." — Tinker 아키텍처

Tinker를 시험해도 되는 곳

진짜 화면에서 진짜 앱을 숨기는 건 아빠가 편집하는 Mac에서 해볼 일이 아냐. Tinker 빌드랑 테스트는 한 곳에서만 돌아. 등록된 피파 홈 랩, 피파 자기 Mac의 pippa 계정이야. 그 Mac의 랩 조정자가 들여보내 줘야 돌아서, 테스트가 녹화나 방송이랑 부딪힐 일이 없어. 이 규칙은 바라기만 하는 게 아니라 강제돼. 테스트랑 랩 스크립트는 전부 require-lab.py로 시작하고, 그게 가족 레지스트리에서 랩의 fleet 라벨을 찾아서 지금 도는 Mac과 계정 이름을 비교해. 다른 데선 "Builds and tests require the registered Pippa Home lab account."로 나가. 첫 실험 전에, 닫힌 쪽으로 실패해.

시험은 세 겹

엔진 테스트는 Swift Testing에서 2트랙의 가짜 보이기 이음매로 돌아. 원래 숨겨진 앱, 겹침, 프로세스 교체, 부분 실패, 증명된 미시도, 고정 거절, 저장 실패, 다시 켠 뒤 복구, 숨기는 도중 긴급 정지까지. 빠르고 결과가 늘 같아. Python 테스트는 들어 있는 키트의 표류 검사랑 진짜 AppActions 루트를 워커에 대고 돌려. actor 거절과 요청 재생도 포함해. 랩 테스트용 앱은 어떤 가짜도 못 하는 부분을 해. 작은 실행 파일 tinker-fixture를 일회용 ad-hoc 서명 앱으로 감싸서 Launch Services로 띄우고, 전용 도우미 앱이 그걸 진짜로 숨겼다 보였다 하면서 끝나고도 살아 있는지 확인하고, 다 하면 닫아.

GUI 랩

따로 도는 실행은 랩용 Tinker.app을 빌드하고, 표시 이름에 공백이랑 괄호가 든 테스트용 앱을 띄우고, Tinker를 랩 스냅숏 플래그로 시작해. 그러면 Tinker가 진짜 AppKit 마우스 이벤트로 자기 설정 창을 움직여. 선택기에서 앱 고르기, 저장, 설정 장면 다시 열기. 그리고 탭마다 스크린숏을 찍어. 자기 문으로는 status, 액션, 변경·활동 목록, 되돌리기 하나, 그리고 중요한 거절들을 돌려봐. 남의 actor, 남의 Host, 브라우저 Origin, 포워딩된 호출자. 성공해야 하는 경우도 하나 있어. Host에 다른 포트를 단 채 127.0.0.1로 오는 터널. 스크립트는 캡처와 기록이 다 있는지 확인하고, 번들 서명이 그대로인지 검사하고, 마지막으로 종료 기록을 봐. 앱이 종료 Apple 이벤트로 나갔고 해제를 끝냈어야 해.

랩이 배운 것

이 퀘스트의 레슨 여럿이 랩에서 나왔어. 숨기기가 되기 전에 hide()가 false를 돌려준 거, Swift Testing 매크로가 AppKit 보이기에 명시적 술어를 원한 거, 테스트용 앱이 다 뜬 다음에 확인해야 한다는 거, 뒤집힌 호스팅 뷰에서 클릭은 창 좌표로 해야 한다는 거. 하나하나가 커밋이 됐고, 짠 사람 말에 맞장구치는 가짜를 쓴 단위 테스트로는 하나도 안 보였을 거야.

Code

닫힌 쪽으로 실패: 랩 정체는 레지스트리에서 와 (scripts/require-lab.py에서)·python
"""Fail closed before experiments; registry placement owns the lab identity."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'bridge'))
import kit_family_endpoints as family

lab = family.host_fleet_label(str(family.FAMILY_SERVICES['cwkPippaHome']['host']))
actual = (Path.home() / '.config/fleet-label').read_text().strip()
if actual != lab or Path.home().name != 'pippa':
    raise SystemExit('Builds and tests require the registered Pippa Home lab account.')
테스트 입구 (scripts/test.sh)·bash
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/.."
PYTHON="$HOME/miniconda3/envs/cwk-tinker/bin/python"
"$PYTHON" scripts/require-lab.py
swift test --jobs 2
"$PYTHON" -m pytest -q Tests
bash scripts/lab-visibility.sh
GUI 랩이 통과라고 부르기 전에 고집하는 것 (scripts/lab-ui.sh에서)·bash
for name in presets shortcuts activity access configuration; do
  test -s "$OUTPUT/$name.png"
done
for name in settings-interaction watchfire health actions status changes restore activity \
            actor-refusal tunnel-actions foreign-host-refusal browser-refusal forwarded-refusal; do
  test -s "$OUTPUT/$name.json"
done
# The app left through a quit Apple event, as it does at logout, and its
# deferred termination finished the release instead of timing out.
grep -Fq '"path":"apple-event","released":true' "$OUTPUT/quit.json"

External links

Exercise

네 앱에서 진짜 OS만 확인해줄 수 있는 동작 셋을 적고, 각각 누구도 기대지 않는 걸 안 건드리고 시험할 일회용 테스트 대상을 설계해 봐. require-lab.py 같은, 다른 데선 테스트가 못 돌게 막는 검사는 뭐가 될까?
Hint
프로세스 경계를 넘거나 권한에 기대는 건 다 좋은 후보야. 창 보이기, 파일 조정, 알림, URL 처리. 테스트 대상은 대개 그 실행만을 위해 만들었다가 지우는 작은 앱이나 파일이야. 가드는 이미 관리하는 데이터(레지스트리, 설정)에서 '맞는 곳'을 끌어내서 코드가 도는 곳과 비교하고, 첫 효과 전에 나가야 해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.