초록색 상태 코드는 거짓말을 합니다. POST /orders 엔드포인트는 201 Created를 반환하고, 응답 본문은 완벽하며, 테스트도 통과합니다. 하지만 해당 행이 올바른 상태로 데이터베이스에 실제로 저장되었을까요? 재고 수량은 감소했을까요? HTTP 응답만 읽는 테스트는 API가 *말한* 것을 확인하는 것이지, 시스템이 *수행한* 것을 확인하는 것이 아닙니다. 이 간극을 메우려면 데이터베이스 자체를 살펴봐야 합니다.
바로 이 지점에서 테스트 시나리오 내의 데이터베이스 쿼리가 그 역할을 톡톡히 해냅니다. 요청 전에 알려진 상태를 시드하고, 요청을 실행한 다음, 테이블을 쿼리하여 디스크의 실제 데이터를 확인합니다. Apidog는 데이터베이스 연결(Database Connections) 및 데이터베이스 작업 프로세서(Database Operation processor)를 통해 이 기능을 내장하고 있으므로, HTTP 호출을 구동하는 동일한 시나리오에서 외부 스크립팅 없이 SQL 또는 NoSQL 명령을 단계로 실행할 수 있습니다. 시나리오 구축이 처음이라면 Apidog로 테스트 시나리오를 작성하는 방법에 대한 가이드가 이 글에서 다루는 요청 수준의 기본 사항을 설명합니다. 관계형 저장소가 이 데이터를 모델링하는 방법에 대한 복습을 원한다면 MDN의 서버 측 개요가 훌륭한 입문서가 될 것입니다.
테스트 내 데이터베이스 작업이 제공하는 이점
데이터베이스에 전혀 접근하지 않는 API 테스트는 블랙박스입니다. 응답을 신뢰합니다. 대부분의 경우 괜찮지만, 흥미로운 버그는 API가 반환하는 것과 실제로 지속하는 것 사이의 간극에 존재합니다: 상태 필드가 절대 변경되지 않거나, 외래 키가 아무 곳도 가리키지 않거나, 소프트 삭제가 하드 삭제가 되는 경우 등입니다.
데이터베이스 단계는 순수 HTTP 테스트로는 할 수 없는 세 가지를 가능하게 합니다:
- 정확한 시작 상태를 **시드**하여 테스트가 남아있는 데이터에 좌우되지 않도록 합니다.
- API가 작성했다고 주장하는 행을 읽어서 **실제 데이터에 대해 단언**합니다.
- 데이터베이스에서 **실제 값을 추출**하여 다음 요청에 공급함으로써, 테스트가 추측 대신 서버가 실제로 생성한 ID를 사용하도록 합니다.
Apidog는 이 기능을 두 가지로 나눕니다. 먼저 설정(Settings) > 데이터베이스 연결(Database Connections)에서 재사용 가능한 연결을 생성합니다. 그런 다음 데이터베이스 작업(Database Operation) 단계를 요청에 사전 프로세서(Pre Processor, 요청 전에 실행) 또는 사후 프로세서(Post Processor, 요청 후에 실행)로 첨부합니다. 하나의 연결을 프로젝트의 모든 시나리오에서 재사용합니다.
계획하기 전에 커버리지에 대한 참고 사항입니다. MySQL, SQL Server (2014 이상), PostgreSQL, Oracle은 무료 플랜에서 작동합니다. ClickHouse, MongoDB, Redis는 유료입니다. MongoDB 문서는 명확히 말합니다: MongoDB 연결은 유료 기능입니다. Redis도 마찬가지입니다. 따라서 아래 MySQL 설명은 무료 티어에서 실행되며, Mongo 및 Redis 섹션은 유료 플랜이 필요합니다.
단계 1: 데이터베이스 연결 생성
설정(Settings) > 데이터베이스 연결(Database Connections)을 열고 오른쪽 상단의 **+ 새로 만들기(+ New)**를 클릭합니다. 데이터베이스 유형을 선택한 다음 연결 필드를 채웁니다:
- **호스트(Host)** (예: `db.staging.internal` 또는 `127.0.0.1`)
- **포트(Port)** (MySQL의 경우 `3306`)
- **사용자 이름(Username)** 및 **비밀번호(Password)**
- **데이터베이스 이름(Database Name)** (예: `shop`)

데이터베이스가 배스천 뒤에 있다면 **SSH 터널(SSH Tunnel)** 섹션을 확장하고 점프 호스트 세부 정보를 추가합니다. MySQL은 또한 `Prefer` (기본값, SSL 시도 후 실패 시 폴백), `Require`, `Verify CA`, `Verify Full`의 네 가지 옵션을 가진 SSL 모드를 노출합니다. 서버가 지원하는 가장 엄격한 옵션을 선택합니다. 그런 다음 **저장(Save)**을 클릭합니다.
두 가지 연결 함정이 혼란스러운 오후를 절약해 줄 것입니다:
- **MySQL 8 인증.** 기본 `caching_sha2_password` 플러그인이 연결을 거부할 수 있습니다. 인증 오류가 발생하면 `ALTER USER 'tester'@'%' IDENTIFIED WITH mysql_native_password BY '...';` 명령으로 사용자를 전환하고 다시 시도하십시오. MySQL 참조 매뉴얼은 플러그인 차이를 심층적으로 다룹니다.
- **자격 증명은 로컬입니다.** 연결 세부 정보는 사용자 자신의 컴퓨터에 저장되며 클라우드에 동기화되지 않습니다. 모든 팀원이 직접 연결을 구성합니다. 팀을 조율하는 경우 데이터베이스 연결 설정 공유에 대한 노트가 워크플로우를 설명합니다.
단계 2: 사전 프로세서로 데이터 시드
주문 생성 흐름을 테스트한다고 가정해 봅시다. 주문을 생성하기 전에 알려진 고객이 존재해야 하므로, 테스트가 테이블에 있는 내용에 의존하지 않게 됩니다.
요청을 열고 **사전 프로세서(Pre Processors)**를 찾아 **데이터베이스 프로세서 추가(Add Database Processor)** 위로 마우스를 올린 후 **데이터베이스 작업(Database operation)**을 선택합니다. 이름을 `seed customer`와 같이 지정하고, MySQL 연결을 선택한 다음 **SQL 명령 입력(Enter SQL Command)** 아래에 삽입 쿼리를 작성합니다. 동적 변수는 `{{variable_name}}` 구문을 사용하므로 환경에서 직접 값을 가져올 수 있습니다:
INSERT INTO customers (id, email, status)
VALUES ({{customer_id}}, '{{customer_email}}', 'active')
ON DUPLICATE KEY UPDATE status = 'active';
이제 요청은 보장된 상태에 대해 실행됩니다. 고객은 존재하고, 활성 상태이며, 나중에 오는 단계에서 이미 알고 있는 ID를 가집니다. 사전 프로세서에서 시드하는 것은 테스트 순서의 부작용에 의존하는 대신 각 테스트를 자체 포함적으로 유지합니다.
단계 3: 사후 프로세서로 데이터베이스에 대해 단언
이것이 핵심입니다. 요청이 주문을 생성합니다. 반환된 후, `orders` 테이블을 쿼리하여 올바른 상태로 행이 존재하는지 확인합니다.
API를 호출하는 요청을 추가합니다:
POST /api/orders
Content-Type: application/json
{
"customer_id": {{customer_id}},
"items": [{ "sku": "APRON-01", "qty": 2 }]
}
응답 본문에 새 주문 ID가 포함되어 있다고 가정하고, 일반적인 응답 단언으로 `order_id`라는 변수에 캡처합니다. 이제 해당 요청에서 **사후 프로세서(Post Processors)**를 엽니다. 디자인 모드(DESIGN Mode)에서는 **실행(Run)** 탭에서 찾을 수 있습니다. 디버그 모드(DEBUG Mode)에서는 **요청(Request)** 탭에 있습니다. **사후 프로세서 추가(Add PostProcessor)** 위로 마우스를 올리고, **데이터베이스 작업(Database operation)**을 선택하고, 이름을 `verify order row`로 지정한 다음, 연결을 선택합니다.
**작업 구성(Configure Operation)** 아래에서 작업을 선택하고 SQL을 입력합니다:
SELECT id, status, total_cents
FROM orders
WHERE id = {{order_id}};
쿼리 결과는 행당 하나의 객체로 구성된 배열로 반환됩니다. 단일 필드를 추출하려면 **결과 추출(선택 사항)(Extract Results (Optional))**을 열고 **변수로 결과 추출(Extract Result To Variable)** 항목을 추가합니다. `db_order_status`와 같은 **변수 이름(Variable Name)**과 첫 번째 행에서 필드를 분리하는 **JSONPath 표현식(JSONPath Expression)**을 설정합니다:
$[0].status
**전송(Send)**을 클릭하고 **콘솔(Console)**을 확인하여 원시 결과와 추출된 값을 봅니다. 이제 `db_order_status`는 디스크에 실제로 있는 값을 가집니다. `pending` (또는 API가 설정해야 하는 값)과 같다는 단언을 추가하면, 테스트는 HTTP 에코뿐만 아니라 최종적으로 지속성(persistence)을 검증합니다. API가 `201`을 반환했지만 `status = 'draft'`를 작성했다면, 이 단계에서 그것을 잡아낼 수 있습니다.
단계 4: 데이터베이스 값 추출 및 하위 스트림에서 사용
추출은 단언만을 위한 것이 아닙니다. 종종 데이터베이스는 응답이 결코 반환하지 않는 값을 가지고 있으며, 다음 요청에 필요할 수 있습니다.
주문을 생성할 때 서버에서 내부 `fulfillment_ref`가 생성되어 행에 기록되지만 API 응답에서는 생략되는 흐름을 상상해 보세요. 다음 요청 `GET /api/fulfillments/{ref}`에는 이 값이 필요합니다. 사후 프로세서에서 이를 쿼리합니다:
SELECT fulfillment_ref
FROM orders
WHERE id = {{order_id}};
**변수 이름(Variable Name)** `fulfillment_ref` 및 **JSONPath 표현식(JSONPath Expression)** `$[0].fulfillment_ref`를 사용하여 추출합니다. Apidog가 해당 변수를 환경에 따라 범위 지정하므로, 다음 요청은 경로 또는 본문에서 `{{fulfillment_ref}}`를 참조하여 실제 서버 생성 값을 가져옵니다. 이는 테스트 단계 간 데이터 전달 및 API 테스트 오케스트레이션 및 데이터 전달에서 다루는 HTTP 응답 연결과 동일한 패턴이지만, 진실의 원천이 JSON 본문 대신 테이블이라는 점만 다릅니다.
MongoDB 및 Redis: NoSQL 변형
이 프로세서는 NoSQL 저장소에서도 동일하게 작동하지만, 구성 인터페이스가 다릅니다. 둘 다 유료 기능이므로 이에 맞춰 계획하십시오. Apidog 문서에는 필요한 경우 전체 필드 참조가 있습니다.
MongoDB
데이터베이스 작업 단계를 추가하고 유형으로 MongoDB를 선택합니다. 원시 SQL 대신, 찾기(Find), 삽입(Insert), 업데이트(Update), 삭제(Delete) 및 **데이터베이스 명령 실행(Run Database Command)**이 포함된 **작업 유형(Operation Type)** 드롭다운이 제공됩니다. 모든 CRUD 작업의 경우 **컬렉션 이름(Collection Name)**은 필수입니다. **쿼리 조건(Query Condition)** 필드는 JSON을 받습니다:
{ "_id": "65486728456e79993a150f1c" }
Apidog는 일치하는 ID 문자열을 ObjectId로 자동으로 변환하므로 직접 래핑할 필요가 없습니다. BSON 유형이 필요한 경우 `ISODate(...)`, `ObjectId(...)`, `NumberDecimal(...)`, `NumberLong(...)` 헬퍼가 지원됩니다. MongoDB 매뉴얼은 이들 각각이 저장된 값에 어떻게 매핑되는지 문서화합니다. 연결은 전체 연결 문자열 또는 개별 호스트 및 자격 증명 구성 요소를 허용합니다.
한 가지 솔직한 참고 사항: MySQL 흐름은 JSONPath 변수 추출을 문서화하지만, MongoDB 및 Redis 문서는 동일한 **변수로 결과 추출(Extract Result To Variable)** 메커니즘을 설명하지 않습니다. 따라서 상태 시드 및 확인을 위해 Mongo 작업에 의존하고, 콘솔에서 확인하기 전까지는 SQL 스타일 추출 경로가 동일하게 작동한다고 가정하지 마십시오.
Redis
Redis 연결은 **호스트(Host)**, **포트(Port)**, **비밀번호(Password)**, **데이터베이스 인덱스(Database Index)**를 요청합니다. 시각적 작업은 GET, SET, DELETE를 지원하는 **작업 유형(Operation Type)** 드롭다운을 사용합니다. 캐시된 세션을 읽으려면 GET을 선택하고 **키(Key)**를 `user:session:123`과 같이 설정합니다. 드롭다운이 다루지 않는 모든 것에 대해 **Redis 명령 실행(Run Redis Command)** 탭은 유효한 명령을 실행합니다:
KEYS user:*
이는 API가 작성했어야 하는 캐시 항목을 확인하거나, 엔드포인트가 다시 채우는지 증명하기 위해 테스트 전에 캐시 항목을 지우는 방법입니다.
고급 변형 및 제한 사항
대규모 스위트를 구축하기 전에 알아두면 좋은 몇 가지 사항:
- **루프(Loops).** ForEach 단계에서 행을 반복할 때, `{{$.StepID.element.field}}`를 사용하여 현재 루프 항목을 참조합니다. 여기서 `StepID`는 루프 단계의 실제 번호입니다. 각 반복마다 한 행을 단언하는 데 유용합니다.
- **DB 값에 따른 분기(Branching on a DB value).** 상태 필드를 추출한 다음, 그에 따라 시나리오의 나머지 부분을 라우팅합니다. 데이터베이스 읽기를 API 테스트 시나리오의 조건부 로직과 결합하면, 행이 `paid`일 때 한 경로를 취하고 `pending`일 때 다른 경로를 취할 수 있습니다.
- **환경 라우팅(Environment routing).** 아래에서 더 자세히 설명하겠지만, 요약하면: 환경별로 하나의 연결을 정의하면 Apidog가 자동으로 올바른 연결을 선택합니다.
- **저장 프로시저(Stored procedures).** 시각적 인터페이스는 저장 프로시저와 같은 복잡한 작업을 처리하지 않습니다. 단계의 SQL은 간단한 문장으로 유지하십시오.
- **Oracle 설정(Oracle setup).** Oracle은 연결하기 전에 컴퓨터에 별도의 Oracle Client가 설치되어 있어야 합니다.
환경별 자격 증명 처리
실수로 프로덕션 데이터에 테스트가 영향을 주는 것을 결코 원하지 않을 것입니다. Apidog의 해결책은 환경별로 하나의 데이터베이스 연결을 사용하는 것입니다. `staging` 연결과 `local` 연결을 생성하며, 각각 자체 호스트와 비밀번호를 가집니다.
그런 다음 오른쪽 상단의 드롭다운을 사용하여 환경을 전환합니다. Apidog는 시나리오의 모든 쿼리를 현재 선택된 환경과 일치하는 연결로 자동 라우팅합니다. `staging`을 선택하면 `SELECT`가 스테이징에 대해 실행되고, `local`로 전환하면 SQL 또는 단계 변경 없이 정확히 동일한 단계가 노트북의 데이터베이스에 대해 실행됩니다. 실행 중에 수동으로 재구성할 필요 없이 환경별로 자격 증명이 다릅니다.
이러한 자격 증명은 로컬에 저장되고 동기화되지 않으므로, 프로덕션 비밀번호가 공유 클라우드 프로젝트에 노출되지 않도록 합니다. 각 엔지니어는 자신의 자격 증명을 관리합니다.
Apidog CLI로 워크플로우 자동화
시나리오가 앱에서 통과하면, CI에서 헤드리스로 실행하여 모든 풀 리퀘스트가 HTTP 계약뿐만 아니라 데이터베이스도 다시 확인하도록 합니다. CLI를 설치하고 인증합니다:
npm install -g apidog-cli
apidog login --with-token <YOUR_ACCESS_TOKEN>
그런 다음 구축한 정확한 시나리오를 ID로 지정하여 선택한 환경에 대해 실행합니다:
apidog run --access-token $APIDOG_ACCESS_TOKEN -t <scenario_id> -e <env_id> -r cli
여기서 `-t`는 테스트 시나리오 ID이고, `-e`는 환경 ID이며, `-r`은 리포터입니다 (`cli`, `html`, 또는 `junit`; 여러 개를 사용하려면 `-r html,cli`와 같이 쉼표로 구분). 한 가지 계획할 사항: 데이터베이스 연결 세부 정보는 로컬이므로, CI에서 데이터베이스에 접근하려면 러너에게 내보낸 구성이 필요합니다. 데이터셋에서 시나리오 행을 공급하는 경우, Apidog CLI를 사용한 데이터 기반 테스트는 각 행이 동일한 데이터베이스 단언을 실행하는 방법을 보여주며, Apidog를 사용한 API 테스트 스케줄링은 주기적으로 실행하여 실패하는 데이터베이스 단언이 다른 테스트와 마찬가지로 드러나도록 하는 방법을 다룹니다.
자주 묻는 질문
**어떤 데이터베이스가 무료이고 어떤 것이 유료인가요?** MySQL, SQL Server (2014 이상), PostgreSQL, Oracle은 무료 플랜에 포함됩니다. ClickHouse, MongoDB, Redis는 유료 플랜이 필요합니다. MongoDB 및 Redis 문서는 모두 연결이 유료 기능이라고 명시하고 있으므로, NoSQL 스위트를 계획하기 전에 가격 페이지를 확인하십시오. Apidog를 다운로드하여 무료 데이터베이스를 먼저 사용해 보십시오.
**데이터베이스의 값을 나중에 요청에 사용할 수 있나요?** 예. 데이터베이스 작업(Database operation)을 포함하는 사후 프로세서(Post Processor)를 추가하고 `SELECT`를 실행한 다음, 변수 이름(Variable Name)과 `$[0].fulfillment_ref`와 같은 JSONPath 표현식(JSONPath Expression)을 사용하여 첫 번째 행에서 필드를 분리하여 **변수로 결과 추출(Extract Result To Variable)**을 사용합니다. 나중에 `{{variable_name}}`으로 참조하십시오. HTTP 응답에 대한 동일한 연결 아이디어는 테스트 단계 간 데이터 전달에서 다룹니다.
**팀원들이 내 데이터베이스 연결을 자동으로 얻을 수 있나요?** 아니요. 연결 자격 증명은 각 클라이언트에 로컬로 저장되며 클라우드에 동기화되지 않으므로, 모든 팀 구성원이 직접 연결을 구성해야 합니다. 이는 의도적인 것으로, 프로덕션 비밀번호가 공유 프로젝트에 노출되지 않도록 합니다.
**내 MySQL 8 연결이 계속 실패합니다. 왜 그런가요?** MySQL 8은 기본적으로 `caching_sha2_password` 인증 플러그인을 사용하며, 이는 연결을 차단할 수 있습니다. `ALTER USER ... IDENTIFIED WITH mysql_native_password` 명령으로 사용자를 `mysql_native_password`로 전환하고 다시 연결하십시오.
**저장 프로시저나 복잡한 데이터베이스 로직을 실행할 수 있나요?** 시각적 인터페이스를 통해서는 안 됩니다. 표준 SELECT, INSERT, UPDATE, DELETE 문은 처리하지만, 저장 프로시저와 같은 복잡한 작업은 지원되지 않습니다. 테스트 단계를 직접적인 문장으로 유지하십시오.
마무리
데이터베이스 쿼리는 API 테스트를 "응답이 올바르게 보였다"에서 "데이터가 실제로 정확하다"로 바꿉니다. 사전 프로세서(Pre Processor)에서 알려진 상태를 시드하고, 사후 프로세서(Post Processor)에서 지속된 행을 확인하며, 서버에서 생성된 값을 추출하여 다음 요청에 연결하는 모든 과정을 하나의 시나리오 안에서 수행합니다. 환경별로 연결을 설정하면 동일한 단계가 편집 없이 스테이징 또는 로컬에 대해 안전하게 실행됩니다.
신용 카드 없이 무료로 사용해 보세요: Apidog를 다운로드하고, 개발 데이터베이스에 MySQL 연결을 설정한 다음, 다음 요청이 생성하는 행을 다시 읽는 사후 프로세서를 하나 추가해 보세요.
