스토리텔링 형식으로 업데이트되었습니다! 실무 사례와 함께 더 쉽게 이해할 수 있어요.
이미지 로딩 중...
AI Generated
2025. 11. 25. · 3 Views
API 테스트 전략과 자동화 완벽 가이드
API 개발에서 필수적인 테스트 전략을 단계별로 알아봅니다. 단위 테스트부터 부하 테스트까지, 실무에서 바로 적용할 수 있는 자동화 기법을 익혀보세요.
목차
- API_테스트_유형
- Jest와_Mocha로_단위_테스트
- Postman과_Newman_자동화
- 계약_테스트_Contract_Testing
- 부하_테스트_k6_JMeter
- CI_CD_파이프라인_통합
1. API_테스트_유형
김개발 씨가 처음으로 REST API를 만들었습니다. 자신 있게 배포했는데, 다음 날 아침 슬랙에 장애 알림이 쏟아졌습니다.
"테스트는 해봤어요?" 선배의 질문에 김개발 씨는 말문이 막혔습니다.
API 테스트는 크게 단위 테스트, 통합 테스트, E2E 테스트 세 가지로 나뉩니다. 마치 자동차를 검사할 때 부품 하나하나를 점검하고, 부품들이 잘 조립되었는지 확인하고, 마지막으로 실제 도로에서 주행해보는 것과 같습니다.
각 테스트는 서로 다른 목적을 가지며, 함께 사용할 때 완벽한 품질을 보장합니다.
다음 코드를 살펴봅시다.
// 단위 테스트: 함수 하나만 검증
function calculateDiscount(price, rate) {
return price * (1 - rate);
}
test('할인 계산이 정확해야 한다', () => {
expect(calculateDiscount(10000, 0.1)).toBe(9000);
});
// 통합 테스트: DB와 연동된 서비스 검증
test('사용자 생성 후 조회가 되어야 한다', async () => {
const user = await userService.create({ name: '김개발' });
const found = await userService.findById(user.id);
expect(found.name).toBe('김개발');
});
// E2E 테스트: 실제 HTTP 요청으로 검증
test('POST /users가 201을 반환해야 한다', async () => {
const res = await request(app).post('/users').send({ name: '김개발' });
expect(res.status).toBe(201);
});
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 첫 번째 API 프로젝트를 성공적으로 마쳤다고 생각했는데, 배포 후 예상치 못한 버그들이 터져 나왔습니다.
분명히 로컬에서는 잘 동작했는데 말이죠. 팀장님이 조용히 물었습니다.
"김개발 씨, 테스트 코드는 작성했나요?" 김개발 씨는 고개를 저었습니다. "API 테스트요?
포스트맨으로 한 번 호출해봤는데요..." 그렇다면 API 테스트란 정확히 무엇일까요? 쉽게 비유하자면, API 테스트는 마치 새 집을 지은 후 검사하는 과정과 같습니다.
단위 테스트는 벽돌 하나하나가 튼튼한지 확인하는 것입니다. 통합 테스트는 벽돌들이 모여 벽이 제대로 세워졌는지 검사합니다.
E2E 테스트는 완성된 집에서 실제로 살아보며 모든 것이 정상인지 확인하는 것이죠. 테스트 없이 개발하던 시절에는 어땠을까요?
개발자들은 코드를 수정할 때마다 불안에 떨어야 했습니다. "이 부분 고치면 저쪽이 망가지지 않을까?" 하는 걱정이 끊이지 않았죠.
더 큰 문제는 버그를 사용자가 먼저 발견한다는 것이었습니다. 회사의 신뢰도는 떨어지고, 개발자는 야근에 시달렸습니다.
바로 이런 문제를 해결하기 위해 체계적인 테스트 전략이 등장했습니다. 단위 테스트는 가장 작은 단위인 함수나 메서드를 검증합니다.
실행 속도가 빠르고, 문제가 생겼을 때 원인을 찾기 쉽습니다. 하지만 함수 간의 상호작용은 확인할 수 없다는 한계가 있죠.
통합 테스트는 여러 모듈이 함께 동작하는지 확인합니다. 데이터베이스 연결, 외부 API 호출 같은 것들이 제대로 작동하는지 검증합니다.
단위 테스트보다는 느리지만, 실제 환경에 더 가깝습니다. E2E 테스트는 사용자 관점에서 전체 시스템을 검증합니다.
실제 HTTP 요청을 보내고, 응답을 확인합니다. 가장 현실적이지만, 실행 시간이 길고 유지보수가 어렵습니다.
실제 현업에서는 이 세 가지를 테스트 피라미드 형태로 구성합니다. 단위 테스트를 가장 많이 작성하고, 통합 테스트는 중간 정도, E2E 테스트는 핵심 기능에만 적용합니다.
주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수는 E2E 테스트만 작성하는 것입니다.
"어차피 전체를 테스트하니까 괜찮겠지"라고 생각하지만, 실행 시간이 너무 길어지고 어디서 문제가 생겼는지 파악하기 어렵습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
팀장님의 조언을 들은 김개발 씨는 테스트 코드 작성을 시작했습니다. 처음에는 시간이 더 걸리는 것 같았지만, 버그가 현저히 줄어들었습니다.
"테스트가 이렇게 중요한 거였군요!"
실전 팁
💡 - 테스트 피라미드를 기억하세요: 단위 70%, 통합 20%, E2E 10%
- 새 기능을 개발할 때 테스트부터 작성하는 TDD를 시도해보세요
2. Jest와_Mocha로_단위_테스트
박시니어 씨가 김개발 씨에게 물었습니다. "단위 테스트 프레임워크는 뭘 써봤어요?" 김개발 씨는 Jest라는 이름은 들어봤지만, 실제로 사용해본 적이 없었습니다.
"테스트 프레임워크가 왜 필요한 거죠?"
Jest와 Mocha는 JavaScript에서 가장 많이 사용되는 테스트 프레임워크입니다. 마치 요리할 때 기본 도구가 필요하듯, 테스트를 작성하려면 테스트를 실행하고 결과를 보여주는 도구가 필요합니다.
Jest는 설정이 간편하고 모든 것이 내장되어 있으며, Mocha는 유연하게 원하는 라이브러리를 조합할 수 있습니다.
다음 코드를 살펴봅시다.
// Jest로 작성한 API 서비스 단위 테스트
const userService = require('./userService');
describe('UserService', () => {
// 각 테스트 전에 데이터 초기화
beforeEach(() => {
jest.clearAllMocks();
});
test('유효한 이메일로 사용자를 찾아야 한다', async () => {
const mockUser = { id: 1, email: 'test@example.com' };
userService.findByEmail = jest.fn().mockResolvedValue(mockUser);
const result = await userService.findByEmail('test@example.com');
expect(result).toEqual(mockUser);
expect(userService.findByEmail).toHaveBeenCalledWith('test@example.com');
});
test('존재하지 않는 이메일은 null을 반환해야 한다', async () => {
userService.findByEmail = jest.fn().mockResolvedValue(null);
const result = await userService.findByEmail('nobody@example.com');
expect(result).toBeNull();
});
});
김개발 씨는 테스트의 중요성을 깨달았지만, 어디서부터 시작해야 할지 막막했습니다. 테스트 코드를 어떻게 작성하고, 어떻게 실행하는 걸까요?
박시니어 씨가 화면을 보여주며 설명을 시작했습니다. "테스트 프레임워크는 말 그대로 테스트를 도와주는 도구예요." 그렇다면 테스트 프레임워크란 정확히 무엇일까요?
쉽게 비유하자면, 테스트 프레임워크는 마치 시험지를 채점해주는 채점 기계와 같습니다. 우리가 정답을 적어두면, 학생의 답안과 비교해서 맞았는지 틀렸는지 알려주죠.
테스트 프레임워크도 우리가 기대하는 결과를 적어두면, 실제 코드의 결과와 비교해줍니다. 테스트 프레임워크 없이 테스트하던 시절에는 어땠을까요?
개발자들은 console.log로 값을 찍어보며 눈으로 확인해야 했습니다. 테스트할 항목이 100개라면 100번을 눈으로 확인해야 했죠.
실수하기도 쉬웠고, 자동화는 꿈도 꿀 수 없었습니다. 바로 이런 불편함을 해결하기 위해 Jest와 Mocha가 등장했습니다.
Jest는 Facebook에서 만든 프레임워크로, "제로 설정"이 특징입니다. 설치만 하면 바로 사용할 수 있습니다.
테스트 실행기, 단언문 라이브러리, 모킹 기능이 모두 내장되어 있어서 초보자가 시작하기 좋습니다. Mocha는 더 오래된 프레임워크로, 유연성이 장점입니다.
단언문 라이브러리는 Chai를, 모킹은 Sinon을 따로 선택해서 사용합니다. 마치 레고 블록처럼 원하는 조합을 만들 수 있죠.
위의 코드를 살펴보겠습니다. describe 블록은 관련된 테스트들을 묶어주는 역할을 합니다.
test 또는 it 함수 안에 실제 테스트 로직을 작성합니다. expect는 기대하는 결과를 선언하고, toBe, toEqual 같은 매처로 비교합니다.
**jest.fn()**은 가짜 함수를 만들어줍니다. 데이터베이스나 외부 API를 실제로 호출하지 않고도 테스트할 수 있게 해주죠.
이것을 **모킹(Mocking)**이라고 합니다. 실제 현업에서는 어떻게 활용할까요?
쇼핑몰 서비스에서 주문 금액을 계산하는 함수를 테스트한다고 가정해봅시다. 상품 가격, 배송비, 할인율 등 다양한 조건을 조합해서 테스트 케이스를 작성합니다.
엣지 케이스(경계값)도 꼭 포함해야 합니다. 주의할 점도 있습니다.
테스트 코드도 유지보수 대상입니다. 너무 복잡하게 작성하면 나중에 수정하기 어렵습니다.
테스트 하나가 하나의 기능만 검증하도록 작성하세요. 김개발 씨는 Jest로 첫 테스트 코드를 작성했습니다.
초록색 체크 표시가 뜨는 순간, 묘한 성취감이 밀려왔습니다. "이거...
재밌는데요?"
실전 팁
💡 - Jest를 처음 시작한다면 npx jest --init으로 설정 파일을 자동 생성하세요
- 테스트 파일명은 *.test.js 또는 *.spec.js 규칙을 따르세요
3. Postman과_Newman_자동화
김개발 씨는 Postman으로 API를 호출해보는 건 익숙했습니다. 하지만 매번 손으로 버튼을 누르는 게 귀찮아졌습니다.
"이거 자동으로 할 수 없나요?" 박시니어 씨가 웃으며 대답했습니다. "Newman이라는 게 있어요."
Postman은 API를 테스트하는 GUI 도구이고, Newman은 Postman 컬렉션을 명령줄에서 실행하는 도구입니다. 마치 자동차의 수동 운전과 자율 주행의 차이와 같습니다.
Postman에서 테스트를 만들고, Newman으로 자동화하면 CI/CD 파이프라인에서도 API 테스트를 실행할 수 있습니다.
다음 코드를 살펴봅시다.
// Postman 컬렉션의 테스트 스크립트 예시
// Tests 탭에 작성하는 코드입니다
// 응답 상태 코드 검증
pm.test("상태 코드가 200이어야 한다", function () {
pm.response.to.have.status(200);
});
// 응답 시간 검증
pm.test("응답 시간이 500ms 이하여야 한다", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});
// JSON 응답 구조 검증
pm.test("응답에 사용자 정보가 포함되어야 한다", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('id');
pm.expect(jsonData).to.have.property('email');
pm.expect(jsonData.email).to.include('@');
});
// 환경 변수에 값 저장 (다음 요청에서 사용)
const jsonData = pm.response.json();
pm.environment.set("userId", jsonData.id);
김개발 씨는 Postman을 매일 사용했습니다. 새로운 API를 만들면 Postman으로 호출해보고, 응답을 확인했죠.
그런데 API가 20개, 30개로 늘어나면서 문제가 생겼습니다. 배포할 때마다 모든 API를 손으로 테스트하는 건 불가능했습니다.
박시니어 씨가 터미널을 열며 말했습니다. "Postman 컬렉션을 Newman으로 실행하면 돼요." 그렇다면 Newman이란 정확히 무엇일까요?
쉽게 비유하자면, Postman이 자동차의 운전대라면 Newman은 자율 주행 시스템입니다. Postman에서 운전 경로(테스트 시나리오)를 설정해두면, Newman이 알아서 운전해줍니다.
사람이 개입하지 않아도 목적지(테스트 완료)까지 도달하죠. 수동 테스트만 하던 시절에는 어땠을까요?
배포 전 API 테스트에 2시간씩 걸렸습니다. 개발자가 하나씩 클릭하며 확인해야 했으니까요.
실수로 테스트를 빼먹기도 했고, 야근의 주범이기도 했습니다. 바로 이런 문제를 해결하기 위해 Newman이 등장했습니다.
Newman은 npm으로 간단히 설치할 수 있습니다. npm install -g newman 명령어면 끝입니다.
Postman에서 컬렉션을 Export하고, newman run collection.json 명령어로 실행하면 됩니다. 위의 코드는 Postman의 Tests 탭에 작성하는 스크립트입니다.
pm.test 함수로 테스트 케이스를 정의합니다. pm.response로 응답 데이터에 접근하고, pm.expect로 기대값을 검증합니다.
특히 유용한 기능은 pm.environment.set입니다. 로그인 API의 응답에서 토큰을 추출해서 환경 변수에 저장하고, 다음 API 호출에서 그 토큰을 사용할 수 있습니다.
이렇게 하면 인증이 필요한 API도 자동으로 테스트할 수 있죠. 실제 현업에서는 어떻게 활용할까요?
배포 전에 Newman을 실행해서 모든 API가 정상인지 확인합니다. Jenkins나 GitHub Actions 같은 CI 도구와 연동하면, 코드가 푸시될 때마다 자동으로 테스트가 실행됩니다.
주의할 점도 있습니다. Postman 컬렉션은 팀원들과 공유해야 합니다.
혼자만 가지고 있으면 자동화의 의미가 없습니다. Postman Workspace를 활용하거나, 컬렉션 파일을 Git에 함께 관리하세요.
김개발 씨는 Newman으로 테스트를 자동화한 후, 배포 시간이 30분으로 줄었습니다. "이제 클릭 노가다는 안 해도 되겠네요!"
실전 팁
💡 - newman run collection.json -e environment.json으로 환경 변수 파일도 함께 사용하세요
- --reporters cli,htmlextra 옵션으로 보기 좋은 HTML 리포트를 생성할 수 있습니다
4. 계약_테스트_Contract_Testing
어느 날 프론트엔드 팀에서 연락이 왔습니다. "API 응답 형식이 바뀌었나요?
화면이 깨졌어요." 김개발 씨는 당황했습니다. 분명히 테스트는 통과했는데, 프론트엔드와의 약속을 깜빡했던 것입니다.
**계약 테스트(Contract Testing)**는 API 제공자와 소비자 사이의 "계약"을 검증하는 테스트입니다. 마치 집주인과 세입자가 계약서를 작성하듯, 백엔드와 프론트엔드가 API 형식에 대한 계약을 맺고 이를 검증합니다.
Pact는 가장 많이 사용되는 계약 테스트 도구입니다.
다음 코드를 살펴봅시다.
// Pact를 사용한 계약 테스트 (Consumer 측)
const { Pact } = require('@pact-foundation/pact');
const { fetchUser } = require('./userClient');
const provider = new Pact({
consumer: 'FrontendApp',
provider: 'UserAPI',
port: 1234,
});
describe('User API Contract', () => {
beforeAll(() => provider.setup());
afterAll(() => provider.finalize());
test('GET /users/:id는 사용자 정보를 반환해야 한다', async () => {
// 기대하는 응답 형식을 정의 (계약)
await provider.addInteraction({
state: '사용자 ID 1이 존재함',
uponReceiving: '사용자 정보 요청',
withRequest: { method: 'GET', path: '/users/1' },
willRespondWith: {
status: 200,
body: { id: 1, name: '김개발', email: 'kim@example.com' }
}
});
const user = await fetchUser(1);
expect(user.name).toBe('김개발');
});
});
김개발 씨는 억울했습니다. 백엔드 테스트는 모두 통과했는데, 왜 프론트엔드에서 문제가 생긴 걸까요?
알고 보니 응답 필드명을 username에서 name으로 바꿨는데, 프론트엔드에 알리지 않았던 것입니다. 팀장님이 한숨을 쉬며 말했습니다.
"계약 테스트를 도입해야겠어요." 그렇다면 계약 테스트란 정확히 무엇일까요? 쉽게 비유하자면, 계약 테스트는 마치 국제 무역에서의 표준 규격과 같습니다.
한국에서 만든 부품이 미국 공장에서도 딱 맞으려면, 사전에 크기와 모양을 약속해야 합니다. API도 마찬가지입니다.
백엔드가 보내는 데이터 형식과 프론트엔드가 기대하는 형식이 일치해야 합니다. 계약 테스트 없이 개발하던 시절에는 어땠을까요?
프론트엔드와 백엔드가 따로 개발하다가, 통합할 때 문제가 터졌습니다. "응답에 createdAt 필드 없는데요?" "어?
저는 created_at으로 보냈는데요." 이런 대화가 밤늦게까지 이어졌죠. 바로 이런 문제를 해결하기 위해 계약 테스트가 등장했습니다.
Pact는 "Consumer Driven Contract" 방식을 사용합니다. API를 사용하는 쪽(Consumer, 보통 프론트엔드)이 먼저 기대하는 형식을 정의합니다.
이 정의가 "계약"이 됩니다. API 제공자(Provider, 보통 백엔드)는 이 계약을 만족하는지 검증합니다.
위의 코드에서 addInteraction이 핵심입니다. "이런 요청을 보내면 이런 응답이 와야 한다"는 계약을 정의합니다.
이 계약은 JSON 파일(Pact 파일)로 저장됩니다. 백엔드에서는 이 Pact 파일을 가져와서 Provider 검증을 수행합니다.
실제 API가 계약대로 응답하는지 확인하는 것이죠. 계약을 어기면 테스트가 실패합니다.
실제 현업에서는 어떻게 활용할까요? 마이크로서비스 아키텍처에서 특히 유용합니다.
서비스가 10개, 20개로 늘어나면 서로의 API 형식을 추적하기 어렵습니다. 계약 테스트가 있으면, 누군가 API를 변경할 때 어떤 서비스가 영향받는지 바로 알 수 있습니다.
주의할 점도 있습니다. 계약 테스트는 "형식"만 검증합니다.
비즈니스 로직이 올바른지는 확인하지 않습니다. 단위 테스트, 통합 테스트와 함께 사용해야 합니다.
김개발 씨는 계약 테스트를 도입한 후, 프론트엔드 팀과의 갈등이 줄었습니다. API 변경이 필요할 때 Pact 파일을 먼저 수정하고, 프론트엔드 팀과 합의한 후 구현합니다.
실전 팁
💡 - Pact Broker를 사용하면 계약 파일을 중앙에서 관리하고 버전을 추적할 수 있습니다
- 계약 테스트는 단위 테스트를 대체하는 게 아니라 보완하는 것입니다
5. 부하_테스트_k6_JMeter
드디어 서비스 오픈 날이 왔습니다. 기대에 부풀어 있던 김개발 씨는 서버 모니터링 화면을 보다가 얼굴이 하얗게 질렸습니다.
동시 접속자 100명만 넘어도 서버가 버벅거리기 시작했습니다. "부하 테스트...
안 했구나."
**부하 테스트(Load Testing)**는 시스템이 얼마나 많은 사용자를 감당할 수 있는지 확인하는 테스트입니다. 마치 다리가 얼마나 무거운 짐을 견딜 수 있는지 시험하는 것과 같습니다.
k6는 JavaScript로 시나리오를 작성하는 현대적인 도구이고, JMeter는 GUI 기반의 전통적인 도구입니다.
다음 코드를 살펴봅시다.
// k6 부하 테스트 스크립트 (load-test.js)
import http from 'k6/http';
import { check, sleep } from 'k6';
// 부하 시나리오 설정
export const options = {
stages: [
{ duration: '30s', target: 50 }, // 30초 동안 50명까지 증가
{ duration: '1m', target: 100 }, // 1분 동안 100명 유지
{ duration: '30s', target: 0 }, // 30초 동안 0명으로 감소
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%의 요청이 500ms 이하
http_req_failed: ['rate<0.01'], // 실패율 1% 미만
},
};
export default function () {
// 실제 사용자 행동 시뮬레이션
const loginRes = http.post('https://api.example.com/login', {
email: 'test@example.com',
password: 'password123',
});
check(loginRes, { '로그인 성공': (r) => r.status === 200 });
sleep(1); // 사용자가 1초 대기하는 것을 시뮬레이션
const userRes = http.get('https://api.example.com/users/me');
check(userRes, { '사용자 조회 성공': (r) => r.status === 200 });
}
김개발 씨는 서버가 다운되는 악몽을 꾸기 시작했습니다. 출시 당일의 기억이 트라우마가 되었죠.
"100명도 못 버틴다니... 도대체 뭐가 문제였을까요?" 박시니어 씨가 부드럽게 말했습니다.
"부하 테스트를 미리 했으면 알 수 있었어요." 그렇다면 부하 테스트란 정확히 무엇일까요? 쉽게 비유하자면, 부하 테스트는 마치 놀이공원 개장 전에 롤러코스터를 시험 운행하는 것과 같습니다.
손님 한 명일 때는 문제없지만, 100명이 동시에 타면 어떻게 될까요? 미리 테스트해서 안전한 최대 인원을 파악해야 합니다.
부하 테스트 없이 출시하던 시절에는 어땠을까요? 기도 메타였습니다.
"제발 서버가 버텨라..." 하지만 대부분 기도는 응답받지 못했습니다. 트래픽이 폭주하면 서버는 다운되고, 사용자들은 떠나갔습니다.
바로 이런 문제를 해결하기 위해 k6와 JMeter 같은 도구가 등장했습니다. k6는 Grafana Labs에서 만든 현대적인 부하 테스트 도구입니다.
JavaScript로 시나리오를 작성하기 때문에 개발자에게 친숙합니다. CLI 기반이라 CI/CD에 통합하기도 쉽습니다.
JMeter는 Apache에서 만든 전통적인 도구입니다. GUI로 시나리오를 구성할 수 있어서 비개발자도 사용할 수 있습니다.
플러그인이 풍부하고, 분산 테스트도 지원합니다. 위의 k6 코드를 살펴보겠습니다.
stages에서 부하 패턴을 정의합니다. 30초 동안 50명까지 늘리고, 1분간 100명을 유지한 후, 다시 줄입니다.
이것을 **램프업(Ramp-up)**이라고 합니다. thresholds는 성공 기준입니다.
95%의 요청이 500ms 이내에 처리되어야 하고, 실패율은 1% 미만이어야 합니다. 기준을 넘으면 테스트가 실패합니다.
default function은 가상 사용자 한 명의 행동을 정의합니다. 로그인하고, 1초 쉬고, 내 정보를 조회합니다.
이 시나리오가 동시에 100번 실행되는 것이죠. 실제 현업에서는 어떻게 활용할까요?
출시 전에 예상 트래픽의 2~3배로 테스트합니다. 어디서 병목이 생기는지 파악하고, 데이터베이스 인덱스를 추가하거나 캐시를 적용합니다.
주의할 점도 있습니다. 운영 서버에서 부하 테스트를 하면 안 됩니다.
별도의 테스트 환경에서 진행해야 합니다. 또한 테스트 데이터가 실제 운영 데이터와 비슷해야 의미 있는 결과를 얻을 수 있습니다.
김개발 씨는 k6로 부하 테스트를 시작했습니다. 예상대로 DB 쿼리에서 병목이 발견되었습니다.
인덱스를 추가하니 동시 접속자 1000명도 거뜬히 처리할 수 있게 되었습니다.
실전 팁
💡 - k6 cloud를 사용하면 분산 부하 테스트와 상세한 대시보드를 이용할 수 있습니다
- 부하 테스트는 한 번이 아니라, 주요 변경 후마다 반복해야 합니다
6. CI_CD_파이프라인_통합
김개발 씨는 테스트 자동화를 열심히 공부했습니다. 하지만 여전히 배포 전에 수동으로 테스트를 실행해야 했습니다.
"이것도 자동화할 수 없을까요?" 박시니어 씨가 답했습니다. "GitHub Actions에 연결하면 돼요."
CI/CD 파이프라인은 코드 변경부터 배포까지 자동화하는 시스템입니다. 마치 공장의 컨베이어 벨트처럼, 코드가 푸시되면 자동으로 빌드하고, 테스트하고, 배포합니다.
GitHub Actions, Jenkins, GitLab CI 등이 대표적인 도구입니다. 테스트를 CI/CD에 통합하면 매번 수동으로 실행할 필요가 없습니다.
다음 코드를 살펴봅시다.
# .github/workflows/api-test.yml
name: API Test Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: test
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Run integration tests
run: npm run test:integration
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/test
- name: Run API tests with Newman
run: npx newman run ./tests/postman/collection.json -e ./tests/postman/env.json
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: test-results
path: ./reports/
김개발 씨의 팀은 성장했습니다. 이제 5명의 개발자가 함께 일합니다.
그런데 새로운 문제가 생겼습니다. A 개발자가 수정한 코드 때문에 B 개발자의 기능이 망가지는 일이 반복되었습니다.
팀장님이 회의에서 말했습니다. "CI/CD 파이프라인을 구축합시다.
모든 PR에 테스트가 자동으로 실행되어야 해요." 그렇다면 CI/CD란 정확히 무엇일까요? 쉽게 비유하자면, CI/CD는 마치 자동화된 품질 검사 라인입니다.
공장에서 제품이 나오면 자동으로 불량품을 걸러내듯, 코드가 푸시되면 자동으로 테스트를 실행해서 문제 있는 코드를 걸러냅니다. **CI(Continuous Integration)**는 지속적 통합입니다.
여러 개발자의 코드를 자주 합치고, 합칠 때마다 테스트를 실행합니다. **CD(Continuous Deployment/Delivery)**는 지속적 배포입니다.
테스트를 통과한 코드를 자동으로 운영 서버에 배포합니다. CI/CD 없이 개발하던 시절에는 어땠을까요?
"통합의 날"이라는 악몽 같은 시간이 있었습니다. 각자 개발한 코드를 한꺼번에 합치는 날이었죠.
충돌이 수십 개씩 발생했고, 밤새 해결해야 했습니다. 바로 이런 문제를 해결하기 위해 CI/CD가 등장했습니다.
위의 GitHub Actions 워크플로우를 살펴보겠습니다. on 섹션에서 언제 파이프라인이 실행될지 정의합니다.
main이나 develop 브랜치에 푸시하거나, PR을 열면 실행됩니다. services에서 테스트용 PostgreSQL을 띄웁니다.
실제 데이터베이스 없이도 통합 테스트를 할 수 있습니다. steps에서는 순서대로 작업을 실행합니다.
코드 체크아웃, 의존성 설치, 단위 테스트, 통합 테스트, Newman API 테스트까지 자동으로 진행됩니다. 테스트가 하나라도 실패하면 파이프라인이 멈춥니다.
PR을 머지할 수 없게 설정하면, 버그가 있는 코드가 main 브랜치에 들어가는 것을 막을 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
대부분의 회사에서 PR 머지 조건에 "CI 통과"를 필수로 설정합니다. 코드 리뷰 전에 자동으로 테스트가 실행되니, 리뷰어는 "테스트는 통과했다"는 전제 하에 로직에만 집중할 수 있습니다.
주의할 점도 있습니다. 테스트가 너무 오래 걸리면 개발 속도가 느려집니다.
단위 테스트는 빠르게, 통합 테스트와 E2E 테스트는 병렬로 실행해서 시간을 단축하세요. 김개발 씨의 팀은 GitHub Actions를 도입한 후 버그가 70% 줄었습니다.
"이제 PR 올리고 녹색 체크만 확인하면 되니까 마음이 편해요."
실전 팁
💡 - 테스트가 실패하면 슬랙으로 알림을 보내도록 설정하세요
- 캐시를 활용하면 의존성 설치 시간을 크게 줄일 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
WebSocket과 Server-Sent Events 실시간 통신 완벽 가이드
웹 애플리케이션에서 실시간 데이터 통신을 구현하는 핵심 기술인 WebSocket과 Server-Sent Events를 다룹니다. 채팅, 알림, 실시간 업데이트 등 현대 웹 서비스의 필수 기능을 구현하는 방법을 배워봅니다.
효과적인 API 문서 작성법 완벽 가이드
API 문서는 개발자와 개발자 사이의 가장 중요한 소통 수단입니다. 이 가이드에서는 좋은 API 문서가 갖춰야 할 조건부터 Getting Started, 엔드포인트 설명, 에러 코드 문서화, 인증 가이드, 변경 이력 관리까지 체계적으로 배워봅니다.
API 캐싱과 성능 최적화 완벽 가이드
웹 서비스의 응답 속도를 획기적으로 개선하는 캐싱 전략과 성능 최적화 기법을 다룹니다. HTTP 캐싱부터 Redis, 데이터베이스 최적화, CDN까지 실무에서 바로 적용할 수 있는 핵심 기술을 초급자 눈높이에서 설명합니다.
OAuth 2.0과 소셜 로그인 완벽 가이드
OAuth 2.0의 핵심 개념부터 구글, 카카오 소셜 로그인 구현까지 초급 개발자를 위해 쉽게 설명합니다. 인증과 인가의 차이점, 다양한 Flow의 특징, 그리고 보안 고려사항까지 실무에 바로 적용할 수 있는 내용을 다룹니다.
JWT 기반 인증 구현하기 완벽 가이드
웹 애플리케이션에서 가장 널리 사용되는 JWT 인증 방식을 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 토큰의 구조부터 보안 베스트 프랙티스까지 실무에 바로 적용할 수 있는 내용을 담았습니다.