Unreal Engine: Testing and Profiling

·4 min read·Graham Mace
Unreal Engine: Testing and Profiling

After last week’s state machines, this week was about testing and profiling: the [Automation Test Framework]https://dev.epicgames.com/documentation/unreal-engine/automation-test-framework-in-unreal-engine, functional and unit testing, and performance analysis with Unreal Insights – the discipline that keeps everything built so far from quietly rotting.

Notes below.


Automated Testing

The starting questions: what can go wrong, and what do you test to build your test cases?

The motivating example from the course: a third-person stealth game whose core feature is AI perception. You want to guarantee AI sight always works. But with a large world and many levels containing spawners, testing a change by playing through the entire game is tedious – so you set up tests to make sure the major feature isn’t broken.

Error handling vs testing

An important distinction – especially between silent error handling and asserts/breaks:

  • Asserts cause game-breaking crashes: execution does not progress beyond the assert. They’re needed to ensure required pointers or objects are valid – preventing a whole class of bugs caused by a missing pointer or object
  • But asserts (and Ensure) only catch what they’re told to catch. They won’t catch logic errors – only testing catches those
    • Example: an enemy killing a friendly because the logic never checked whether the target is an ally. No pointer is null; nothing asserts. A test map with two allies, run for a number of seconds, asserting that neither ally took damage, catches it

Functional Testing

The AI perception demo as a functional test:

  • Create a folder for test maps and assets
  • Create a map for the test
  • Create a Blueprint of type Functional Test
  • Fill in the logic and call the Finish Test node - without it, the test times out and reports failure
  • Create variables and functions to keep the test’s logic legible

Keeping tests alive: functional tests must not go stale. When code or logic changes, the test gets updated or deleted. Stale tests become like overwhelming warning messages – they get ignored, and eventually the whole test suite gets ignored because nobody can tell whether a test failed or simply stopped being relevant.

Unit Testing

Examples of what unit tests are good for:

  • Leaderboards: unit testing that the scoring logic ranks correctly
  • Stress-testing a spatial algorithm: generate random coordinates, find the closest point, and brute-force the expected answer to verify the sort
  • A quadtree implementation: feed it thousands of locations to verify the getters return what’s intended
  • Small utilities: string lengths, floating-point precision

Profiling

Performance is a key factor in build stability: as the game grows in complexity, performance drops. Unreal Insights tracks framerate and shows why.

Context for the numbers: a frame processes game logic, rendering, and various engine/game code. If the logic takes too long to process, the game slows down. The ideal framerate is 60; some games ship at 30; VR targets 90+.

Workflow with the test project (a Scan feature on the player doing a sphere trace, tied to an input action):

  1. Start a trace - the button in the lower right starts it; press again to stop
  2. Parse the information looking for spikes - There’s always a spike when the game loads in: BeginPlay and Construction Script events. Expected, not a bottleneck
  3. Click on a frame to see the breakdown of functions called and their timing
  4. Filter for Sphere Cast and double-click the function to see the time taken
  5. Remove other charts to reduce noise; zoom out to see the sphere cast in isolation
  6. Click anywhere on the timeline to show that frame’s breakdown; clicking PerformScan shows the detailed breakdown of functions called

What this bought us: a direct pointer at the bottleneck. Vector math calls turn out to be very fast – the slow part was elsewhere.

Quick console alternative: stat Unit brings up the per-frame timing stats overlay.


Notes on things that stood out

Two things this week reframed how I think about the whole course’s worth of work. First, the assert/testing distinction is really a humility lesson: asserts catch the bugs you imagined, tests catch the bugs you didn’t. The friendly-fire example is perfect – nothing crashes, nothing logs, the game is just wrong.

Second, the stale-test warning is sneakier than it sounds. A test suite with a few dead tests trains you to ignore red, which is functionally identical to having no test suite – with all the maintenance cost and none of the benefit. Tests are code, and code rots.

And stat Unit is going to be muscle memory from here on out.