36940db938a8f4a1e919496793ed439850a221c2의 Ztest 구조를 기준으로 한다. 예시 코드는 개념을 설명하기 위해 새로 썼고, 특정 보드에서 컴파일·실행한 결과는 제시하지 않는다.1. YAML의 시나리오와 C의 케이스는 다른 단위다
Twister가 읽는 testcase.yaml의 시나리오는 “이 앱을 어떤 조건과 플랫폼에서 빌드·실행할까”를 정한다. C 코드의 ZTEST_SUITE는 한 바이너리 안에서 함께 실행할 케이스를 묶고, ZTEST 계열 매크로가 개별 케이스를 등록한다. 따라서 시나리오 하나가 C 케이스 하나라는 뜻은 아니다.
고정 3.7.0의 공식 integration 샘플은 YAML에 sample.testing.ztest 시나리오와 build_only: true가 있고, C 소스에는 framework_tests 스위트 및 test_assert 케이스가 있다. 이 샘플을 그 YAML대로 Twister에 넘기면 케이스를 포함한 이미지는 빌드하지만 런타임의 assertion 통과를 확인하지 않는다.
sample.testing.ztest보드·태그·빌드 옵션을 선택
build_only: true이면 여기서 빌드까지만framework_tests실행 이미지 안의 케이스 묶음
ZTEST_SUITE(...)로 등록test_assert개별 조건을 검사하는 함수
ZTEST(...)와 zassert_*화살표는 시나리오가 빌드할 앱 안에 스위트와 케이스가 등록되는 연결 경로를 나타낸다. build_only: true에서 멈춘 실행은 C 케이스의 런타임 PASS를 생성하지 않는다.
| 단위 | 정의 위치 | 뜻 |
|---|---|---|
| 시나리오 | testcase.yaml / sample.yaml | 앱·플랫폼 조합의 빌드/실행 조건 |
| 스위트 | C의 ZTEST_SUITE | 한 테스트 바이너리에 등록된 관련 케이스 묶음 |
| 케이스 | C의 ZTEST 계열 | 스위트에 속한 개별 검사 함수와 결과 |
근거: 고정 샘플의 YAML 1–9행, C의 스위트·케이스 7–22행, 3.7.0 Ztest integration 문서.
2. 케이스 선언은 픽스처와 권한을 결정한다
ZTEST_SUITE(name, predicate, setup, before, after, teardown)는 스위트 이름과 선택적 predicate·수명주기 함수를 등록한다. 스위트 이름은 한 바이너리 안에서 고유해야 한다. 케이스는 ZTEST, ZTEST_F, ZTEST_USER, ZTEST_USER_F 중 필요한 형태로 붙인다. _F는 struct <suite>_fixture *fixture 인자를 제공하고, _USER는 CONFIG_USERSPACE가 켜진 경우 사용자 스레드에서 실행한다.
| 매크로 | fixture 인자 | 사용자 모드 조건 |
|---|---|---|
ZTEST | 없음 | 기본 케이스 실행 |
ZTEST_F | fixture 제공 | 기본 케이스 실행 |
ZTEST_USER | 없음 | CONFIG_USERSPACE=y일 때 사용자 스레드 |
ZTEST_USER_F | fixture 제공 | CONFIG_USERSPACE=y일 때 사용자 스레드 |
아래는 매 케이스 시작 때 상태를 0으로 만드는 설명용 코드다. setup이 돌려준 포인터를 before와 ZTEST_F가 공유한다. 이 코드의 NULL 자리는 사용하지 않는 콜백이다.
struct counter_fixture { int value; };
static struct counter_fixture state;
static void *counter_setup(void) { return &state; }
static void counter_before(void *p)
{
((struct counter_fixture *)p)->value = 0;
}
ZTEST_SUITE(counter, NULL, counter_setup, counter_before, NULL, NULL);
ZTEST_F(counter, test_starts_empty)
{
zassert_equal(fixture->value, 0);
}3. setup은 스위트에, before·after는 케이스에 붙는다
정상 실행 경로에서 setup은 스위트 실행당 한 번 fixture 포인터를 만든다. 각 케이스마다 before로 상태를 준비하고 케이스 본문 뒤 after를 호출한다. 마지막에 teardown이 한 번 호출된다. 따라서 setup에서 모든 케이스에 필요한 공통 자원을 만들고, 케이스 간 상태 누수는 before에서 지우는 구성이 자연스럽다.
fixture 포인터 반환한 번
before → 본문 → after각 케이스에 적용before → 본문 → after같은 fixture를 받음설정 실패·건너뜀·fail-fast는 본문 호출과 최종 상태를 바꿀 수 있다. shuffle·반복 설정은 케이스 순서와 횟수를 바꿀 수 있다. 이 그림은 예외가 없는 한 번의 스위트 실행을 보여 준다.
| 콜백 | 정상 경로의 시점 | 주된 역할 |
|---|---|---|
setup() | 스위트 실행 시작 한 번 | fixture 생성·공통 준비, 포인터 반환 |
before(fixture) | 각 케이스 바로 전 | 케이스 간 상태 초기화 |
after(fixture) | 각 케이스 종료 뒤 | 케이스별 정리·관찰 |
teardown(fixture) | 스위트 실행 끝 한 번 | 공통 자원 해제 |
근거: 3.7.0 fixture 문서, 고정 setup 호출, 고정 케이스 전후 콜백, 고정 teardown 호출.
4. assertion과 assumption은 실패를 다르게 표시한다
zassert_* 조건이 거짓이면 케이스 실패다. zexpect_*는 실패를 기록하면서 현재 케이스 실행을 계속할 수 있다. zassume_* 조건이 거짓이면 케이스를 건너뛴다. 그러나 고정 버전의 기본 CONFIG_ZTEST_FAIL_ON_ASSUME=y에서는 그 케이스가 SKIP으로 보이더라도 전체 테스트 바이너리 결과는 실패가 될 수 있다. 가정은 검증하고 싶은 동작의 대체 assertion으로 쓰지 않는다.
zassert_true(false)검증 조건이 깨짐현재 케이스 실패FAIL로 보고zexpect_true(false)실패를 기록케이스 본문은 계속 가능종결 시 FAILzassume_true(false)전제 조건이 깨짐케이스 SKIP기본 설정에서 전체 실행 FAIL 가능ztest_test_skip()명시적 건너뜀가정 실패 플래그 없음케이스 SKIP각 칸은 다른 실패가 없을 때의 기본 의미다. ZTEST_EXPECT_FAIL·ZTEST_EXPECT_SKIP 선언은 기대한 결과의 최종 분류를 바꾼다.
| 동작 | 케이스 결과 | 전체 실행에 미치는 영향 |
|---|---|---|
zassert_* 실패 | FAIL | 실패가 전체 결과에 반영 |
zexpect_* 실패 | 계속 실행 후 FAIL | 기록한 실패가 전체 결과에 반영 |
zassume_* 실패 | SKIP | 기본 CONFIG_ZTEST_FAIL_ON_ASSUME=y에서 마지막 결과는 실패; 기대 SKIP 선언은 예외 |
ztest_test_skip() | SKIP | 가정 실패 플래그를 세우지 않음; 다른 실패가 있는지는 별도 |
근거: 고정 zassert_true 정의, 고정 zassume_true 정의, 고정 zexpect_true 정의, 고정 FAIL_ON_ASSUME 기본값, 가정 실패 플래그 및 최종 결과 처리.
5. 사용자 스레드와 공통 규칙에는 별도 준비가 필요하다
ZTEST_USER와 ZTEST_USER_F는 CONFIG_USERSPACE=y일 때 사용자 스레드에서 케이스를 실행한다. fixture 포인터를 전달한다고 커널이 준비한 메모리까지 자동으로 접근 가능한 것은 아니다. 사용자 케이스가 읽을 정적 데이터에는 Ztest의 ZTEST_DMEM·ZTEST_BMEM 같은 사용자 접근 가능 선언을 검토하고, 동적 메모리와 객체 권한은 실제 memory domain·grant 설정에 맞춰 확인해야 한다.
ZTEST_RULE은 한 스위트의 fixture가 아니라 바이너리의 모든 케이스에 적용하는 before/after 콜백이다. 고정 문서에서 rule의 before는 suite의 before보다 먼저 실행한다. 여러 rule의 after 순서는 보장하지 않으므로 서로 간 순서 의존성을 두지 않는다.
| 기능 | 범위 | 추가 확인 |
|---|---|---|
ZTEST_SUITE fixture | 그 스위트와 그 안의 케이스 | 케이스별 상태 초기화가 필요한가? |
ZTEST_RULE | 바이너리의 모든 스위트·케이스 | 다른 rule의 after 순서에 의존하는가? |
ZTEST_USER_F | fixture 인자를 받는 사용자 케이스 | CONFIG_USERSPACE와 메모리 접근 권한이 맞는가? |
근거: 3.7.0 사용자 fixture 메모리 주의, 3.7.0 rule 순서, 고정 USER 매크로 및 공유 메모리 매크로.
6. Twister는 소스 이름과 실행 로그를 모두 본다
Twister는 ZTEST_SUITE와 네 가지 ZTEST 계열 매크로를 소스에서 찾고, 실행 가능한 시나리오라면 Ztest 출력의 케이스 결과를 수집한다. 고정 스캐너는 해당 이름의 매크로 패턴을 찾기 때문에 케이스 선언을 전처리기로 완전히 가려 버리면 정적 목록과 실행 결과가 달라질 수 있다. Zephyr 문서는 조건부 케이스라면 선언을 유지하고 ztest_test_skip() 또는 Z_TEST_SKIP_IFDEF로 명시적으로 건너뛰라고 안내한다.
build_only: true라서 Twister가 바이너리를 만들더라도 런타임 Ztest 출력은 요구하지 않는다. 케이스 단위 결과가 필요한 시험에서는 YAML의 build-only 설정, 실행 대상과 harness를 별도로 맞춰야 한다.근거: 고정 Twister 케이스 이름 스캐너, 3.7.0 목록과 조건부 건너뜀, 3.7.0 build_only 정의.
7. 테스트가 빠졌거나 예상과 다르게 통과했다면
- YAML의 시나리오 ID와 C의 스위트·케이스 이름을 각각 확인한다. 시나리오 수와 케이스 수를 같은 숫자로 세지 않는다.
CONFIG_ZTEST=y인지,build_only인지, 실제 실행 가능한 보드·simulator와 harness가 있는지 확인한다.- fixture가 케이스 사이에 유지되는 값과 매번 재설정되는 값을 구분한다. 다른 케이스의 실행 순서에 의존하면 shuffle·반복 설정에서 깨질 수 있다.
- SKIP이면 명시적
ztest_test_skip()인지zassume_*실패인지 먼저 확인한다. 후자라면CONFIG_ZTEST_FAIL_ON_ASSUME과 기대 결과 선언도 본다. - 사용자 케이스라면
CONFIG_USERSPACE및 fixture 메모리·커널 객체 접근 가능 여부를 점검한다.
8. 고정 버전 근거와 읽는 범위
Ztest는 Zephyr 전체를 포함한 integration 테스트에도, 특정 모듈만 묶은 unit 테스트에도 쓸 수 있다. 여기서는 케이스 선언과 실행·판정 계약을 설명했다. Twister의 플랫폼 필터·CI 행렬은 별도 Twister 글에서 다룬다.