$npx -y skills add quodsoler/unreal-engine-skills --skill ue-testing-debuggingUse when writing automation tests, functional tests, or any test in Unreal Engine. Also use when the user asks about "UE_LOG", logging, log categories, assertion, check, ensure, verify, DrawDebug, debug draw, console command, profiling, Unreal Insights, stat commands, or debuggin
| 1 | # UE Testing & Debugging |
| 2 | |
| 3 | You are an expert in testing, debugging, and profiling Unreal Engine C++ projects. |
| 4 | |
| 5 | ## Context |
| 6 | |
| 7 | Read `.agents/ue-project-context.md` for engine version, existing log categories, test infrastructure (automation modules, test maps), and project-specific conventions before providing guidance. |
| 8 | |
| 9 | ## Before You Start |
| 10 | |
| 11 | Ask which area the user needs help with if unclear: |
| 12 | - **Automation tests** — unit/integration tests using IMPLEMENT_SIMPLE_AUTOMATION_TEST |
| 13 | - **Functional tests** — actor-based AFunctionalTest in maps |
| 14 | - **Logging** — UE_LOG, custom categories, verbosity filtering |
| 15 | - **Assertions** — check, ensure, verify and when to use each |
| 16 | - **Debug drawing** — DrawDebug helpers for runtime visualization |
| 17 | - **Console commands** — UFUNCTION(Exec), FAutoConsoleCommand, CVars |
| 18 | - **Profiling** — Unreal Insights, stat commands, SCOPE_CYCLE_COUNTER |
| 19 | |
| 20 | --- |
| 21 | ## Automation Framework |
| 22 | |
| 23 | Automation tests live in a dedicated module (e.g., `MyGameTests`) that depends on `"AutomationController"`. Include the module in the editor target via `ExtraModuleNames` and conditionally in the game target via `if (bWithAutomationTests)`. |
| 24 | |
| 25 | ### Simple Tests |
| 26 | |
| 27 | ```cpp |
| 28 | // Source/MyGameTests/Private/MyFeature.spec.cpp |
| 29 | #include "Misc/AutomationTest.h" |
| 30 | // Must specify one application context flag AND exactly one filter flag |
| 31 | IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyInventoryTest, "MyGame.Inventory.AddItem", |
| 32 | EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) |
| 33 | |
| 34 | bool FMyInventoryTest::RunTest(const FString& Parameters) |
| 35 | { |
| 36 | UInventoryComponent* Inv = NewObject<UInventoryComponent>(); |
| 37 | Inv->AddItem(FName("Sword"), 1); |
| 38 | TestEqual(TEXT("Item count after add"), Inv->GetItemCount(FName("Sword")), 1); |
| 39 | TestTrue(TEXT("Has sword"), Inv->HasItem(FName("Sword"))); |
| 40 | TestFalse(TEXT("No axe"), Inv->HasItem(FName("Axe"))); |
| 41 | TestNotNull(TEXT("Inv valid"), Inv); |
| 42 | return true; |
| 43 | } |
| 44 | ``` |
| 45 | |
| 46 | ### Complex / Parameterized Tests |
| 47 | |
| 48 | `IMPLEMENT_COMPLEX_AUTOMATION_TEST` requires overriding `GetTests()` to populate the parameter list, and `RunTest(Parameters)` receives each entry in turn. |
| 49 | |
| 50 | ```cpp |
| 51 | IMPLEMENT_COMPLEX_AUTOMATION_TEST(FMyAssetLoadTest, "MyGame.Assets.LoadByPath", |
| 52 | EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) |
| 53 | |
| 54 | void FMyAssetLoadTest::GetTests(TArray<FString>& OutBeautifiedNames, |
| 55 | TArray<FString>& OutTestCommands) const |
| 56 | { |
| 57 | OutBeautifiedNames.Add(TEXT("Sword")); OutTestCommands.Add(TEXT("/Game/Items/BP_Sword")); |
| 58 | OutBeautifiedNames.Add(TEXT("Shield")); OutTestCommands.Add(TEXT("/Game/Items/BP_Shield")); |
| 59 | } |
| 60 | |
| 61 | bool FMyAssetLoadTest::RunTest(const FString& Parameters) |
| 62 | { |
| 63 | UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *Parameters); |
| 64 | if (!TestNotNull(TEXT("Asset loaded"), Asset)) { return false; } |
| 65 | TestTrue(TEXT("Is DataAsset"), Asset->IsA<UPrimaryDataAsset>()); |
| 66 | return true; |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | ### Test Assertion Methods |
| 71 | |
| 72 | ```cpp |
| 73 | // Equality — overloaded for int32, int64, float, double, FVector, FRotator, |
| 74 | // FTransform, FColor, FLinearColor, FString, FStringView, FText, FName |
| 75 | TestEqual(TEXT("Label"), Actual, Expected); |
| 76 | TestEqual(TEXT("Float approx"), ActualF, ExpectedF, 0.001f); // tolerance overload |
| 77 | |
| 78 | // Boolean |
| 79 | TestTrue(TEXT("Label"), bCondition); |
| 80 | TestFalse(TEXT("Label"), bCondition); |
| 81 | |
| 82 | // Pointer / validity |
| 83 | TestNotNull(TEXT("Label"), Ptr); // fails if Ptr == nullptr |
| 84 | TestNull(TEXT("Label"), Ptr); // fails if Ptr != nullptr |
| 85 | TestValid(TEXT("Label"), WeakPtr); // IsValid() check on TWeakObjectPtr etc. |
| 86 | |
| 87 | // Custom failure messages |
| 88 | AddError(FString::Printf(TEXT("Unexpected damage %d"), Damage)); |
| 89 | AddWarning(TEXT("Deprecated path hit")); |
| 90 | |
| 91 | // Add error and bail — returns false from RunTest if condition fails |
| 92 | UE_RETURN_ON_ERROR(Ptr != nullptr, TEXT("Ptr must not be null")); |
| 93 | ``` |
| 94 | |
| 95 | ### Test Flags Reference |
| 96 | |
| 97 | | Flag | Meaning | |
| 98 | |---|---| |
| 99 | | `EditorContext` | Runs in the editor process | |
| 100 | | `ClientContext` | Runs in game client | |
| 101 | | `ServerContext` | Runs on dedicated server | |
| 102 | | `SmokeFilter` | Fast; runs on every CI check-in | |
| 103 | | `ProductFilter` | Project/game-level tests | |
| 104 | |
| 105 | --- |
| 106 | ## Latent Commands (Async Testing) |
| 107 | |
| 108 | Use latent commands when the test must wait for an async operation. `Update()` returns `true` when done, `false` to retry next frame. |
| 109 | |
| 110 | ```cpp |
| 111 | DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitSecondsCommand, float, Duration); |
| 112 | bool FWaitSecondsCommand::Update() { return GetCurrentRunTime() >= Duration; } |
| 113 | |
| 114 | // Enqueue inside RunTest; comma |