Skip to content

검증 ​

목차 ​

  • 어떤 변경에 무엇을 실행할지
  • 연결 검증하기
  • 플러그인 검증하기
  • 디자인 변경 검증하기
  • 릴리스 산출물 검증하기
  • 업데이트 인계 검증하기
  • 여기서 검증할 수 없는 것

변경을 확인할 수 있는 최소한의 검사를 실행하고 검증한 범위와 검증하지 못한 범위를 알려 주세요.

어떤 변경에 무엇을 실행할지 ​

변경실행
타입, 런타임, 전송 로직script/test swift
디바이스 패널 캐시, 플러그인 로딩, Mac 앱 단위 테스트script/test native (임시 저장소 사용, UI 실행 없음)
@necto/bridge, 플러그인 소스script/test web
릴리스 패키징script/test release
업데이트 설치와 재실행 순서swift test --package-path NectoMac --filter UpdateFinisherTests 실행 후, Dock에 고정한 앱 업데이트하기
빌드된 패널 진입점과 에셋node script/check-panel-assets.mjs
토큰, NectoTheme, components.cssscript/test native 실행 후, 갤러리를 두 외형 모두에서 열기
앱까지 도달하는 모든 변경script/build 실행 후, 앱 실행하기
SDK의 공개 API시뮬레이터를 부팅한 상태에서 script/build로 ExampleApp의 SDK 연동까지 빌드하기
연결 수명주기, CLI의 디바이스 오퍼레이션script/test e2e
계약 변경같은 커밋에서 픽스처 업데이트하기

script/test는 Swift·Vitest 단위 테스트, 네이티브 캐시·WebView 통합 테스트, 패키지·릴리스 계약과 패널 에셋 검사를 실행해요. 레이아웃과 번역 문구는 플러그인 미리보기에서 직접 확인해요. 테스트 통과만으로 UI나 전송 경로를 검증한 것은 아니므로 해당 변경은 앱이나 실기기에서도 확인해야 해요.

NectoAppTests 스킴은 Mac 앱을 실행하지 않고 Swift Testing 테스트를 실행해요. 검사할 앱 소스를 두 타깃이 함께 사용해요. 테스트에는 메모리 승인 저장소, 임시 캐시 디렉터리, 격리된 WebKit 저장소를 사용해요. Xcode에서 스킴을 실행하거나 다음 명령을 사용하세요.

bash
xcodebuild -project Necto.xcodeproj -scheme NectoAppTests -destination 'platform=macOS' test

연결 검증하기 ​

시뮬레이터 E2E ​

Necto를 종료한 뒤 script/test e2e를 실행하세요. 테스트 전용 번들 ID로 Mac 앱, CLI, ExampleApp을 빌드하고 기존 iPhone 17 Pro 시뮬레이터에서 NectoE2ETests Xcode 스킴을 실행해요. 회사 서명 인증서나 별도 테스트 도구는 필요하지 않아요.

CLI로 기기와 플러그인을 조회하고 plugin help를 확인해요. UserDefaults에 고유한 값을 쓰고 읽어 once를 검증하고, 성능 측정 이벤트 세 개를 JSONL로 받아 stream을 검증해요. 구독 중 ExampleApp을 종료하면 CLI가 오류로 끝나고 기기가 목록에서 사라져야 해요. ExampleApp을 다시 실행한 뒤 Necto를 재시작하지 않고 두 동작을 반복해요.

CI와 로컬 모두 부팅 상태와 관계없이 iPhone 17 Pro를 사용해요. 여러 iOS 버전에 있으면 최신 버전을 선택하고, 해당 기기가 없으면 바로 실패해요. Simulator를 열고 부팅이 끝나면 테스트용 ExampleApp(im.toss.necto.e2e.example)을 설치해요. 중단된 실행에서 남은 앱은 덮어쓰고, 검증할 값은 테스트에서 직접 저장해요. 끝나면 해당 앱과 임시 Mac 홈을 제거해요. 시뮬레이터는 켜둔 채로 유지하며 초기화하거나 삭제하지 않아요. 다른 앱도 그대로 두어요. 호스트끼리 SDK의 루프백 포트를 공유하므로 다른 Necto는 종료해야 해요. 정해진 시간만큼 기다리는 대신 제한 시간 안에 연결과 응답을 확인해요. 로그와 테스트 결과는 Build/E2E/Logs에 남아요. 로컬에서는 별도로 실행하며 기본 script/test에는 포함하지 않아요.

CI에서는 Mac·SDK 잡이 끝나면 Simulator Connection & CLI E2E를 실행해요. Mac 잡은 앱·CLI·테스트 번들을, SDK 잡은 ExampleApp을 전달해요. 같은 워크플로 실행의 아티팩트를 받아 test-without-building으로 재빌드 없이 검증해요. 실행 권한과 번들 내부 심볼릭 링크를 유지하도록 압축해서 전달해요. 로컬에서도 script/test e2e 실행 후 script/test-e2e run으로 재빌드 없이 반복할 수 있어요.

E2E가 실패하면 로그와 테스트 결과를 첨부해요. 실제 GUI 호스트·CLI·SDK 전송 경로를 검증하며 WebView 조작, 레이아웃, USB는 제외해요.

수동 연결 확인 ​

루프백 연결 경로는 시뮬레이터로 확인해요.

bash
xcrun simctl boot <device-id>
xcodebuild -project Necto.xcodeproj -scheme ExampleApp -destination "id=<device-id>" build
xcrun simctl install <device-id> Build/Products/Debug-iphonesimulator/ExampleApp.app
xcrun simctl launch <device-id> im.toss.necto.example

앱이 수신 포트를 보여 주고 몇 초 안에 Mac 앱의 사이드바에 나타나요. 나타나지 않을 때는 lsof -nP -iTCP:9979-9986 -sTCP:LISTEN으로 SDK의 기본 포트 범위를 확인해요. 시작 포트를 바꿨다면 해당 범위를 검사하세요.

동시 연결은 부팅한 시뮬레이터 두 대에서 SDK를 연동한 앱을 실행해 확인해요. SDK는 8개 포트 범위에서 빈 포트를 선택하고 Mac은 8개 모두 탐색해요. 사이드바와 necto-cli targets에 두 타깃이 모두 표시되는지 확인한 뒤, 타깃을 전환하며 각각 디바이스 오퍼레이션을 호출하세요. 8개 포트가 모두 사용 중이면 추가 SDK 인스턴스는 그 범위에서 리스닝을 시작할 수 없어요.

실기기는 USB로 연결해 사이드바에 나타나는지 확인해요. connectionRefused는 터널이 기기까지 연결됐지만 앱이 수신 대기 중이지 않다는 뜻이에요. 앱이 실행 중이 아닐 때 나오는 정상 응답이에요.

플러그인 검증하기 ​

CLI 설치·삭제 변경은 Settings → About의 명령을 실행한 뒤 necto --help, necto install --help, necto delete --help와 necto-cli 별칭을 확인하세요. --local --remote를 함께 쓰거나 --local에 URL을 넣으면 실패해야 해요. 픽스처 플러그인으로 necto install <repo>, necto install <folder> --local, necto delete <pluginID> --json을 확인하세요. 설치·업데이트 승인과 전송 중 취소 후 즉시 재시도를 확인하고 CLI 작업 중 GUI 업데이트를 시도하세요. 시작을 거부한 업데이트는 목록에 남아 있어야 해요. 삭제하면 설치 파일이 휴지통으로 이동하고 권한·등록이 제거되며 백그라운드 패널이 종료되어야 해요. 없는 ID나 디바이스 플러그인은 파일을 바꾸지 않고 실패해야 해요. 설치·삭제 중 Reload와 연속 Reload를 시도해 동시 작업이 거부되는지, 삭제한 행이 다시 나타나지 않는지 확인하세요.

NectoProcessRunnerTests는 출력·표준 입력·취소·타임아웃과 자손 프로세스가 유지하는 파이프를 검사해요. 릴리스 다운로드와 셸 실행은 같은 실행기를 사용해요. 인증 도우미가 쓰기 쪽을 유지하더라도 취소한 전송의 두 출력 리더를 닫은 뒤 다음 설치를 받아야 해요.

식별 기준이나 설치 흐름을 바꿨다면 Global Full Access를 끄고 다음도 확인하세요.

  1. 데스크톱 폴더·ZIP을 설치하고 무해한 셸 명령 하나를 승인해요. 같은 매니페스트 ID로 업데이트하면서 먼저 취소해 기존 파일과 권한이 유지되는지 확인하고 다시 승인해 설치 UUID와 명령 승인은 유지되고 콘텐츠 해시만 바뀌는지 확인해요.
  2. Necto 밖에서 설치된 파일을 수정하고 Reload해요. 기존 권한으로 실행되지 않고 Review required에 표시되어야 해요. 승인과 취소를 모두 확인하세요.
  3. 같은 ID를 삭제한 뒤 재설치해요. 설치 UUID가 바뀌고 이전 명령 승인이 사라져야 해요. 중복 폴더와 다른 ID로의 대상 지정 업데이트도 거부되어야 해요.
  4. 같은 앱 번들 ID와 플러그인 ID로 디바이스 플러그인을 업데이트하고 재연결해요. 셸 권한은 유지되고 다른 앱의 같은 ID는 권한을 이어받지 않아야 해요. assertion을 꺼도 SDK는 중복 등록을 거부하며 등록 해제 후 재등록은 가능해야 해요.

삭제·재설치 테스트는 플러그인 파일용 임시 CFFIXED_USER_HOME과 UserDefaults용 별도 테스트 앱 번들 ID를 함께 사용하세요. 환경 변수만으로는 설정 도메인이 격리되지 않아요. 임시 홈 디렉터리는 앱을 실행하기 전에 만드세요. 디렉터리가 없으면 macOS가 해당 프로세스의 설정 저장을 비활성화할 수 있어요. 동작 전후의 새 Loupe 결과를 남기세요. 빌드 성공만으로 UI를 검증했다고 할 수는 없어요.

웹 패키지를 먼저 빌드하고 그다음 앱을 빌드해요. 플러그인 출력물은 앱 번들에 포함되므로 yarn build 전에 빌드한 앱은 오래된 에셋을 사용해요.

디바이스 플러그인은 검사 중인 시뮬레이터에 ExampleApp을 다시 설치하고 실행해요. 부팅된 다른 시뮬레이터를 대상으로 빌드하면 그 프로덕트는 갱신되지만 이미 패널을 제공하고 있는 앱은 갱신되지 않아요.

플러그인은 브라우저뿐 아니라 앱에서도 확인해요. 브리지와 창이 적용하는 테마는 실제 호스트에서 확인해야 해요.

플러그인을 열어 둔 채 시스템 설정에서 라이트와 다크를 전환해 보세요. 토큰이 리로드 없이 따라와야 해요.

디자인 변경 검증하기 ​

백그라운드 플러그인은 빌드한 Necto에서 다음도 확인하세요.

  1. 앱을 다시 열고 선택한 디바이스를 바꿔도 플러그인 공용 설정이 유지돼요.
  2. 다른 패널이나 Settings를 보는 동안에도 픽스처 플러그인이 알림을 제출하고 두 화면 사이를 전환해도 백그라운드 작업이 이어져요.
  3. 비활성화·삭제·업데이트 시 백그라운드 작업이 종료돼요. 일반 패널은 기존처럼 선택 변경 시 취소하고 마지막 창을 닫으면 모든 작업이 멈춰요.
  4. 알림 권한 거부, 집중 모드, 앱이 앞에 있을 때의 알림 표시를 확인해요.
  5. 사용자가 누른 HTTPS 링크는 시스템 브라우저에서 열고 패널은 유지해요.
  6. Command-N으로 두 번째 창을 열어도 백그라운드 초기화는 한 번만 실행돼요. 각 창을 활성화하면 입력 상태를 유지한 채 같은 페이지가 옮겨져요. 어느 창에서든 비활성화하면 모든 창에서 종료되고, 그 창을 닫아도 다시 실행되지 않아야 해요.
  7. 플러그인 입력창에 포커스를 둔 채 Command-comma로 Settings를 열고 타이핑해요. 숨겨진 입력값은 바뀌지 않아야 해요. 다른 패널로 전환해서도 확인하고 그 패널의 입력창은 정상적으로 동작하는지 확인하세요.
  8. 최초 창을 닫은 뒤 CLI로 픽스처를 설치해요. 남은 창에 승인 창이 나타나고 승인과 취소를 모두 완료할 수 있어야 해요.

스토리지는 NectoStorageProviderTests, 승인한 명령과 분리된 셸 v2 표준 입력은 NectoShellProviderTests로 확인해요. 실제 WebView 수명과 화면 알림 표시는 수동 검증이 필요해요.

레지스트리 테스트는 프로바이더 콜백이 제한 시간이나 취소 이후에도 남아 있는 상황에서 호출자가 바로 반환되는지, 재시도로 미종료 작업이 쌓이지 않는지 확인해요. 플러그인을 삭제할 때 활성 구독도 취소되는지, 다른 기기의 구독은 유지되는지 확인해요. 구독을 여는 도중 플러그인이 삭제되거나 호출자가 취소되면 구독을 시작하지 않아야 해요. NectoWebViewRecoveryTests는 실제 숨겨진 WKWebView에 콘텐츠 프로세스 종료 delegate 이벤트를 주입해 재로딩·재시도 제한·해제 시 취소를 확인해요. script/test native에도 포함돼 있어요. 메모리 부족을 강제로 만들거나 사용자의 WebKit 프로세스를 종료하는 검증은 아니며, 실제 OS 종료 후 복구와 장시간 실행은 별도로 확인해야 해요.

docs/design/index.html을 열어 배포되는 스타일시트의 변경을 확인해요. 두 외형과 두 텍스트 크기 모두 확인하세요. 대비 기준을 통과해도 실제 화면이 잘 읽히는지는 별도로 확인해야 해요.

릴리스 산출물 검증하기 ​

script/test release는 빌드·서명·배포 도구를 테스트 대역으로 바꾸고 임시 픽스처에서 실제 릴리스 스크립트를 실행해요. 생성한 산출물, 릴리스 첨부 파일, dry-run 격리를 검사하며 앱을 빌드하거나 GitHub에 연결하지 않아요. 실제 DMG, 서명 또는 설치한 앱의 업데이트 흐름을 검증하지는 않아요.

npm 압축 파일에는 LICENSE를, DMG와 앱의 Contents/Resources에는 THIRD_PARTY_NOTICES.txt를 포함해요. 앱 고지는 서명 전에 추가해요. yarn docs:build는 웹사이트에 포함된 라이브러리와 글꼴의 고지를 같은 파일명으로 생성해요. 의존성의 라이선스가 없으면 빌드가 실패해요. 게시 전에는 실제 배포물에 고지가 포함됐는지 확인하세요.

swift test --package-path NectoMac --filter NectoAppReleaseTests는 릴리스 메타데이터, 리디렉션 정책, 번들·버전 일치 여부와 내장 CLI를 포함한 실제 ad-hoc 서명 앱을 검사해요. 정상 업데이트는 통과하고, 서명이 없거나 수정된 앱은 거부돼야 해요. 전체 다운로드·설치 흐름은 gh가 없는 Mac에서 내려받은 DMG로 설치한 뒤 더 최신 릴리스로 업데이트하며 별도로 검증하세요. 수정된 앱, 번들 ID나 버전이 다른 산출물은 업데이트 인계 전에 거부돼야 해요. NectoUpdateDownloadTests는 루프백 HTTP 서버로 오류 응답, 리디렉션 거부, 스트리밍 용량 제한과 취소를 검사해요. 실제 GitHub HTTPS·CDN 다운로드 검증을 대신하지는 않아요.

릴리스 스크립트 테스트는 번들 버전 일치 여부, ad-hoc 서명 순서, 실패 시 태그나 게시 전에 중단하는지도 검사해요. 두 릴리스 모드 모두 내장 CLI와 앱을 ad-hoc 서명하고 검증한 뒤 DMG를 만들어요. 회사 인증서나 공증 프로필은 필요 없어요. --dry-run은 산출물만 만들고 태그나 릴리스를 게시하지 않아요. 서명은 무결성을 확인할 뿐 배포자를 인증하지 않으며, 업데이트는 지정된 릴리스 저장소를 신뢰해요.

릴리스 인자는 MARKETING_VERSION을 설정하며 빌드한 앱의 CFBundleShortVersionString과 다르면 중단해요. 호스트 정보와 업데이트 비교도 별도 소스 코드 상수가 아니라 이 번들 버전을 사용해요.

공개 CI는 GitHub-hosted runner를 사용해요. Mac 잡의 앱과 테스트 번들에는 ad-hoc 서명을 사용하고 SDK 잡의 ExampleApp은 서명 없이 빌드해요. Check의 작업은 Mac Build & Tests, SDK Build & Tests, Simulator Connection & CLI E2E, Web Plugins Build & Tests, Documentation Build로 나뉘어요. Mac·SDK·E2E는 macos-26에서 Xcode 26.6을 사용하고 웹과 문서 빌드는 ubuntu-24.04에서 실행해요. Node 22.12.0은 E2E를 제외한 네 작업에서, Yarn 4.6.0은 웹과 문서 빌드에서 사용해요. 문서 배포는 별도 워크플로에서 수동으로 실행하며 GitHub Pages는 Actions 배포로 설정해야 해요. 워크플로 파일을 추가한 것만으로 외부 환경에서 실행에 성공했다고 볼 수는 없어요.

script/release는 DMG, 해당 파일의 .sha256, 개발용 패키지 두 개를 첨부해요. 체크섬은 디렉터리 없이 DMG 파일명만 담은 shasum -a 256 형식이에요. 업데이터는 HEAD /releases/latest가 이동하는 주소에서 버전 태그를 읽고, 그 버전의 체크섬과 DMG를 받아요. GitHub API나 gh 인증 정보는 사용하지 않아요. 게시 후 로그인 없이 리디렉션과 두 파일의 주소를 확인하세요. 체크섬이 없거나 형식이 잘못되면 실패해야 하고, 수정된 DMG는 마운트 전에 거부해야 해요. 설치된 버전과 같거나 오래된 릴리스는 업데이트로 안내하지 않는지도 확인하세요. 0.4.x에서 공개 버전 0.1.0으로 옮길 때는 한 번 직접 설치해야 해요. 자동 업데이트는 다운그레이드하지 않아요. 이미 게시한 파일은 변경하지 않아요.

업데이트 인계 검증하기 ​

swift test --package-path NectoMac --filter UpdateFinisherTests는 임시 앱 디렉터리를 사용해 Swift 인계 로직을 검사해요. 프로세스 상태, 앱 실행과 주입한 실패만 테스트 대역이에요. 기존 앱 종료 전 설치·실행 금지, 앱 최상위 디렉터리의 inode 유지, 전체 Contents 교체, 롤백과 제한된 대기를 Necto 실행 없이 검증해요.

릴리스 빌드 후 서명된 necto-cli가 앱에 포함됐는지 확인하세요. 테스트 앱을 Dock에 고정하고 업데이트해 기존 PID가 종료된 뒤 새 PID가 시작되는지, 기존 Dock 항목이 같은 자리에 남는지, 두 번째 아이콘이 남지 않는지 확인하세요. 이 검증에는 실제 macOS 앱 실행이 필요해요. Swift 테스트는 최종 Dock 동작이나 릴리스 패키징을 증명하지 않으며, Dock 설정을 다시 쓰지도 않아요.

인계에 성공하면 앱 옆의 .necto-update-* 준비 디렉터리 삭제를 시도해요. 실패하면 복구를 위해 install.log와 PreviousContents를 유지해요. 복원에 실패했다면 해당 백업을 삭제하면 안 돼요.

여기서 검증할 수 없는 것 ​

  • 시뮬레이터에서의 USB. 시뮬레이터에는 usbmuxd 경로가 없어요. 루프백을 사용해요.
  • 런타임 증거 없는 외형 확인. 빌드나 정적 검사만으로 화면을 확인할 수는 없어요. Loupe나 권한이 허용된 캡처 경로를 사용하고 필요한 런타임 접근이나 화면 기록 권한이 없다면 미검증 범위를 명시하세요.