이 문서에서는 다음과 같은 내용을 논의합니다.
- 로우 레벨 테스트(Low-Level Tests) 작성, 여기에는 다음 내용이 포함됩니다.
- 추가적인 로우 레벨 테스트 기능
- 가이드라인 및 모범 사례
로우 레벨 테스트 작성
이 페이지에서는 주로 언리얼 엔진(UE) 환경에서 Catch2를 사용하여 로우 레벨 테스트(LLT) 를 작성하기 위한 구조, 가이드라인, 모범 사례를 논의합니다. Catch2에 대한 내용은 Catch2 GitHub 저장소를 참조하세요. 테스트 작성에 대한 완전한 가이드는 Catch2 레퍼런스를 참조하세요.
테스트에는 언리얼 C++ 코딩 규칙을 사용해야 합니다. 자세한 내용은 언리얼 코딩 표준을 참조하세요.
시작하기 전에
다음 항목에 대해 로우 레벨 테스트 타입 문서를 미리 살펴보세요.
- 자신의 사용 사례에 명시적(Explicit) 테스트와 묵시적(Implicit) 테스트 중 어느 것이 적합한지 결정합니다.
- 디렉터리가 해당 문서의 단계에서 설명한 디렉터리와 일치하는지 확인합니다.
테스트 .cpp 파일을 작성할 준비가 되었다면, 다음 단계를 따르세요.
<NAME_OF_FILE>Test.cpp처럼.cpp파일에 서술형 이름을 지정합니다.- 자세한 내용은 이 문서의 명명 규칙 섹션을 참조하세요.
- 묵시적 테스트를 작성하는 경우
.cpp파일을#if WITH_LOW_LEVEL_TESTS-#endif지시문으로 랩핑(wrap)합니다.- 아래의 BDD 테스트 예시를 참조하세요.
- 모든 필수 헤더 파일을 포함합니다.
- 적어도 선택 항목인
#include "CoreMinimal.h"및#include "TestHarness.h"는 포함해야 합니다. - 최소 헤더를 포함한 다음에는 테스트를 완료하는 데 필요한 헤더만 포함합니다.
- 적어도 선택 항목인
- 이제 테스트 중심 개발(TDD) 패러다임이나 동작 중심 개발(BDD) 패러다임을 사용하여 테스트를 작성할 수 있습니다.
동작 중심 개발 테스트 예시
BDD 스타일 테스트는 SCENARIO 를 통한 테스트에 초점을 맞춥니다. 한 파일에 여러 시나리오를 포함할 수 있습니다. 시나리오의 핵심 구조는 다음과 같습니다.
GIVEN: 설정 조건WHEN: 액션 수행THEN: 예상 결과 보유.
GIVEN 및 WHEN 섹션은 추가적인 초기화 및 내부 스테이트 변경을 포함할 수 있습니다. THEN 섹션은 원하는 결과가 true인지 확인하는 검사를 진행합니다. CHECK 실패는 실행을 계속하지만, REQUIRE 는 단일 테스트 실행을 중지합니다.
아래 코드 예시는 UE의 로우 레벨 테스트 프레임워크에서 BDD 스타일 테스트의 일반적인 개요를 보여줍니다.
#if WITH_LOW_LEVEL_TESTS // 묵시적 테스트에만 필요!
#include "CoreMinimal.h"
#include "TestHarness.h"
// 포함하는 다른 헤더는 CoreMinimal.h 및 TestHarness.h 뒤에 std 라이브러리, UE 모듈, 타사 같은 범위별로 묶어서 배치해야 함
/* BDD 스타일 테스트 */
SCENARIO("Summary of test scenario", "[functional][feature][slow]") // 태그는 대괄호 [] 안에 배치
{
GIVEN("Setup phase")
{
// 변수 초기화, 테스트 전제 조건 구성 등
[...]
WHEN("I perform an action")
{
// 내부 스테이트 변경
[...]
THEN("Check for expectations")
{
REQUIRE(Condition_1);
REQUIRE(Condition_2);
// 이전 요청 실패 시 도달 못함
CHECK(Condition_3);
}
}
}
}
#endif //로우 레벨 테스트 포함
테스트 중심 개발 테스트 예시
TDD 스타일 테스트는 TEST_CASE 를 통한 테스트에 초점을 맞춥니다. 각 TEST_CASE 에는 테스트할 케이스를 설정하는 코드가 포함될 수 있습니다. 그런 다음, 실제 테스트 케이스는 여러 SECTION 블록으로 나뉠 수 있습니다. 각 SECTION 블록은 결과가 true인지 확인하는 검사를 진행합니다. SECTION 블록에서 확인을 모두 수행한 다음에는 TEST_CASE 끝에 필요한 해체 코드를 포함할 수 있습니다.
아래 코드 예시는 UE의 로우 레벨 테스트 프레임워크에서 TDD 스타일 테스트의 일반적인 개요를 보여줍니다.
#if WITH_LOW_LEVEL_TESTS // 묵시적 테스트에만 필요!
#include "CoreMinimal.h"
#include "TestHarness.h"
// 포함하는 다른 헤더는 CoreMinimal.h 및 TestHarness.h 뒤에 std 라이브러리, UE 모듈, 타사 같은 범위별로 묶어서 배치해야 함
/* 전통적인 TDD 스타일 테스트 */
TEST_CASE("Summary of test case", "[unit][feature][fast]")
{
// 이 테스트 케이스의 코드 구성
[...]
// 테스트는 여러 섹션으로 나뉠 수 있음
SECTION("Test #1")
{
REQUIRE(Condition_1);
}
...
SECTION("Test #n")
{
REQUIRE(Condition_n);
}
// 이 테스트 케이스의 해체 코드
[...]
}
#endif //로우 레벨 테스트 포함
#if WITH_LOW_LEVEL_TESTS 조건부 확인은 묵시적 테스트 컴파일용으로 예약되어 있습니다. 명시적 테스트에는 이러한 확인이 필요 없지만, 여전히 종속성에서 묵시적 테스트를 포함할 수는 있습니다. 그러려면 타깃 클래스에서 bWithLowLevelTestsOverride = true 를 설정합니다. 이 플래그는 WITH_LOW_LEVEL_TESTS 를 1로 강제 설정합니다.
테스트 케이스에서는 이중 콜론 :: 기호를 사용하여 테스트에서 계층구조를 생성할 수도 있습니다.
TEST_CASE("Organic::Tree::Apple::Throw an apple away from the tree") { ... }
이 섹션에 포함된 예시가 언리얼 엔진에서의 Catch2 또는 로우 레벨 테스트의 기능을 전부 보여주는 것은 아닙니다. 생성기, 벤치마크, 부동 소수점 근사치 헬퍼, 매처, 변수 캡처, 로깅 등은 모두 외부 Catch2 문서에서 자세한 내용을 확인할 수 있습니다.
추가 예시
엔진 디렉터리 Engine/Source/Runtime/Core/Tests 에 몇 가지 UE 전용 로우 레벨 테스트 예시가 있습니다. 로우 레벨 테스트 타입의 예시를 이어가려면 Engine/Source/Programs/LowLevelTests/Tests 의 ArchiveReplaceObjectRefTests.cpp 파일에 있는 TDD 스타일 테스트 예시를 확인하면 됩니다.
추가적인 로우 레벨 테스트 기능
테스트 그룹 및 라이프 사이클 이벤트
테스트 그룹화는 UE의 확장된 Catch2 라이브러리 기능입니다. 기본적으로 모든 테스트 케이스는 이름이 비어 있는 그룹으로 그룹화됩니다. 그룹에 테스트를 추가하려면, 첫 번째 파라미터로 그 이름을 지정하고 GROUP_* 버전의 테스트 케이스를 사용합니다.
GROUP_TEST_CASE("Apples", "Recipes::Baked::Pie::Cut slice", "[baking][recipe]")
GROUP_TEST_CASE_METHOD("Oranges", OJFixture, "Recipes::Raw::Juice Oranges", "[raw][recipe]")
GROUP_METHOD_AS_TEST_CASE("Pears", PoachInWine, "Recipes::Boiled::Poached Pears", "[desert][recipe]")
GROUP_REGISTER_TEST_CASE("Runtime", UnregisteredStaticMethod, "Dynamic", "[dynamic]")
각 그룹마다 용도를 스스로 설명하는 여섯 개의 라이프 사이클 이벤트가 있습니다. 다음 코드 섹션은 이러한 이벤트를 보여줍니다.
GROUP_BEFORE_ALL("Apples") {
std::cout << "Called once before all tests in group Apples, use for one-time setup.\n";
}
GROUP_AFTER_ALL("Oranges") {
std::cout << "Called once after all tests in group Oranges, use for one-time cleanup.\n";
}
GROUP_BEFORE_EACH("Apples") {
std::cout << "Called once before each test in group Apples, use for repeatable setup.\n";
}
GROUP_AFTER_EACH("Oranges") {
std::cout << "Called once after each tests in group Oranges, use for repeatable cleanup.\n";
}
GROUP_BEFORE_GLOBAL() {
std::cout << "Called once before all groups, use for global setup.\n";
}
GROUP_AFTER_GLOBAL() {
std::cout << "Called once after all groups, use for global cleanup.\n";
}
GROUP_TEST_CASE("Apples", "Test #1") {
std::cout << "Apple #1\n";
}
GROUP_TEST_CASE("Apples", "Test #2") {
std::cout << "Apple #2\n";
}
GROUP_TEST_CASE("Oranges", "Test #1") {
std::cout << "Orange #1\n";
}
GROUP_TEST_CASE("Oranges", "Test #2") {
std::cout << "Orange #2\n";
}
그러면 다음과 같이 출력됩니다.
Called once before all groups, use for global setup.
Called once before all tests in group Apples, use for one-time setup.
Called once before each test in group Apples, use for repeatable setup.
Apple #1.
Called once before each test in group Apples, use for repeatable setup.
Apple #2.
Orange #1.
Called once after each tests in group Oranges, use for repeatable cleanup.
Orange #2.
Called once after each tests in group Oranges, use for repeatable cleanup.
Called once after all tests in group Oranges, use for one-time cleanup.
Called once after all groups, use for global cleanup.
테스트 작성 및 구성 가이드라인
파일 및 폴더 명명 규칙
- 테스트 파일에 테스트를 설명하는 이름을 지정합니다.
SourceFile.cpp가 테스트하려는 소스 파일이라면, 테스트 파일 이름을SourceFileTest.cpp또는SourceFileTests.cpp로 지정합니다.
- 테스트되는 모듈의 폴더 구조를 미러링합니다.
Alpha/Omega/SourceFile.cpp는 묵시적 테스트의 경우Tests/Alpha/Omega/SourceFileTests.cpp에 매핑되고, 명시적 테스트의 경우Alpha/Omega/SourceFileTests.cpp에 매핑됩니다.
- 유닛 테스트가 아닌 한 테스트 파일 이름에 유닛 테스트에서 파생된 용어를 사용하지 마세요. 이 정의는 제한적이기 때문에 이름을 잘못 지으면 혼란이 발생할 수 있습니다.
유닛 테스트는 클래스나 메서드 같이 가장 작은 테스트 가능 유닛을 대상으로 하며, 몇 초 미만으로 실행되어야 합니다. 통합, 기능성, 스모크, 엔드 투 엔드, 퍼포먼스, 스트레스, 로드 테스트 등 다른 특화된 테스트 타입에도 같은 원칙이 적용됩니다. 또한, 모든 유닛 테스트를 하나의 Unit 서브폴더에 배치할 수도 있습니다.
명시적 테스트 리소스 폴더
임의의 바이너리 파일이나 에셋 파일, 기타 파일 시스템 기반의 리소스 같은 테스트 파일은 명시적 테스트를 위해 _리소스 폴더_에 배치해야 합니다. 이 폴더를 .Build.cs 모듈에 설정하세요.
SetResourcesFolder("TestFilesResources");
언리얼 빌드 툴(UBT) 이 플랫폼 디플로이 단계를 실행할 때, UBT는 이 폴더와 폴더의 모든 콘텐츠를 바이너리 경로에 복사하여 테스트가 바이너리 경로를 기준으로 리소스를 찾아 로드할 수 있게 만듭니다.
모범 사례
- 테스트 케이스와 시나리오에 태그를 제공합니다.
- 일관되고 짧은 이름을 사용합니다.
- 태그를 알맞게 활용합니다. 예를 들어,
[unit]태그가 지정된 테스트를 병렬로 실행할 수도 있고, 야간 빌드에서 실행되는 느린 실행 속도의 모든 테스트에 태그를 지정할 수도 있습니다.
- 각
SECTION또는THEN블록에 하나 이상의REQUIRE또는CHECK가 포함되어 있는지 확인합니다.- 원하는 예상 결과가 없는 테스트는 무의미합니다.
- 테스트 전제 조건을 충족해야 하는 경우
REQUIRE를 사용합니다.REQUIRE는 실패 시 즉시 중지되지만CHECK는 그렇지 않습니다.
- 결정 기준이 뚜렷하고 원하는 타입에 맞도록 테스트를 디자인합니다.
- 유닛, 통합, 기능성, 스트레스 등 타입별로 테스트를 생성하고 그룹화합니다.
- 퍼포먼스 테스트로 느린 테스트를 의도한 경우
[slow]또는[performance]를 태그로 지정합니다.- 이는 지속 통합(CI)/지속 전달(CD) 파이프라인에서 야간 빌드로 필터로 거르는 데 사용할 수 있습니다.
- 테스트 코드가 테스트된 모듈에 필요한 모든 플랫폼을 지원하는지 확인합니다.
- 예를 들어, 플랫폼 파일 시스템과 함께 작동하는 경우,
FPlatformFileManager클래스를 사용하세요. 이 테스트가 데스크톱 플랫폼에서만 실행된다고 가정하면 안 됩니다.
- 예를 들어, 플랫폼 파일 시스템과 함께 작동하는 경우,
- 테스트 그룹 및 라이프 사이클 이벤트를 사용하여 다른 테스트와는 별도로 특정 테스트를 초기화합니다.
- 테스트 그룹 및 라이프 사이클 이벤트 섹션을 참조하세요.
- 각 테스트 타입의 모범 사례를 따릅니다.
- 예를 들어, 유닛 테스트는 모의를 사용하고, 다른 모듈이나 로컬 데이터베이스 같은 외부 종속성에 의존하면 안 되며 순서 종속성이 있어서도 안 됩니다.
- 다양한 테스트 타입과 그 특성에 대한 자세한 내용은 로우 레벨 테스트 개요를 참조하세요.
다음 단계
테스트를 모두 작성한 다음에는 로우 레벨 테스트 빌드 및 실행 문서를 참조하여 테스트를 실행하세요.