Test Driven Development (TDD) — 테스트 케이스를 먼저 작성하고, 그 테스트를 통과하는 코드를 작성한 뒤, 테스트가 통과하면 코드를 리팩토링하는 개발 방식.

TDD 사이클

TDD 사이클 — 실패 테스트·통과 코드·리팩토링 3단계 반복

  1. 테스트 케이스(코드/스크립트) 작성 — 아직 제품 코드가 없으므로 실패
  2. 테스트 케이스를 통과하는 최소한의 제품 코드 작성 → 성공 확인
  3. 테스트 성공 상태를 유지한 채 코드 구조 개선(리팩토링)
  4. 반복

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

FatalNonfatalVerifies
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를 활용한 단위 테스트 실습 지원.

같이 보기