Claude
Skills
Sign in
Back

ue-testing-debugging

Included with Lifetime
$97 forever

Use 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 debugging techniques. See ue-module-build-system for test module setup, and ue-cpp-foundations for general C++ logging patterns.

Writing & Docs

What this skill does


# UE Testing & Debugging

You are an expert in testing, debugging, and profiling Unreal Engine C++ projects.

## Context

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.

## Before You Start

Ask which area the user needs help with if unclear:
- **Automation tests** — unit/integration tests using IMPLEMENT_SIMPLE_AUTOMATION_TEST
- **Functional tests** — actor-based AFunctionalTest in maps
- **Logging** — UE_LOG, custom categories, verbosity filtering
- **Assertions** — check, ensure, verify and when to use each
- **Debug drawing** — DrawDebug helpers for runtime visualization
- **Console commands** — UFUNCTION(Exec), FAutoConsoleCommand, CVars
- **Profiling** — Unreal Insights, stat commands, SCOPE_CYCLE_COUNTER

---
## Automation Framework

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)`.

### Simple Tests

```cpp
// Source/MyGameTests/Private/MyFeature.spec.cpp
#include "Misc/AutomationTest.h"
// Must specify one application context flag AND exactly one filter flag
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyInventoryTest, "MyGame.Inventory.AddItem",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

bool FMyInventoryTest::RunTest(const FString& Parameters)
{
    UInventoryComponent* Inv = NewObject<UInventoryComponent>();
    Inv->AddItem(FName("Sword"), 1);
    TestEqual(TEXT("Item count after add"), Inv->GetItemCount(FName("Sword")), 1);
    TestTrue(TEXT("Has sword"), Inv->HasItem(FName("Sword")));
    TestFalse(TEXT("No axe"),   Inv->HasItem(FName("Axe")));
    TestNotNull(TEXT("Inv valid"), Inv);
    return true;
}
```

### Complex / Parameterized Tests

`IMPLEMENT_COMPLEX_AUTOMATION_TEST` requires overriding `GetTests()` to populate the parameter list, and `RunTest(Parameters)` receives each entry in turn.

```cpp
IMPLEMENT_COMPLEX_AUTOMATION_TEST(FMyAssetLoadTest, "MyGame.Assets.LoadByPath",
    EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter)

void FMyAssetLoadTest::GetTests(TArray<FString>& OutBeautifiedNames,
                                 TArray<FString>& OutTestCommands) const
{
    OutBeautifiedNames.Add(TEXT("Sword"));  OutTestCommands.Add(TEXT("/Game/Items/BP_Sword"));
    OutBeautifiedNames.Add(TEXT("Shield")); OutTestCommands.Add(TEXT("/Game/Items/BP_Shield"));
}

bool FMyAssetLoadTest::RunTest(const FString& Parameters)
{
    UObject* Asset = StaticLoadObject(UObject::StaticClass(), nullptr, *Parameters);
    if (!TestNotNull(TEXT("Asset loaded"), Asset)) { return false; }
    TestTrue(TEXT("Is DataAsset"), Asset->IsA<UPrimaryDataAsset>());
    return true;
}
```

### Test Assertion Methods

```cpp
// Equality — overloaded for int32, int64, float, double, FVector, FRotator,
//            FTransform, FColor, FLinearColor, FString, FStringView, FText, FName
TestEqual(TEXT("Label"), Actual, Expected);
TestEqual(TEXT("Float approx"), ActualF, ExpectedF, 0.001f);   // tolerance overload

// Boolean
TestTrue(TEXT("Label"), bCondition);
TestFalse(TEXT("Label"), bCondition);

// Pointer / validity
TestNotNull(TEXT("Label"), Ptr);          // fails if Ptr == nullptr
TestNull(TEXT("Label"), Ptr);             // fails if Ptr != nullptr
TestValid(TEXT("Label"), WeakPtr);        // IsValid() check on TWeakObjectPtr etc.

// Custom failure messages
AddError(FString::Printf(TEXT("Unexpected damage %d"), Damage));
AddWarning(TEXT("Deprecated path hit"));

// Add error and bail — returns false from RunTest if condition fails
UE_RETURN_ON_ERROR(Ptr != nullptr, TEXT("Ptr must not be null"));
```

### Test Flags Reference

| Flag | Meaning |
|---|---|
| `EditorContext` | Runs in the editor process |
| `ClientContext` | Runs in game client |
| `ServerContext` | Runs on dedicated server |
| `SmokeFilter` | Fast; runs on every CI check-in |
| `ProductFilter` | Project/game-level tests |

---
## Latent Commands (Async Testing)

Use latent commands when the test must wait for an async operation. `Update()` returns `true` when done, `false` to retry next frame.

```cpp
DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FWaitSecondsCommand, float, Duration);
bool FWaitSecondsCommand::Update() { return GetCurrentRunTime() >= Duration; }

// Enqueue inside RunTest; commands drain sequentially after RunTest returns
ADD_LATENT_AUTOMATION_COMMAND(FWaitSecondsCommand(2.0f));
```

See `references/automation-test-patterns.md` for delegate-wait, timeout, and post-async assertion patterns.

---
## Functional Tests

`AFunctionalTest` is a `UCLASS` actor placed in a test map. Override `StartTest()` and call `FinishTest()` when done.

```cpp
// MyFunctionalTest.h
UCLASS()
class MYGAMETESTS_API AMyFunctionalTest : public AFunctionalTest
{
    GENERATED_BODY()
public:
    virtual void StartTest() override;
private:
    UFUNCTION() void OnTimerExpired();
    FTimerHandle TimerHandle;
};

// MyFunctionalTest.cpp
void AMyFunctionalTest::StartTest()
{
    Super::StartTest();
    GetWorldTimerManager().SetTimer(TimerHandle, this,
        &AMyFunctionalTest::OnTimerExpired, 1.0f, false);
}

void AMyFunctionalTest::OnTimerExpired()
{
    bool bPassed = /* verify world state */ true;
    FinishTest(bPassed ? EFunctionalTestResult::Succeeded
                       : EFunctionalTestResult::Failed,
               TEXT("Timer-based check"));
}
```

Place actors in `Maps/Test_MyFeature.umap`. Run via `RunAutomationTest "MyGame.Functional.MyFeature"` or the Session Frontend.

### TimeLimit

```cpp
// Timeout — auto-fail if test exceeds time limit
void AMyFunctionalTest::PrepareTest()
{
    Super::PrepareTest();
    TimeLimit = 10.0f;  // seconds; 0 = no timeout (default)
}
```

---
## Logging

### Declaring and Defining a Category

```cpp
// MyModule.h  — visible across the module
DECLARE_LOG_CATEGORY_EXTERN(LogMyModule, Log, All);
//                           ^Name       ^Default  ^CompileTime max

// MyModule.cpp
DEFINE_LOG_CATEGORY(LogMyModule);

// Single-file static category (no extern needed)
DEFINE_LOG_CATEGORY_STATIC(LogMyHelper, Warning, All);
```

### UE_LOG Usage

```cpp
// UE_LOG(CategoryName, Verbosity, Format, ...)
UE_LOG(LogMyModule, Log,        TEXT("Player spawned: %s"), *PlayerName);
UE_LOG(LogMyModule, Warning,    TEXT("Inventory full, dropping item %s"), *ItemName);
UE_LOG(LogMyModule, Error,      TEXT("Failed to load asset at %s"), *AssetPath);
UE_LOG(LogMyModule, Verbose,    TEXT("Tick called, dt=%.4f"), DeltaTime);
UE_LOG(LogMyModule, Fatal,      TEXT("Unrecoverable save corruption")); // crashes

// Conditional log — condition evaluated only if category/verbosity is active
UE_CLOG(Health <= 0.f, LogMyModule, Warning, TEXT("Actor %s has zero health"), *GetName());
```

### Verbosity Levels (highest to lowest severity)

| Level | When to use |
|---|---|
| `Fatal` | Crash-worthy unrecoverable errors |
| `Error` | Operation failed, needs developer attention |
| `Warning` | Unexpected but recoverable condition |
| `Display` | User-visible output (always shown) |
| `Log` | Standard development info |
| `Verbose` | Detailed per-frame or per-call info |
| `VeryVerbose` | Trace-level; very high frequency |

### Structured Logging (UE 5.2+)

```cpp
#include "Logging/StructuredLog.h"
// Named fields — order does not matter; extra fields beyond the format string are allowed
UE_LOGFMT(LogMyModule, Warning,
    "Loading '{Name}' failed with error {Error}",
    ("Name", AssetName), ("Error", ErrorCode), ("Flags", LoadFlags));
```

### Runtime Log Filtering

```
# Command line: set a category to Verbose at startup
-LogCmds="LogMyModule Verbose, LogAI Warning"

# Console at runtime
Log LogMyModule Verbose
Log LogAI Warning
Log reset          # restore defaults
```

---
## Assertions

Assertions are

Related in Writing & Docs