Test Driven Development (TDD) — 테스트 케이스를 먼저 작성하고, 그 테스트를 통과하는 코드를 작성한 뒤, 테스트가 통과하면 코드를 리팩토링하는 개발 방식.
TDD 사이클
- 테스트 케이스(코드/스크립트) 작성 — 아직 제품 코드가 없으므로 실패
- 테스트 케이스를 통과하는 최소한의 제품 코드 작성 → 성공 확인
- 테스트 성공 상태를 유지한 채 코드 구조 개선(리팩토링)
- 반복
Baby Step 원칙
한 번에 모든 테스트 케이스를 만들지 않고 조금씩 만들어 간다. 문제의 크기가 작을 때 빠르게 해결·검증하는 것이 핵심.
최소 예제
// SumTest.c
static void testSum(void) {
assert(sum(3, 2) == 5);
}
// Sum.c
int sum(int a, int b) {
return a + b;
}장단점
장점:
- 코드의 목적을 명확히 하고 코딩 진행 (테스트가 명세 역할)
- 지속적인 코드 개선 — 리팩토링을 사이클에 포함
- 테스트 케이스가 문서의 역할을 수행
단점:
- 단위 테스트에 몰입 — 시스템·통합 관점 누락 가능
테스트 프레임워크 선택
C/C++ 단위 테스트에서 대표 프레임워크는 3종:
| 프레임워크 | 무게 | 특징 |
|---|---|---|
| Unity | 가볍 | 소형 임베디드에서 부담 적음 |
| CppUTest | 무겁 | 전통적 C/C++ 단위 테스트 프레임워크 |
| Google Test | 무겁 | xUnit 기반, 요즘 표준. 기능 풍부 |
기존 상용 도구와의 연계: 이미 VectorCAST 같은 상용 단위 테스트 도구를 사용 중이라면, TDD 프레임워크와의 역할 구분을 먼저 정리해야 한다.
assert()로 시작하기
assert(boolean) — boolean이 True면 성공, False면 실패.
#include <assert.h>
assert(3 * 3 == 9); // 3*3이 9가 맞니?
assert(1); // 테스트 성공관례적으로 좌측에 함수 실행 결과, 우측에 예상 결과를 작성한다.
실행 예
#include "calc.h"
#include <assert.h>
#include <stdio.h>
static void testSum(void) {
assert(sum(3, 2) == 5);
}
int main(void) {
testSum();
puts("All tests passed");
}파일·함수 네이밍 관례
- 제품 코드는
src/, 테스트 코드는test/폴더에 분리 - 제품 파일
calc.c의 테스트 파일은calc.tests.c또는calcTest.c - 제품 함수
sum()의 테스트 함수는testSum()
Google Test
xUnit 아키텍처 기반 C++ 단위 테스트 라이브러리. xUnit은 AdaUnit·JUnit 계열 단위 테스트 프레임워크의 총칭.
Assertion 함수 호출로 테스트를 수행하며, 실패 시 동작 방식에 따라 2종:
- Fatal assertion(
ASSERT_*) — 실패 시 해당 테스트 중단 - Nonfatal assertion(
EXPECT_*) — 실패해도 나머지 테스트 계속 실행
주요 Assertion
| Fatal | Nonfatal | Verifies |
|---|---|---|
ASSERT_EQ(v1, v2) | EXPECT_EQ(v1, v2) | v1 == v2 |
ASSERT_NE(v1, v2) | EXPECT_NE(v1, v2) | v1 != v2 |
ASSERT_LT(v1, v2) | EXPECT_LT(v1, v2) | v1 < v2 |
ASSERT_LE(v1, v2) | EXPECT_LE(v1, v2) | v1 <= v2 |
ASSERT_GT(v1, v2) | EXPECT_GT(v1, v2) | v1 > v2 |
ASSERT_GE(v1, v2) | EXPECT_GE(v1, v2) | v1 >= v2 |
TEST 매크로
TEST(TestSuiteName, TestName) {
// test body...
}C 언어 대상 예:
#include <gtest/gtest.h>
extern "C" {
#include "hiker.h"
}
using namespace testing;
TEST(Hiker, Life_the_universe_and_everything) {
EXPECT_EQ(42, answer());
}Google Test 설계 원칙
1. 테스트는 독립적이고 반복 가능해야 한다.
- 다른 테스트 결과에 따라 결과가 달라지면 안 됨
- Google Test는 각 테스트를 분리된 오브젝트로 관리
2. 테스트는 잘 구조화되어야 한다.
- 관련 테스트를 test suite로 그룹화하여 데이터·subroutine 공유
- 테스트 대상 코드의 구조를 잘 반영
3. 테스트는 재사용 가능하고 플랫폼 독립적이어야 한다.
- 서로 다른 OS에서도 실행 가능
기타 기능
- Fail 원인 보고 — 실패 시 왜 실패했는지 자동 보고 → 버그 위치 파악 용이
- Shared resource 재사용 — 테스트 간 공용 자원 공유
- Set-up / Tear-down — 한 번만 실행되는 초기화·정리 메서드 지원
- 테스팅 프레임워크는 테스트 작성자의 반복 작업을 줄여 테스트 자체에 집중하도록 돕는다.
TDD와 지속적 통합
CI와 TDD는 SW 개발 관점에서 동일한 목적을 공유한다:
- 문제의 크기가 작을 때 빠르게 해결
- 지속적으로 문제를 검증하고 해결
TDD는 “작성 단위”를, CI는 “통합 단위”를 작게 쪼개 같은 철학을 두 층위에서 실현한다. CI 파이프라인의 Continuous Testing 단계에서 Google Test·JUnit이 자동 실행되며, TDD로 축적된 단위 테스트가 회귀 테스트의 기반이 된다. → 회귀 테스트.
실습 환경
Cyber-dojo(https://cyber-dojo.org/) — 소스코드 테스트 프레임워크와 TDD를 경험할 수 있는 웹사이트. Google Test를 활용한 단위 테스트 실습 지원.