임베디드 시스템 테스트 전략과 실시간 검증
임베디드 시스템의 하드웨어 의존성, 실시간 제약, 자원 제한을 고려한 테스트 레벨과 환경, 분석 기법을 정리한다.
2026-08-14 · 최초 발행 2026-01-12
하드웨어와 함께 검증해야 하는 소프트웨어
임베디드 시스템은 하드웨어와 소프트웨어가 결합된 상태에서 요구사항을 충족하는지 확인해야 한다. 일반적인 소프트웨어 테스트 범위에 더해 실제 장치 의존성, 실시간 응답, 메모리·CPU·전력 사용량, 장시간 무중단 동작까지 검증 대상이 된다.
테스트 전략은 다음과 같은 제약을 전제로 잡는다.
- 실제 하드웨어 또는 에뮬레이터가 필요하다.
- 타이밍과 응답 시간을 확인해야 한다.
- 메모리, CPU, 전력 사용량을 측정해야 한다.
- 장시간 연속 운영에서의 안정성을 검증해야 한다.
검증 범위를 넓혀 가는 테스트 레벨
단위 테스트는 개별 함수, 모듈, 클래스를 대상으로 한다. 의존성은 Mock 또는 Stub으로 대체해 격리된 환경에서 검증하며, Unity나 CppUTest 같은 프레임워크와 코드 커버리지 측정을 함께 사용할 수 있다.
// Unity 테스트 프레임워크 예제
#include "unity.h"
#include "mymodule.h"
void setUp(void) {
// 테스트 전 초기화
}
void tearDown(void) {
// 테스트 후 정리
}
void test_add_function(void) {
TEST_ASSERT_EQUAL(5, add(2, 3));
TEST_ASSERT_EQUAL(0, add(-1, 1));
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_add_function);
return UNITY_END();
}
통합 테스트에서는 모듈 사이의 인터페이스와 상호작용을 본다. Bottom-up, Top-down, Sandwich 접근을 적용할 수 있으며, 데이터 흐름·제어 흐름·프로토콜 준수를 시뮬레이터 또는 실제 하드웨어에서 확인한다.
시스템 테스트는 타겟 하드웨어 위에서 전체 시스템을 대상으로 수행한다. 요구사항이 모두 충족되는지 추적하고, 실제 사용 사례에 기반한 기능·성능·신뢰성 시나리오를 실행한다.
인수 테스트는 계약과 사양서를 기준으로 고객 요구사항 충족 여부를 판단하는 단계다. 고객 또는 최종 사용자가 수행하며 알파 테스트, 베타 테스트, 현장 테스트 형태를 취할 수 있다.
기능 결과와 운용 조건을 함께 본다
기능 테스트는 정상 입력에 대한 동작뿐 아니라 경계와 오류, 상태 변화를 다룬다.
| 테스트 항목 | 확인 내용 | 방법 |
|---|---|---|
| 정상 동작 | 명시된 기능 수행 여부 | 입력-출력 검증 |
| 경계 조건 | 최소·최대값 처리 | 경계값 분석 |
| 오류 처리 | 예외 상황 대응 | 오류 주입 |
| 상태 전이 | 시스템 상태 변화 | 상태 다이어그램 기반 |
비기능 테스트에서는 응답 시간, 초당 처리 작업 수, CPU 사용률, 메모리 점유율을 확인한다. 인터럽트 지연과 태스크 응답 시간은 실시간 제약과 직접 연결되며, 프로파일러·타이머·오실로스코프를 측정 도구로 활용할 수 있다.
신뢰성은 장시간 연속 운영을 보는 내구성 테스트, 최대 부하를 가하는 스트레스 테스트, 다양한 조건에서의 안정성 테스트, 장애 이후 복구 능력 검증으로 나뉜다. 전력 소비는 정상 동작, 슬립 모드, 최대 부하처럼 서로 다른 상태에서 측정하고 기록한다.
// 전력 소비 측정 예제
void power_consumption_test(void) {
// 전류 측정 시작
start_current_measurement();
// 정상 동작
run_normal_operation();
float normal_current = get_current();
// 슬립 모드
enter_sleep_mode();
float sleep_current = get_current();
// 최대 부하
run_max_load();
float max_current = get_current();
// 결과 기록
log_power_consumption(normal_current,
sleep_current,
max_current);
}
환경 테스트에서는 동작 온도 범위, 습도 조건, 진동에 대한 견딤성, 전자기 호환성과 간섭을 검증한다.
실시간 제약은 측정과 분석으로 확인한다
실시간 시스템은 평균 실행 시간만으로 판단할 수 없다. 최악의 경우 실행 시간인 WCET(Worst Case Execution Time), 최선의 경우 실행 시간인 BCET(Best Case Execution Time), 실행 시간 편차인 Jitter, 데드라인 위반인 Deadline Miss를 함께 확인해야 한다.
스케줄 가능성 분석에서는 고정 우선순위 스케줄링을 위한 Rate Monotonic Analysis, 태스크별 응답 시간을 계산하는 Response Time Analysis, CPU 사용률 한계를 계산하는 Utilization Bound, 우선순위 역전 문제를 검토한다.
# 타이밍 측정 예제
void measure_execution_time(void) {
uint32_t start, end, duration;
start = get_timer_ticks();
// 측정 대상 함수
critical_function();
end = get_timer_ticks();
duration = end - start;
if(duration > MAX_ALLOWED_TIME) {
log_timing_violation(duration);
}
}
시뮬레이션과 장비 검증을 분리해 운영한다
호스트 기반 테스트에서는 호스트 PC에서 타겟 동작을 시뮬레이션한다. QEMU는 CPU 에뮬레이터로, Renode는 전체 하드웨어 시뮬레이션에 사용할 수 있다. 실행과 디버깅이 빠르다는 장점이 있지만 실제 하드웨어 동작과 차이가 날 수 있다.
하드웨어 기반 테스트는 개발 보드, 프로토타입, 최종 제품에서 순차적으로 수행한다. 초기 단계에서는 개발 보드가 유용하고, 프로토타입은 제품과 유사한 환경을 제공하며, 최종 제품은 출시 전 실제 동작을 검증하는 대상이 된다. 검증 정확도는 높지만 디버깅이 어렵고 비용이 증가할 수 있다.
HIL(Hardware-In-the-Loop)은 실제 하드웨어, 시뮬레이터, 테스트 장비를 결합하는 방식이다. 자동차, 항공우주, 산업 제어에서 안전하고 재현 가능한 테스트 환경을 구성할 때 활용할 수 있다.
자동화 테스트 벤치는 스크립트 기반 반복 테스트와 변경 이후의 회귀 테스트를 지원한다. Jenkins, GitLab CI, Robot Framework 등을 사용해 커밋 시 자동 빌드와 테스트를 수행하고 CI/CD 흐름에 통합할 수 있다.
내부 구조와 외부 동작을 교차 검증한다
화이트박스 테스트는 코드 구조를 기준으로 문장 커버리지, 분기 커버리지, 조건 커버리지, 경로 커버리지를 확인한다. 모든 코드 라인과 조건문 경로, 조건 조합, 실행 경로를 검증해 내부 로직을 살핀다.
블랙박스 테스트는 구현 내부가 아니라 관찰 가능한 동작을 기준으로 한다. 입력 공간을 클래스로 나누는 등가 분할, 경계 부근 값을 확인하는 경계값 분석, 조건 조합을 다루는 결정 테이블, 상태 전이 시나리오가 여기에 속한다.
회귀 테스트는 변경 이후에도 기존 기능이 유지되는지 확인하는 절차다. 테스트 스크립트를 재사용하고 영향받는 영역을 선택적으로 실행할 수 있으며, CI/CD 파이프라인에 통합해 지속적으로 수행한다.
분석 도구와 커버리지 기준
단위 테스트 프레임워크는 언어와 요구사항에 따라 선택한다.
| 도구 | 언어 | 특징 |
|---|---|---|
| Unity | C | 임베디드 특화, 경량 |
| CppUTest | C/C++ | Mock 지원 |
| Google Test | C++ | 강력한 기능 |
| Ceedling | C | Unity + CMock + 빌드 자동화 |
코드 커버리지 분석에는 gcov, lcov, Bullseye를 사용할 수 있으며 목표는 최소 70~80% 커버리지 달성이다.
정적 분석은 메모리 누수, 버퍼 오버플로, 미초기화 변수를 검출하는 데 사용한다. Cppcheck, Clang Static Analyzer, PC-lint가 대표 도구다.
# Cppcheck 정적 분석 예제
cppcheck --enable=all --inconclusive \
--suppress=missingIncludeSystem \
src/
# Clang Static Analyzer
scan-build make
동적 분석에서는 호스트 시뮬레이션 환경의 Valgrind, 컴파일 타임 계측을 사용하는 AddressSanitizer, gprof와 Callgrind 같은 프로파일러, SystemView와 Tracealyzer 같은 추적 도구를 활용할 수 있다.
계획부터 결함 추적까지 이어지는 흐름
테스트 계획에서는 대상과 제외 범위를 정하고, 접근 방법과 우선순위, 단계별 일정, 인력·장비·도구를 정한다. 이어서 요구사항별 테스트 케이스를 매핑하고 입력·기대 출력·절차를 정의한다. 정상·경계·오류 데이터를 준비하며, 하드웨어·소프트웨어·네트워크 환경도 구성한다.
실행 단계에서는 단위, 통합, 시스템 테스트 결과를 집계하고 실패가 있으면 빌드를 중단하도록 자동화할 수 있다.
// 자동화 테스트 실행 예제
void run_automated_tests(void) {
int total = 0, passed = 0, failed = 0;
// 테스트 스위트 실행
total += run_unit_tests(&passed, &failed);
total += run_integration_tests(&passed, &failed);
total += run_system_tests(&passed, &failed);
// 결과 보고
printf("Total: %d, Passed: %d, Failed: %d\n",
total, passed, failed);
// 실패 시 빌드 중단
if(failed > 0) {
exit(1);
}
}
결함은 발견 내용과 함께 기록하고, 심각도와 영향도를 기준으로 우선순위를 정한다. 수정과 재테스트 상태를 추적하며, 반복되는 문제는 근본 원인 분석을 통해 재발 방지 대책으로 연결한다.
조기 검증과 현장 피드백을 결합한다
Shift Left와 TDD(Test-Driven Development)는 개발 초기부터 테스트를 시작하는 방식이다. 초기 결함 발견을 통해 비용을 줄이고, 개발과 테스트를 병행하면서 지속적으로 검증한다.
자동화는 변경 뒤의 회귀 테스트, 코드 커밋 시 자동 빌드와 테스트, 야간 빌드에서의 전체 테스트 스위트 실행, 결과 대시보드 제공에 사용할 수 있다.
실제 환경 검증에서는 필드 테스트와 제한된 사용자 대상의 베타 테스트를 수행한다. 현장 데이터를 수집·분석하고 사용자 경험을 반영한다. 이 과정은 명확한 절차와 기대 결과를 담은 테스트 케이스, 실행 결과와 커버리지를 기록한 테스트 보고서, 결함 로그, 요구사항과 테스트 케이스를 연결하는 추적 매트릭스로 뒷받침된다.
보안·안전·호환성까지 확인한다
보안 테스트는 알려진 보안 취약점을 확인하는 취약점 스캔, 공격 시나리오를 시뮬레이션하는 침투 테스트, 암호 알고리즘 정확성 검증, 권한과 인증을 다루는 접근 제어 테스트를 포함한다.
안전성 테스트에서는 FMEA(Failure Mode and Effects Analysis), FTA(Fault Tree Analysis), Fault Injection을 활용하고 IEC 61508, ISO 26262 등의 준수 여부를 확인한다.
호환성 테스트는 다양한 하드웨어 변종, 다른 소프트웨어 버전, 통신 프로토콜 간 상호운용성, 산업 표준과 규격 충족 여부를 대상으로 한다.