
1. 590배라는 숫자가 말해주는 것
여러분, SQLite라는 데이터베이스 들어보셨죠? 아마 지금 이 글을 읽고 계신 스마트폰 안에도 SQLite가 돌아가고 있을 거예요. 그런데 이 SQLite의 핵심 코드는 약 15만 줄 정도인데, 테스트 코드는 무려 9,200만 줄이 넘는다고 해요. 실제 코드 대비 590배죠.
보통 소프트웨어 개발에서는 테스트 코드가 실제 코드의 1~2배만 되어도 "우리 테스트 잘하고 있네!" 하고 어깨를 으쓱하는데요. SQLite는 그 기준을 아예 다른 차원으로 끌어올렸어요.
왜 이렇게까지 하냐고요? Stack Overflow의 2024년 개발자 설문조사에 따르면 SQLite는 전 세계적으로 가장 많이 사용되는 데이터베이스 중 하나로 꼽히고 있어요. 여러분의 스마트폰, 웹브라우저는 물론이고 자동차, 비행기 시스템까지 SQLite가 들어가 있거든요. 단 한 번의 버그도 용납할 수 없는 환경이죠.
2. 4개의 독립적인 테스트 도구를 동시에 돌린다
SQLite 개발팀은 하나의 테스트 도구에만 의존하지 않아요. 무려 4개의 완전히 독립적인 테스트 시스템을 운영하고 있죠.
첫 번째는 TCL 테스트예요. SQLite 개발 초기부터 함께해온 전통적인 도구로, 5만 개 이상의 테스트 케이스가 들어있어요. 개발자들이 코드 한 줄 수정할 때마다 가장 먼저 돌려보는 기본 중의 기본이죠.
두 번째는 TH3라는 상용 테스트 도구인데요. 이 녀석의 진짜 무서운 점은 100% 브랜치 커버리지를 달성한다는 거예요. 쉽게 말하면 코드에서 나올 수 있는 모든 가능한 경로를 다 테스트한다는 뜻이에요. 무려 2억 4,800만 개의 테스트를 실행한다고 하니, 규모 자체가 압도적이죠.
세 번째는 SQL Logic Test예요. 이건 SQLite뿐만 아니라 PostgreSQL, MySQL, MS SQL Server 같은 다른 유명 데이터베이스들과도 결과를 비교해요. 720만 개의 쿼리를 돌려서 모두 같은 결과를 내는지 확인하는 거죠. 한 마디로 "너네도 이렇게 답 나오지? 우리도 똑같아!"를 검증하는 셈이에요.
마지막은 dbsqlfuzz라는 퍼즈 테스터인데요. 이 녀석은 하루에 약 10억 번의 테스트를 돌리면서 악의적인 공격이나 말도 안 되는 입력값에도 SQLite가 안전하게 작동하는지 확인해요. 365일 24시간 쉬지 않고 돌아가는 보안 파수꾼이죠.
3. 메모리 부족도, 디스크 오류도 전부 시뮬레이션
개발자들이 가장 두려워하는 에러 메시지 중 하나가 바로 "Out of Memory"예요. 특히 모바일 기기나 IoT 장비처럼 리소스가 제한된 환경에서는 자주 발생하는 문제거든요.
SQLite는 이런 최악의 상황까지 완벽하게 대비해요. 테스트 과정에서 일부러 메모리 할당을 실패시키고, SQLite가 어떻게 반응하는지 관찰하죠. 첫 번째 메모리 할당부터 실패시켜보고, 두 번째부터, 세 번째부터... 이런 식으로 모든 경우의 수를 다 테스트해요.
디스크 입출력 오류도 마찬가지예요. 저장 공간이 꽉 찼을 때, 하드웨어에 물리적 문제가 생겼을 때, 네트워크 파일 시스템 연결이 끊겼을 때 등등. 실제로는 발생하기 어려운 이런 상황들을 가상으로 만들어서 SQLite가 데이터 손실 없이 안전하게 에러를 처리하는지 확인하는 거죠.
더 대단한 건 여러 에러를 동시에 발생시키는 극한 테스트도 한다는 거예요. 크래시가 발생한 상태에서 메모리 부족 에러가 나고, 거기에 디스크까지 꽉 찼다면? 상상만 해도 아찔하지만, SQLite는 이런 상황까지도 전부 테스트하고 있어요.
4. 전원이 나가도 데이터는 반드시 살아남아야 한다
데이터베이스에서 가장 중요한 게 뭘까요? 바로 데이터 무결성이에요. 아무리 빠르고 기능이 많아도, 데이터가 날아가거나 손상되면 아무 의미가 없거든요.
SQLite는 크래시 테스트를 통해 이를 철저하게 검증해요. 데이터를 쓰는 중간에 갑자기 전원이 나가거나 시스템이 다운되는 상황을 시뮬레이션하는 거죠. 물론 실제로 전원 플러그를 뽑을 순 없으니까, 특수하게 설계된 가상 파일 시스템을 만들어서 테스트해요.
재미있는 건 단순히 "크래시 후에 복구되나요?"만 확인하는 게 아니라는 점이에요. 크래시 시점을 아주 세밀하게 조절하면서, 데이터 쓰기 과정의 어느 단계에서 문제가 생기든 데이터베이스가 일관성을 유지하는지 확인해요.
TH3 테스트에서는 심지어 파일 시스템의 스냅샷을 찍어놓고, 크래시 후의 상태를 여러 번 재현하면서 무작위로 손상을 가해요. 그리고 매번 데이터베이스가 정상적으로 복구되는지 체크하죠. 정말 집요하게 파고드는 거예요.
5. 하루 10억 번의 퍼즈 테스팅으로 버그를 찾는다
퍼즈 테스팅이라는 말 들어보셨나요? 쉽게 말하면 "말도 안 되는 입력값을 마구잡이로 넣어보면서 프로그램이 죽나 안 죽나 보는" 테스트예요.
SQLite는 2014년 AFL(American Fuzzy Lop)이라는 혁신적인 퍼저가 등장한 이후부터 본격적으로 퍼즈 테스팅을 도입했어요. AFL은 그냥 무작위로 입력을 만드는 게 아니라, 프로그램의 실행 경로를 분석해서 새로운 동작을 유발할 만한 입력을 똑똑하게 찾아내요.
2016년부터는 구글의 OSS-Fuzz 프로젝트에도 참여하고 있어요. 구글의 강력한 클라우드 인프라를 활용해서 24시간 내내 SQLite를 테스트하고, 문제가 발견되면 자동으로 개발자들에게 알림을 보내주죠.
그리고 SQLite만의 독자적인 퍼저인 dbsqlfuzz도 있어요. 이 녀석의 특별한 점은 SQL 쿼리만 변형시키는 게 아니라, 데이터베이스 파일 자체도 동시에 조작한다는 거예요. 덕분에 다른 퍼저들이 못 찾던 깊숙한 버그들을 발견할 수 있었죠. 현재 약 16개의 CPU 코어에서 계속 돌아가면서 하루에 5억 번 이상의 테스트를 실행하고 있다고 해요.
6. 100% 브랜치 커버리지, 그 엄격한 기준
일반적으로 소프트웨어 테스트에서 "커버리지"라고 하면 문장 커버리지를 얘기해요. 코드의 몇 퍼센트가 실행됐는지를 측정하는 거죠. 대부분의 기업들은 80~90% 정도만 달성해도 "우리 잘하고 있어!"라고 평가받아요.
근데 SQLite는 훨씬 더 엄격한 기준인 브랜치 커버리지 100%를 달성해요. 이게 뭐냐면, 코드의 모든 분기점(if문, switch문 같은 것들)에서 나올 수 있는 모든 경로를 다 테스트한다는 뜻이에요.
예를 들어 "if (a>b && c!=25)"라는 코드가 있다면요. a가 b보다 작을 때, a가 b보다 크지만 c가 25일 때, a가 b보다 크고 c가 25가 아닐 때, 이 세 가지 경우를 모두 테스트해야 해요. 문장 커버리지였다면 이 중 하나만 실행돼도 통과였겠지만, 브랜치 커버리지는 모든 경로를 요구하는 거죠.
이렇게 하면 뭐가 좋냐고요? 특정 조건에서만 발생하는 미묘한 버그들을 훨씬 더 잘 찾을 수 있어요. 평소엔 멀쩡하다가 특정 상황에서만 터지는 그런 숨어있는 버그들을 놓치지 않게 되는 거예요.
7. 방어적 코드와 테스트, 그 딜레마를 해결하는 방법
개발하다 보면 "혹시 모를 상황"에 대비해서 방어적인 코드를 넣게 돼요. 예를 들어 "이론적으로 이 값은 절대 NULL이 될 수 없지만, 만약을 위해 체크해보자" 같은 거죠.
그런데 문제가 뭐냐면요. 100% 브랜치 커버리지를 달성하려면 이런 "절대 실행되지 않을" 코드도 테스트해야 하거든요. 실행될 수 없는 코드를 어떻게 테스트해요? 모순이죠.
SQLite는 이 문제를 ALWAYS()와 NEVER()라는 똑똑한 매크로로 해결했어요. ALWAYS()는 "이 조건은 항상 참이어야 해"라는 의미고, NEVER()는 "이 조건은 절대 거짓이어야 해"라는 의미예요.
일반 빌드에서는 그냥 조건을 정상적으로 평가하지만, 테스트 중에는 이 가정이 깨지면 에러를 터뜨려요. 그리고 커버리지를 측정할 때는 아예 상수로 바꿔버려서 브랜치로 계산되지 않게 만드는 거죠. 정말 영리한 해결책이에요.
현재 SQLite 코드에는 이런 testcase() 매크로가 1,184개나 있어요. 모든 엣지 케이스를 놓치지 않으려는 개발팀의 집요함이 느껴지죠.
8. Valgrind로 메모리 문제를 완벽하게 잡아낸다
개발자들 사이에서 Valgrind는 거의 전설적인 도구예요. SQLite 개발자들은 Valgrind를 "세계에서 가장 놀랍고 유용한 개발 도구"라고 극찬할 정도죠.
Valgrind는 프로그램을 가상으로 실행시키면서 메모리 누수, 배열 범위 초과, 초기화되지 않은 메모리 읽기, 스택 오버플로우 같은 온갖 메모리 관련 문제들을 찾아내요. 그것도 문제가 발생한 정확한 지점에서 디버거를 띄워주니까, 버그 수정이 훨씬 쉬워지죠.
물론 단점도 있어요. 시뮬레이션이다 보니 실행 속도가 엄청 느려요. 일반적으로 프로그램이 30~50배 정도 느려진다고 해요. 하지만 SQLite는 모든 릴리스 전에 반드시 Valgrind로 전체 테스트를 돌려요. 속도가 느려도 찾아낼 수 있는 버그의 가치가 그만큼 크기 때문이에요.
SQLite는 또한 자체 메모리 디버깅 시스템인 memsys2도 가지고 있어요. Valgrind만큼 강력하진 않지만 훨씬 빠르기 때문에, 개발 과정에서 자주 돌려보면서 빠르게 피드백을 받을 수 있죠.
9. 컴파일러 버그까지 발견하는 테스트
정말 놀라운 사실 하나 알려드릴게요. SQLite의 테스트 과정에서 실제로 GCC, Clang, MSVC 같은 세계적으로 유명한 컴파일러들의 버그를 여러 번 발견했다고 해요.
어떻게 그게 가능하냐고요? SQLite는 테스트를 두 번 실행해요. 한 번은 gcov 같은 커버리지 측정 도구를 켠 상태로, 또 한 번은 실제 배포용 최적화 설정으로 컴파일해서요. 그리고 두 결과가 정확히 일치하는지 확인하죠.
만약 결과가 다르다면? 그건 SQLite 코드가 정의되지 않은 동작(undefined behavior)을 하고 있거나, 컴파일러에 버그가 있다는 뜻이에요. SQLite 개발자들은 이런 미묘한 차이도 절대 그냥 넘어가지 않아요.
또한 -ftrapv, -fsanitize=undefined, /RTC1 같은 다양한 컴파일러 옵션들을 사용해서 정의되지 않은 동작을 찾아내요. 32비트와 64비트, 빅엔디안과 리틀엔디안, ARM부터 x86까지 여러 CPU 아키텍처에서 반복해서 테스트하면서 모든 환경에서 동일하게 작동하는지 확인하는 거죠.
10. 200개 항목의 수동 체크리스트
아무리 자동화가 발달해도, SQLite 개발팀은 "사람의 눈"을 매우 중요하게 생각해요. 그래서 매 릴리스마다 약 200개 항목으로 구성된 상세한 체크리스트를 사용해요.
이 체크리스트는 완전히 수동으로 진행돼요. 각 개발자가 하나하나 항목을 꼼꼼히 확인하고, 테스트 결과를 직접 리뷰하면서 "이게 정말 맞는 결과일까?"를 계속 질문하죠.
왜 이렇게 시간을 들여서 수동으로 하냐고요? 자동화된 테스트는 "통과"나 "실패"만 알려줄 뿐이지만, 사람은 그 과정에서 이상한 패턴이나 미묘한 신호를 발견할 수 있거든요. 테스트는 통과했지만 뭔가 평소와 다른 경고 메시지가 나온다든지, 실행 시간이 이상하게 늘어났다든지 하는 것들이요.
이 체크리스트는 고정된 게 아니라 계속 진화해요. 새로운 문제가 발견될 때마다 관련 항목을 추가해서, 같은 실수가 반복되지 않도록 하죠. Atul Gawande의 "The Checklist Manifesto"라는 책에서 영감을 받았다고 하는데, 정말 체계적인 접근이에요.
마무리: 신뢰는 테스트에서 나온다
SQLite가 전 세계에서 가장 널리 사용되는 데이터베이스 중 하나가 된 데는 다 이유가 있어요. 실제 코드보다 590배나 많은 테스트 코드, 4개의 독립적인 테스트 시스템, 하루 10억 번 이상의 퍼즈 테스팅, 100% 브랜치 커버리지까지. 이 모든 노력이 모여서 "어떤 상황에서도 믿을 수 있는" 데이터베이스를 만들어낸 거죠.
오픈소스라고 해서 품질 관리가 부실할 거라는 편견을 완전히 깨버린 사례예요. 오히려 많은 상용 소프트웨어보다 훨씬 더 엄격하고 철저한 테스트 과정을 거치고 있거든요. 여러분의 스마트폰, 브라우저, 자동차, 심지어 비행기까지. SQLite가 조용히 세상을 떠받치고 있는 이유가 바로 이런 광적인 수준의 테스트 덕분이랍니다.
소프트웨어 개발자든, 스타트업 창업자든, 아니면 단순히 기술에 관심 있는 분이든, SQLite의 이런 자세에서 배울 점이 많아요. 신뢰는 하루아침에 만들어지는 게 아니라, 이렇게 보이지 않는 곳에서의 끊임없는 노력으로 쌓이는 거니까요.
'IT > 소프트웨어' 카테고리의 다른 글
| 테스트는 검증이 아니다, AI 시대 소프트웨어 품질의 진짜 비밀 (0) | 2026.01.26 |
|---|---|
| 영국 프리랜서 개발자가 2025년을 버틴 이야기 - David Bushell의 솔직 회고록 (1) | 2026.01.25 |
| 🚀 웹앱이 느려지는 진짜 이유, Microsoft가 밝힌다 (0) | 2026.01.25 |
| Claude Code가 너무 좋아서 생긴 문제 (0) | 2026.01.24 |
| 🔍 테스트 통과했다고 끝? 진짜 전쟁은 배포 후에 시작된다 (0) | 2026.01.24 |