이메일 아카이버가 정말로 실패할 수 있는 방법은 단 하나뿐입니다: 요청하지 않은 일을 메일에 저지르는 것입니다. 메시지를 읽음으로 표시하거나, 잘못된 폴더에 넣거나, 엉뚱한 것을 삭제하는 것. 그래서 MailVault 의 모든 기능은 같은 방식으로 출시됩니다: 기계가 실제 애플리케이션을 열고, 사람이 하듯 그 기능을 수행하고, 실제 화면에서 결과를 읽어낸 뒤에만.
이 글은 그 과정이 어떻게 돌아가는지에 대한 진행 노트입니다. 설정 자체가 기능들보다 더 흥미로웠기 때문입니다.
테스트는 앱을 직접 사용합니다. 흉내 내지 않습니다.
MailVault는 Tauri 애플리케이션입니다: IMAP 작업을 처리하는 Rust 백엔드와, 네이티브 webview 안의 React 인터페이스로 이루어져 있습니다. Tauri에는 여기서 잘 알려지지 않은 강점이 하나 있습니다: 앱을 webdriver 기능과 함께 빌드하면, 브라우저와 마찬가지로 실제 애플리케이션을 원격으로 제어할 수 있게 됩니다. 저희 테스트 스위트는 WebdriverIO 위에서 wry 기능을 사용해, tauri-driver를 거쳐, 실제로 컴파일된 바이너리를 대상으로 실행됩니다.
이 차이는 중요합니다. 컴포넌트 테스트는 가상의 브라우저 안에 목록을 렌더링하고 가상 출력값에 대해 검증합니다. 반면 저희의 엔드투엔드 테스트는 사용자가 받는 것과 동일한 창을 열고, 같은 행들이 그려지길 기다리고, 같은 버튼을 클릭한 뒤, 인터페이스와 서버의 메일이 실제로 무엇을 말하는지 확인합니다. 이번 주 실행에서 발췌한, 선택 액션 바를 테스트하는 예시입니다:
Selection Action Bar effects
✓ marks a selected row as read and repaints it
✓ marks a row back as unread
✓ marks several selected rows as read in one action
✓ archives a selected row and flips its source icon to local
✓ unarchives it again
✓ moves a selected row to another folder and drops it from the list
✓ deletes a selected row from the server and drops it from the list
7 passing (833ms)
한 줄 한 줄이 모두 완전한 왕복입니다: 인터페이스에서의 클릭, Rust 백엔드 호출, 서버와의 IMAP 대화, 그리고 테스트가 직접 눈으로 확인하는 리페인트까지. 스위트가 보관 처리로 소스 아이콘이 로컬로 바뀐다고 말할 때, 그것은 실제로 기계가 그 아이콘이 바뀌는 것을 지켜보았다는 뜻입니다.
메일 서버는 실행과 함께 태어나고 함께 사라집니다
이 중 어느 것도 실제 계정에 손대지 않습니다. 각 실행은 로컬 포트에 새로운 모의 IMAP 서버를 띄우고, 이미 알려진 두 개의 계정으로 시드를 채웁니다. 그래서 스위트는 단일한 정상 경로 대신 계정 전환과 통합 받은편지함까지 시험할 수 있습니다. 그중 한 계정은 일부러 700통의 메시지를 담고 있는데, 이는 앱의 두 로딩 창보다 큰 규모라서 언제나 진짜로 일부만 로드된 메일함 상태, 즉 페이지네이션 버그가 사는 바로 그 상태를 확보할 수 있습니다.
앱 자체도 그 실행을 위해 만들어진, 쓰고 버리는 홈 디렉터리를 가리킵니다. 아카이브, 설정, 로컬 데이터베이스 모두 1분 전에는 존재하지도 않았고 실행이 끝나면 삭제될 폴더 안에 놓입니다. 자격 증명도, 네트워크도, 실제 메일도 그 프로세스 근처에는 전혀 없습니다.
결정성도 또 다른 이유입니다. 실제 메일 서버는 당신을 배급합니다. Gmail은 IMAP 다운로드를 하루 2,500 MB로 제한하는데, 이에 대해서는 저희가 지난 필드 노트에서 다룬 적이 있습니다. 그리고 모든 제공업체는 테스트 스위트가 감당할 수 없을 만큼 변덕스럽습니다. 모의 서버는 시나리오가 말하는 그대로, 매번 밀리초 단위로 정확히 동작합니다. 스위트가 할 일은 저희의 버그를 잡는 것이지, Google의 날씨를 잡는 게 아닙니다.
정말 중요한 것을 담고 있는 것처럼 테스트받는 이메일 클라이언트. MailVault는 당신의 메일을 당신 소유의 디스크에 있는 표준 파일로 보관하며, 모든 릴리스는 먼저 이 스위트를 통과해야만 세상에 나갑니다.
MailVault 방문 →책상 밑의 그 기계는 저희가 작업하는 기계가 아닙니다
여기서부터가 일상을 바꾼 부분입니다. 완전한 엔드투엔드 실행은 무겁습니다: React 프런트엔드를 빌드하고, Rust 백엔드를 컴파일한 뒤, 애플리케이션을 수십 번 열고 닫습니다. 스펙 파일마다 새 세션 하나씩이고, connected 스위트 하나만 해도 스펙 파일이 14개입니다. 이걸 개발 머신에서 돌리면, 창들이 포커스를 훔쳐가는 동안 CPU가 당신의 존재를 잊어버리는 걸 지켜보게 됩니다. 그 시간 동안은 아무도 코드를 쓸 수 없습니다.
그래서 실행은 그곳에서 일어나지 않습니다. 작은 Apple Silicon Mac mini 한 대가 로컬 네트워크에 앉아 다른 일은 아무것도 하지 않습니다. 이 기계는 오직 SSH로만, 그것도 네트워크 안에서만 접근할 수 있으며, 저장소와 툴체인의 클론을 갖고 있습니다. 기능이 스스로를 증명할 준비가 되면, 실행은 명령 하나로 mini에게 전달되고, 개발 머신은 곧바로 다음 작업을 만드는 일로 돌아갑니다. 그동안 mini는 지금 이 기능을 빌드하고, 부팅하고, 클릭해 나갑니다.
이 두 활동은 번갈아 일어나는 대신 겹쳐서 일어납니다. 별것 아닌 것처럼 들리지만, 기능이 개발되는 동안 스위트가 하루에 몇 번이나 실행되는지 세어 보면 이야기가 달라집니다. mini의 각 세션은 항상 앱을 콜드 스타트로, 새로운 상태와 새로운 서버로 시작하는데, 이는 마침 앱이 만들어진 기계보다 느린 하드웨어에서 이루어지는 정직한 콜드 스타트 테스트이기도 합니다. 어떤 기능이 mini에서 굼뜨게 느껴진다면, 그것은 mini의 문제가 아닙니다. 누군가의 4년 된 노트북에서 벌어질 일을 미리 보여주는 것입니다.
실패 출력은 로컬에서와 똑같은 방식으로 돌아옵니다: 어떤 스펙인지, 어떤 검증인지, 인터페이스가 실제로 무엇을 보여줬는지. 고치고, 다시 전달합니다. 이 루프는 지루하고, 지루하다는 것이 바로 핵심입니다.
유닛 테스트가 얌전히 놓치는 것을 이 스위트가 잡아내는 지점
스위트가 잡아내는 가장 값진 것들은 결코 유닛 테스트가 다루는 층에 있지 않습니다. 그것들은 이음매에 있습니다: 받은편지함에서 연 스레드가 엉뚱한 메일함의 답장 본문을 보여주는 것, 서버에서는 옮겨졌지만 목록에서는 남아 있는 메시지, 실제로는 동작했지만 엉뚱한 행의 아이콘을 다시 그린 보관 액션. 이런 것들은 저마다 세 개의 하위 시스템이 개별적으로는 옳지만 합쳐지면 틀린 결과에 합의한 경우입니다. 이런 문제를 잡아내는 유일한 테스트는 사용자가 하는 것을 그대로 하는 테스트뿐입니다.
바로 그래서 스펙은 함수 이름이 아니라 사용자의 문장으로 작성됩니다 ("marks a selected row as read and repaints it"). 하나가 실패하면, 리포트는 마치 사람이 접수한 버그 리포트처럼 읽힙니다.
직접 스위트를 실행해 보세요
MailVault는 오픈 소스이며, 스위트는 전적으로 모의 서버를 대상으로 실행됩니다: 계정도, 자격 증명도, 설정할 것도 없습니다. Node 18 이상과 Rust 툴체인이 설치되어 있다면:
git clone https://github.com/GraphicMeat/mail-vault-app
cd mail-vault-app
npm install
cargo install tauri-webdriver-automation
npm run test:e2e
cargo install은 tauri-wd를 제공합니다. 이는 스위트가 앱을 제어하기 위해 실행하는 WebDriver 브리지입니다. 마지막 명령이 나머지를 처리합니다: 테스트 모드로 프런트엔드를 빌드하고, webdriver 기능으로 Rust 백엔드를 컴파일하고, 모의 IMAP 서버를 시작한 뒤, 모든 스펙을 통해 앱을 구동합니다. 첫 실행은 대부분의 시간을 Rust 컴파일러 안에서 보냅니다. 그 이후로는 npm run test:e2e:ui 와 npm run test:e2e:connected 가 그 빌드를 재사용해 각자의 스위트를 곧바로 실행합니다.
실행되는 동안 앱 창은 계속 열리고, 스스로를 클릭하고, 닫히기를 반복합니다. 로봇이 이메일을 읽는다는 사실을 어떻게 느끼느냐에 따라, 이것은 섬뜩하게 느껴지거나 아니면 바로 그게 핵심으로 느껴질 것입니다.
아직 해결하지 못한 것
정직이 마케팅보다 저렴하므로 솔직히 말하면: 모의 서버는 저희가 구현한 IMAP만 구사할 뿐, Gmail과 Outlook이 즉흥적으로 만들어내는 모든 방언을 구사하지는 못하며, 실제 제공업체를 상대로 한 OAuth 로그인은 여전히 사람의 손으로 검증합니다. 시각적 회귀 테스트와 백업 스위트는 아직 매번의 실행마다가 아니라 수동으로 돌립니다. 그리고 mini는 한 번에 하나의 스위트만 순차적으로 실행합니다: 16기가바이트의 메모리는 정해진 예산이고, 저희는 불안정한 실행 두 개보다 믿을 수 있는 실행 하나를 원합니다. 이 세 가지 모두 그 순서대로 할 일 목록에 올라 있습니다.
그동안에도 기능들은 느린 방식으로 계속 도착합니다: 작성되고, 전달되고, 지루해하지 않는 작은 컴퓨터에 의해 클릭되어 검증되고, 더 이상 문제를 찾지 못할 때에만 출시됩니다. 당신의 메일은 적어도 그 정도의 예의는 받을 자격이 있습니다.
당신의 메일을, 마케팅보다 더 혹독하게 테스트받는 앱이, 로컬에 보관합니다. macOS와 Linux용 무료 오픈 소스입니다.
MailVault 방문 →