CQTest 또는 코드 퀄리티 테스트(Code Quality Test) 는 테스트 픽스처와 일반적인 자동화 테스트 명령을 제공하는 언리얼 엔진(UE) FAutomationTestBase 의 익스텐션입니다. CQTest의 목표는 UE의 이전 테스트 프레임워크에 비해 새 테스트 작성을 간소화하고, 테스트 간 상태를 자동으로 리셋하는 테스트 이전 및 이후 액션을 지원하는 것입니다.
구성
CQTest는 프로젝트에 추가할 수 있는 C++ 모듈로 UE에 포함되어 있습니다. UE에는 해당 작동 방식을 살펴볼 수 있는 테스트가 포함된 여러 플러그인도 있습니다. 이 섹션에서는 CQTest 모듈을 구성하고 테스트 플러그인을 사용하는 방법을 안내합니다.
프로젝트에 CQTest 모듈 추가하기
프로젝트에서 CQTest 모듈을 사용하려면 다음 단계를 따르세요.
-
선택한 IDE(예: Visual Studio, Xcode, Rider)에서 프로젝트를 엽니다.
-
프로젝트의
.Build.cs파일을 열고 프로젝트 모듈의PrivateDependencyModuleNames에CQTest를 추가합니다.프로젝트의 Build.cs
PrivateDependencyModuleNames.AddRange ( new string[] { "Core", "CoreUObject", "Engine", "CQTest" } ); -
프로젝트를 컴파일합니다. 컴파일이 완료되면 이제 프로젝트에서 CQTest를 사용할 수 있습니다.
테스트 플러그인
언리얼 엔진에는 CQTest 동작의 유효성을 검사하고 시연하는 테스트 세트를 제공하는 두 개의 플러그인이 포함되어 있습니다.
- 코드 퀄리티 테스트 언리얼 테스트 플러그인(Code Quality Tests Unreal Test Plugin)
- 향상된 입력 코드 퀄리티 언리얼 테스트 플러그인(Enhanced Input Code Quality Unreal Test Plugin)
플러그인 활성화하기
테스트 플러그인을 활성화하는 단계는 다음과 같습니다.
-
언리얼 에디터(Unreal Editor) 를 엽니다.
-
편집(Edit) > 플러그인(Plugins) 을 엽니다.
-
코드 퀄리티 언리얼 테스트 플러그인(Code Quality Unreal Test Plugin) 을 검색합니다.
-
위의 플러그인 중 하나를 활성화합니다.
-
메시지가 표시되면 언리얼 에디터를 재시작합니다.
이러한 플러그인을 활성화하면 프로젝트에 해당 테스트를 사용할 수 있습니다.
플러그인 테스트 실행하기
언리얼 에디터 내 플러그인이 제공하는 테스트를 실행하는 단계는 다음과 같습니다.
-
프로젝트와 함께 언리얼 에디터를 실행합니다.
-
툴(Tools) 드롭다운 메뉴로 이동하여 세션 프론트엔드(Sessions Frontend) 를 선택합니다.
-
기본적으로 테스트는 Product.Plugins.CQTest 아래에 가장 먼저 나열됩니다.
-
실행할 테스트를 선택하고 테스트 시작(Start Tests) 을 누릅니다.
테스트 매크로
CQTest는 여러 테스트 매크로를 제공합니다.
| 매크로 | 설명 |
|---|---|
TEST |
기본 테스트 오브젝트입니다. |
TEST_CLASS |
구성, 분리, 공통 상태 및 그룹화를 포함하는 테스트 오브젝트입니다. 이 매크로에 사용할 수 있는 액션에 대한 자세한 내용은 아래의 레이턴트 액션을 참조하세요. |
TEST_CLASS_WITH_ASSERTS |
커스텀 어서터를 포함하는 테스트 오브젝트입니다. |
TEST_CLASS_WITH_BASE |
다른 테스트 오브젝트의 상속을 받을 수 있는 테스트 오브젝트입니다. |
TEST_CLASS_WITH_FLAGS |
다양한 자동화 테스트 플래그를 사용할 수 있는 테스트 오브젝트입니다. |
TEST_CLASS_WITH_BASE_AND_FLAGS |
다른 테스트 오브젝트의 상속을 받고 다양한 커스텀 자동화 테스트 플래그를 사용할 수 있는 테스트 오브젝트입니다. |
TEST_CLASS_IMPL |
위 매크로에서 커스텀 어서터, 다른 테스트 오브젝트의 상속을 받을 테스트 오브젝트를 지정하거나 커스텀 자동화 테스트 플래그를 허용하는 데 사용되는 베이스 매크로입니다. |
프레임워크 확장하기
CQTest 프레임워크는 몇 가지 영역에서 익스텐션을 사용하도록 설계되었습니다. 코드 예제는 CQTestTests/Private/ExtensionTests.cpp 를 참조하세요.
테스트 컴포넌트
CQTest 프레임워크는 상속보다는 컴포지션을 선호합니다. 새 컴포넌트 생성은 프레임워크 확장을 위한 기본 메커니즘입니다. 사용 가능한 일부 컴포넌트는 다음과 같습니다.
| 컴포넌트 | 설명 |
|---|---|
SpawnHelper |
액터 및 기타 오브젝트 스폰 기능을 지원합니다. ActorTestSpawner 및 MapTestSpawner 에 의해 구현됩니다. |
ActorTestSpawner |
테스트에서 액터를 스폰할 수 있도록 최소한의 UWorld를 생성하고 디스포닝을 관리합니다. |
MapTestSpawner |
임시 맵을 생성하거나 지정된 레벨을 엽니다. 이를 사용하여 앞서 언급한 테스트 월드에서 액터를 스폰할 수 있습니다. |
CQTestBlueprintHelper(폐기됨) |
테스트에서 블루프린트 오브젝트를 스폰하는 기능을 지원하며, 이 컴포넌트는 폐기되었으며 대신 CQTestAssetHelper가 사용됩니다. -- 아래의 헬퍼 오브젝트 및 메서드를 참조하세요. |
PIENetworkComponent |
서버와 클라이언트 컬렉션을 생성합니다. 리플리케이션 테스트에 유용합니다. PIENetworkComponent 는 에디터 컨텍스트 내에서만 사용할 수 있는 서버 및 클라이언트 PIE 인스턴스를 구성합니다. PIENetworkComponent 를 사용하는 테스트에서는 EAutomationTestFlags::EditorContext 플래그를 지정해야 합니다. |
InputTestActions |
Pawn 에 InputActions 를 주입합니다. |
CQTestSlateComponent |
UI가 업데이트되면 현재 테스트에 알립니다. |
헬퍼 오브젝트 및 메서드
이 테스트 프레임워크는 다음과 같은 헬퍼 오브젝트와 메서드를 제공합니다.
| 헬퍼 오브젝트 | 설명 |
|---|---|
| 'FAssetBuilder' | CQTestAssetHelper 네임스페이스 메서드와 함께, 또는 AssetRegistry 를 직접 검색할 때 사용할 수 있는 에셋 필터를 생성합니다. |
CQTestAssetHelper |
다음 작업에 사용할 수 있는 헬퍼 메서드가 포함된 네임스페이스입니다.
|
예외 처리하기
모든 플랫폼이 예외를 지원하는 것은 아니므로, 어서트에서 예외에 의존할 수는 없습니다.
다음은 예외를 지원하지 않는 플랫폼에서 테스트를 실행하기 위한 몇 가지 옵션입니다.
-
예외를 발생시키고 예외를 지원하는 플랫폼에서만 테스트를 실행합니다.
-
각 어서트를 확인하고 실패하면 반환하도록
[[nodiscard]]부울을 반환합니다. -
중요한 경우 일반 부울을 반환하고 사람들에게 확인을 요청합니다.
예외는 헬퍼 함수와 람다에서 작동하며 개발자가 직접 신경 쓰지 않아도 된다는 장점이 있습니다.
일반 부울은 덜 번잡하고 개발자가 IntelliSense를 사용할 수 있지만, 오류가 발생하기 더 쉽습니다.
기본적으로 사용되는 구현은 '[[nodiscard]] 부울이며, ASSERT_THAT 헬퍼 매크로로 조기 반환 검사를 자동으로 수행합니다. 필요에 따라 == 및 != 연산자를 정의한다면, Assert.AreEqual 및 Assert.AreNotEqual` 메서드 내에서 자체 타입을 사용할 수 있습니다.
또한 ToString 메서드도 정의한다면, 오류 메시지에 해당 타입의 스트링 버전이 출력됩니다. 프레임워크가 값을 출력하는 방법을 알지 못하면 오류가 발생합니다.
프레임워크에 스트링을 제공하는 방법에 대한 예제는 CQTestTests/Private/Assert/CQTestConvertTests.cpp 에서 확인할 수 있으며, 아래는 간단한 예제입니다.
예제 C++
struct MyCustomType
{
int32 Value;
bool operator==(const MyCustomType& other) const
{
return Value == other.Value;
}
bool operator!=(const MyCustomType& other) const
{
return !(*this == other);
}
FString ToString() const
{
//스트링 로직으로
return FString();
}
};
enum struct MyCustomEnum
{
Red, Green, Blue
};
template<>
FString CQTestConvert::ToString(const MyCustomEnum&)
{
//스트링 로직으로
return FString();
}
레이턴트 액션
CQTest는 TEST_CLASS 매크로를 통해 레이턴트 액션을 지원합니다. 각 단계에서 레이턴트 액션을 완료한 후 다음 단계로 넘어갑니다. 레이턴트 액션 중에 어서트가 발생하면 더 이상의 레이턴트 액션은 처리되지 않지만 AFTER_EACH 메서드는 계속 호출됩니다.
예제 C++
TEST_CLASS(LatentActionTest, "Game.Test")
{
uint32 calls = 0;
BEFORE_EACH()
{
AddCommand(new FExecute([&]() { calls++; }));
}
AFTER_EACH()
{
AddCommand(new FExecute([&]() { calls++; })); // 레이턴트 액션이므로 다음 줄 이후에 실행됨
ASSERT_THAT(AreEqual(2, calls));
}
TEST_METHOD(PerformLatentAction)
{
ASSERT_THAT(AreEqual(1, calls));
AddCommand(new FExecute([&]() { calls++; }));
}
};
CQTest는 다음과 같은 추가 레이턴트 액션을 제공합니다.
| 레이턴트 액션 타입 | 설명 |
|---|---|
FExecute |
한 번만 실행되는 액션입니다. |
FWaitUntil |
완료되거나 기간이 타임아웃을 초과할 때까지 여러 틱에 걸쳐 실행되는 액션입니다. 타임아웃 전에 조건을 충족하지 못하면 액션이 실패합니다. |
FWaitDelay |
지정된 기간을 대기하는 액션입니다. 경고: 시간 지정된 대기를 사용하면 다양한 런타임으로 인해 불안정성이 발생할 수 있습니다. 대신 위의 FWaitUntil 액션을 사용하는 것이 좋습니다. |
FRunSequence |
이전 액션이 모두 완료된 후에만 레이턴트 액션 컬렉션이 순서대로 발생하도록 보장하는 액션입니다. |
명령 빌더
유창한 명령 빌더도 명령에 사용할 수 있습니다.
예제 C++
TEST_METHOD(SomeTest)
{
TestCommandBuilder
.Do([&]() { StepOne(); })
.Then([&]() { StepTwo(); })
.Until([&]() { return StepThreeComplete(); })
.Then([&]() { ASSERT_THAT(IsTrue(SomethingImportant)); });
}
명령 빌더는 위에서 언급한 레이턴트 액션을 래핑하는 명령을 제공합니다. 다음 명령을 사용할 수 있습니다.
| 명령 | 설명 |
|---|---|
Do/Then |
실행될 지정된 람다를 포함한 FExecute 레이턴트 액션을 추가하는 명령입니다. |
StartWhen/Until |
평가될 지정된 람다를 포함한 FWaitUntil 레이턴트 액션을 추가하는 명령입니다. |
WaitDelay |
계속하기 전에 지정된 기간을 대기하는 명령입니다. 시간 지정된 대기를 사용하면 다양한 런타임으로 인해 불안정성이 발생할 수 있으므로 위의 |
OnTearDown/CleanUpWith |
테스트 후에 실행될 지정된 람다를 포함한 FExecute 레이턴트 액션을 추가하는 명령입니다. 여러 번 호출하여 여러 개의 클린업 레이턴트 액션을 추가할 수 있습니다. 혼동을 피하기 위해 OnTearDown 또는 CleanUpWith를 사용하는 액션은 역순으로 실행합니다. |
이 프레임워크는 현재 레이턴트 액션 내에서 레이턴트 액션 추가를 지원하지 않습니다. 대신 일련의 독립된 단계로 액션을 추가할 수 있습니다.
CQTest 예제
다음과 같이 간단하게 테스트를 작성할 수 있습니다.
예제 C++
#include "CQTest.h"
TEST(MinimalTest, "Game.MyGame")
{
ASSERT_THAT(IsTrue(true));
}
다음을 포함하는 테스트 오브젝트에는 TEST_CLASS 매크로를 사용합니다.
-
구성
-
분리
-
여러 테스트 간의 공통 상태
-
관련 테스트 그룹화
TEST_CLASS 매크로는 다음과 같이 사용할 수 있습니다.
예제 C++
#include "CQTest.h"
TEST_CLASS(MyTest, "Game.MyGame")
{
bool SetupCalled = false;
uint32 SomeNumber = 0;
Thing* Thing = nullptr;
// 이 TEST_CLASS의 모든 테스트 전에 실행되는 선택적 스태틱 메서드입니다.
// 사용하지 않는 경우 제거
BEFORE_ALL()
{
//레벨 로딩과 같이 모든 테스트에 공유되는 로직을 수행합니다.
}
BEFORE_EACH()
{
//이 TEST_CLASS의 각 테스트 전에 호출되는 로직을 수행합니다.
SetupCalled = true;
SomeNumber++;
Thing = new Thing();
}
AFTER_EACH()
{
//이 TEST_CLASS의 각 테스트 후에 호출되는 로직을 수행합니다.
delete Thing;
}
// 이 TEST_CLASS의 모든 테스트 후에 실행되는 선택적 스태틱 메서드입니다.
// 사용하지 않는 경우 제거
AFTER_ALL()
{
//BEFORE_ALL에 할당된 모든 리소스의 클린업을 수행합니다.
}
protected:
bool HelperMethod() const
{
return true;
}
TEST_METHOD(BeforeRunTest_CallsSetup)
{
ASSERT_THAT(IsTrue(SetupCalled));
}
TEST_METHOD(ProtectedMembers_AreAccessible)
{
ASSERT_THAT(IsTrue(HelperMethod()));
}
TEST_METHOD(DataMembers_BetweenTestRuns_AreReset)
{
ASSERT_THAT(AreEqual(1, SomeNumber));
}
};