본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 28. 05:20

Schemathesis를 사용하여 단 한 번의 명령으로 API 퍼징(Fuzzing)을 수행하고 Claude로 버그를 분류하는 방법

요약

Schemathesis를 활용하여 OpenAPI 스키마 기반의 자동화된 API 퍼징 테스트를 수행하는 방법을 소개합니다. 속성 기반 테스트를 통해 수동 테스트로 발견하기 어려운 경계값 오류와 서버 예외 상황을 효율적으로 탐지할 수 있습니다.

핵심 포인트

  • Schemathesis는 OpenAPI 스키마를 기반으로 수천 개의 테스트 케이스를 자동 생성합니다.
  • 속성 기반 테스트(Property-based testing)를 통해 유효/무효 데이터를 모두 검증합니다.
  • 잘못된 데이터에 대한 2xx 응답, 예상치 못한 5xx 오류, 규약 위반을 포착합니다.
  • 스키마 변경 시 테스트 케이스가 자동으로 업데이트되어 유지보수가 용이합니다.

수동 API 테스트는 당신이 예상하는 일이 일어나는지를 확인합니다. 운영 환경을 중단시키는 버그는 아무도 테스트를 작성할 생각을 하지 못한 것들입니다. 코드가 값을 가정했을 때 들어온 빈 문자열, 양수만 들어올 것이라 예상한 필드에 들어온 음수, 날짜 필드에 들어온 이모지 같은 것들 말이죠.

당신은 앉아서 손으로 깨진 입력값들을 브레인스토밍할 수 있습니다. 열 개 정도 쓰다 보면 지루해질 것입니다. 실제로 서버를 다운시키는 300번째 입력값에는 절대 도달하지 못할 것입니다.

Schemathesis가 당신을 대신해 이를 작성해 줍니다. OpenAPI 스키마를 제공하면, 스스로 수천 개의 요청을 생성하고 모든 엔드포인트(Endpoint)를 두드려 깨지는 지점을 찾아냅니다. 제가 이를 어떻게 실행하는지, 실제로 무엇을 찾아내는지, 그리고 쏟아져 나오는 결과들을 어떻게 처리하는지 소개합니다.

Schemathesis란 정확히 무엇인가

Schemathesis는 속성 기반 테스트(Property-based testing) 라이브러리인 Hypothesis를 기반으로 구축된 Python 도구입니다. 속성 기반(Property-based)이라는 것은 예시 입력값을 직접 작성하지 않는다는 것을 의미합니다. 유효한 데이터의 형태를 기술하면, 엔진이 그 형태에 부합하는(그리고 의도적으로 그 형태를 깨뜨리는) 수백 개의 케이스를 생성합니다.

당신의 OpenAPI 스키마는 이미 그 형태를 설명하고 있습니다. 모든 엔드포인트, 모든 파라미터, 모든 타입이 말이죠. Schemathesis는 이를 읽어 테스트 생성기로 변환합니다. 수동으로 유지 관리해야 할 단언문(Assertion)이 없습니다. 스키마가 변경되면 테스트도 함께 변경됩니다.

설치 방법:

pip install schemathesis

첫 번째 실행

스키마를 지정하는 단 한 줄의 명령:

st run http://localhost:8000/openapi.json

이것이 전부입니다. 스키마를 읽은 다음, 모든 엔드포인트에 수천 개의 요청을 보냅니다. 유효한 요청과 의도적으로 잘못 구성된 요청 모두를 말이죠. 기본적으로는 유효한 데이터와 유효하지 않은 데이터를 섞어서 실행하는 all 모드로 동작합니다. 음수 데이터만 생성하는 방식으로 더 강하게 밀어붙일 수도 있습니다:

st run --mode=negative http://localhost:8000/openapi.json

무엇을 잡아내는가

Schemathesis는 기본적으로 모든 응답에 대해 일련의 검사를 수행합니다. 그중 세 가지가 가장 중요한 버그들을 찾아냅니다.

조용한 2xx 응답. malformed payload를 보내면서 400 Bad Request를 기대합니다. 하지만 서버는 대신 200 OK로 응답하며 이 쓰레기 데이터를 조용히 받아들입니다. 잘못된 데이터가 데이터베이스에 저장되었거나 흔적도 없이 사라졌을 수 있습니다. 둘 다 버그이며, 행복한 경로(happy-path) 테스트에서는 어느 것도 나타나지 않습니다.**

예상치 못한 5xx 응답. 문자열 경계(huge Unicode strings, null bytes, 범위의 끝에 있는 정수 등)를 퍼징하면 단위 테스트가 지나치는 코드에서 원시 스택 트레이스(raw stack traces)와 처리되지 않은 예외(unhandled exceptions)가 드러납니다.**

규약 위반 (contract violation). 서버가 자체 OpenAPI 스키마에서 한 번도 문서화하지 않은 상태 코드나 응답 본문(response body)을 반환하는 경우입니다. status_code_conformanceresponse_schema_conformance 검사가 문서가 약속한 것과 API가 실제로 하는 것 사이의 격차를 포착합니다.

특정 부분만 실행할 수도 있습니다:

st run openapi.yaml --checks not_a_server_error,response_schema_conformance

실패 사례 읽기 (Reading a failure)

이것이 Schemathesis가 가치 있는 이유입니다. 모든 실패 사례에는 재현하기 위한 정확한 curl 명령어가 함께 제공됩니다. 수천 개의 요청 중 어떤 것이 문제를 일으켰는지 추측할 필요가 없습니다.

예약 API의 실제 예시:

- 서버 오류 (Server error)

- 문서화되지 않은 HTTP 상태 코드 (Undocumented HTTP status code)
...

"00"인 게스트 이름과 빈 room_type을 사용하여 POST /bookings가 500 에러로 충돌합니다. 이 명령어를 복사하여 터미널에 붙여넣기만 하면 버그가 매번 재현됩니다. 이 줄을 해당 엔드포인트의 개발자에게 바로 전달하세요.

노이즈 문제 (The noise problem)

Schemathesis를 처음 실행하면 수백 개의 발견 사항(findings)이 나타날 것입니다. 당황하지 말고, 그 모든 것을 신뢰해서도 안 됩니다.

negative_data_rejection 검사는 알려진 오탐지(false positives)의 원천입니다. Schemathesis는 malformed 요청(예: 빈 쿼리 매개변수)을 보내고 4xx를 기대합니다. 하지만 FastAPI를 포함한 많은 프레임워크들은 단순히 빈 매개변수를 무시하고 200 OK를 반환할 뿐입니다. 이 경우 아무것도 실제로 고장 나지 않았음에도 불구하고 Schemathesis는 이를 실패로 표시합니다.

따라서 첫 번째 단계는 주로 분류(triage) 작업입니다. 즉, 실제 크래시(crash)와 프로토콜상의 사소한 문제(nitpicks)를 분리하는 것입니다. 수백 개의 발견 사항을 일일이 수동으로 분류하다 보면 오후 시간 전체를 허비하게 됩니다.

Claude를 활용한 대량의 데이터 분류

이 지점에서 저는 AI 레이어를 도입합니다. Schemathesis의 전체 보고서를 가져와 Claude에게 간단한 지침과 함께 전달합니다: 발견 사항을 그룹화하고, 빈 파라미터와 같은 오탐(false positives)은 제거하며, 남은 항목들을 심각도(severity)에 따라 순위를 매기라는 지침입니다.

Claude는 중복 항목을 압축하고(하나의 근본 원인이 20개의 서로 다른 요청 형태(request shapes)로 나타나는 경우가 많습니다), 프레임워크 수준의 노이즈를 제거한 뒤 짧은 목록을 반환합니다: "이 세 가지는 실제 서버 크래시이므로 먼저 수정하십시오. 나머지는 negative_data_rejection 패턴이므로 무시해도 안전합니다."

오후 내내 걸리던 수동 분류 작업이 단 몇 분 만에 끝납니다. 머신은 공격(attacks)을 생성하고, AI는 노이즈를 제거하며, 저는 스택 트레이스(stack traces)를 읽는 대신 실제 버그를 수정하는 데 집중할 수 있게 됩니다.

CI에 연결하기

CI(지속적 통합) 환경에서 퍼저(fuzzer)를 무작위 상태로 두는 것은 위험합니다. 생성기(generator)가 새로운 케이스를 우연히 발견했다는 이유만으로 오늘은 통과하고 내일은 실패하는 파이프라인은, 모든 팀원이 해당 테스트를 무시하도록 길들여 버립니다.

실행 결과가 재현 가능하도록 시드(seed)를 고정하십시오:

uvx schemathesis run --generation-deterministic http://localhost:8000/openapi.json

uvx는 의존성 설정 없이 격리된 일회성 환경에서 실행되는데, 이는 CI에서 정확히 원하는 방식입니다. 결정론적(deterministic) 플래그를 사용하면 동일한 코드에 대해 매번 동일한 결과가 나옵니다. 완전히 무작위인 실행은 야간 크론 잡(nightly cron job)을 위해 남겨두십시오. 그곳에서의 새로운 실패는 빌드가 깨진 것이 아니라 조사해야 할 신호가 됩니다.

Schemathesis는 또한 JUnit XML을 출력하므로, 결과가 팀에서 이미 사용 중인 대시보드에 바로 반영됩니다.

활용 위치

만약 Dredd를 사용해 보셨다면, Schemathesis는 그 다음 단계입니다. Dredd는 API가 문서의 예시와 일치하는지 검증합니다. 하지만 부정 테스트(negative tests)를 생성하거나 퍼징(fuzzing)을 수행하지는 않습니다. Schemathesis는 시스템을 망가뜨리는 입력을 적극적으로 찾아냅니다.

pytest나 Postman에서 수동으로 어설션 (assertions)을 작성하는 것과 비교하면, 그 대가는 명확합니다. 해당 도구들은 모든 검증 사항을 직접 작성하고 유지 관리하도록 만듭니다. 반면 Schemathesis는 OpenAPI 스펙을 단일 진실 공급원 (single source of truth)으로 취급합니다. 스펙을 업데이트하면 테스트 범위 (test surface)도 함께 업데이트됩니다.

요약 버전

수동 테스트는 당신이 상상한 입력값들을 다룹니다. Schemathesis는 당신이 상상하지 못한 입력값들을 다루며, 발견된 모든 버그에 대해 재현 가능한 curl 명령어를 생성하고, AI 패스 (AI pass)를 통해 노이즈가 섞인 출력물을 깔끔한 수정 목록으로 변환합니다. 취약점을 찾기 위한 단 하나의 명령, CI (지속적 통합)의 신뢰성을 유지하기 위한 결정론적 시드 (deterministic seed), 그리고 지속적인 탐색을 위한 야간 카오스 실행 (nightly chaos run)이 준비되어 있습니다.

만약 API를 테스트하고 있다면, 스키마 (schema)를 한 번 지정해 보고 무엇이 튀어나오는지 확인해 보세요. 첫 번째 실행은 대개 유용한 방식으로 당혹스러울 것입니다.

댓글에 여러분의 스택 (stack)을 남겨주시면 어디서부터 시작해야 할지 알려드리겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0