Quick answer: Start with three automated tests — a launch test, a scene load test, and a save/load roundtrip test — run them on every build via GitHub Actions, and treat a rising crash rate in Bugnet as an automated regression signal for everything the test suite does not cover.
Most indie studios do not have a QA department. Testing happens when the developer plays the game, when friends and family try a build, and eventually when playtesters or early access players find bugs. This system works until it does not — until a patch that fixes one thing quietly breaks another, and nobody catches it until players start leaving negative reviews. Automated regression testing is the structural answer to that problem, and you do not need a QA department to implement it. You need a realistic scope and a willingness to start small.
What Regression Testing Means for Games
In software development, regression testing means re-running a set of tests after a change to verify that existing functionality still works. In game development, the same concept applies but with a game-specific definition of “existing functionality.”
A regression in a game is when a bug you fixed comes back, or when a change to one system unexpectedly breaks another. Common examples:
- Fixing the inventory system breaks the save/load cycle
- Updating the shader pipeline causes a crash on integrated graphics that was previously fixed
- A new level causes the scene loader to throw a null reference that was never triggered before
- An audio refactor silently disables footstep sounds in levels that were working before
Manual regression testing — a human playing through known-fixed areas after every build — is thorough but slow and does not scale. Automated regression testing trades coverage for speed: it catches the most common failure modes automatically and leaves the subtle, creative, and context-dependent testing to humans.
The Minimum Viable Test Suite
Do not build a comprehensive automated test suite. Build a minimum viable one and expand it as your team feels the pain of untested areas. Three tests will take you a surprisingly long way.
1. The Launch Test
Launch the game in headless mode and verify it reaches the main menu without crashing. This catches: missing asset references, null pointer errors during initialization, dependency ordering bugs, and configuration loading failures. It is the most valuable single automated test you can write, and it runs in under 60 seconds.
2. The Scene Load Test
Load every scene (or every named level) in sequence and verify each one loads without errors. This catches broken scene references, missing prefabs, corrupted level data, and initialization errors in scene-specific code. For large games with many scenes, run this on a subset of critical scenes if a full pass takes too long.
3. The Save/Load Roundtrip Test
Create a save state in a known condition, save it, load it, and verify the loaded state matches the saved state. This is the single most valuable integration test for games with progression systems. Save/load bugs are devastatingly common and unusually hard to catch through manual play, because the bug often only manifests after specific sequences of events that the developer has already played through many times without encountering.
Unity Test Framework
Unity includes a built-in test framework accessible through the Test Runner window (Window → General → Test Runner). It supports two test modes:
- Edit Mode tests: Run without entering Play Mode. Best for testing pure logic, data structures, and utility functions. Fast and deterministic.
- Play Mode tests: Run inside the Unity player, either in-editor or in a standalone build. Necessary for testing anything that requires the Unity runtime: physics, scene loading, input handling, coroutines.
Your save/load roundtrip test and scene load test will need to be Play Mode tests. The launch test can be a Play Mode test that verifies the game reaches a known initialization state.
Unity tests use the NUnit framework and UnityTest attribute for coroutine-based async tests:
[UnityTest]
public IEnumerator SaveLoad_RoundtripPreservesPlayerHealth() {
GameManager.Instance.Player.Health = 75;
SaveSystem.Save("test_slot");
GameManager.Instance.Player.Health = 0;
SaveSystem.Load("test_slot");
yield return null;
Assert.AreEqual(75, GameManager.Instance.Player.Health);
}
Godot’s Built-In Testing Tools
Godot 4 includes a built-in unit testing framework accessible via the GdUnit4 plugin (the most widely used community testing library) or through Godot’s native gdscript-test capabilities. GdUnit4 integrates with the editor and provides an assertion library, test runner, and scene testing utilities.
For headless CI execution, Godot can run tests without a display server:
godot --headless --path . -s addons/gdUnit4/bin/GdUnitCmdTool.gd
This command runs all registered GdUnit4 tests in headless mode and exits with a non-zero status code if any tests fail — exactly the behavior needed for a CI pipeline.
Screenshot Comparison Tests for Visual Regressions
Some regressions are not logic errors — they are visual. A shader change makes lighting look wrong. A UI update clips text in a certain language. A particle system change looks different than intended. Screenshot comparison tests catch these automatically.
The approach: render a known scene to a screenshot, compare it pixel-by-pixel (or with a configurable tolerance for anti-aliasing variation) against a stored reference image, and fail the test if the difference exceeds a threshold.
- Keep reference screenshots in version control alongside the tests
- Update reference screenshots intentionally when you make deliberate visual changes
- Use a tolerance threshold of 1–5% pixel difference to avoid false positives from anti-aliasing and temporal rendering effects
- Focus screenshot tests on UI screens, which have deterministic rendering and low visual variance
Screenshot comparison tests are harder to maintain than logic tests but invaluable for catching unintended visual changes before they reach players.
Integrating Tests into a CI Pipeline with GitHub Actions
Running tests locally is better than not running them. Running tests automatically on every commit is far better still, because it removes the human decision to run (or skip) the tests before pushing.
For Unity, Valve’s GameCI project maintains official GitHub Actions for building and testing Unity projects. A minimal workflow that runs tests on every push to main:
name: Game Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: game-ci/unity-test-runner@v4
with:
unityVersion: 2023.3.0f1
testMode: playmode
For Godot, the community maintains a godot-ci Docker image that runs Godot in headless mode on Linux CI runners. The pattern is similar: check out the repository, run the headless test command, fail the workflow if tests fail.
The critical discipline: make the CI pipeline block merges to main. A CI test that does not block merges is advisory at best. Connect your test workflow to branch protection rules so that failing tests prevent the merge.
Crash Rate as an Automated Regression Signal
Your automated test suite will never cover every possible regression. The gap between what automated tests check and what players actually experience is where Bugnet’s crash rate monitoring fills in.
Configure a crash rate alert in Bugnet to notify you when the crash-free session rate drops below your defined threshold after a new build deployment. This gives you a continuous regression signal that covers every player on every hardware configuration — far broader coverage than any automated test suite can achieve.
The combination of automated tests and crash rate monitoring gives you two complementary signals:
- Automated tests: Fast, pre-deployment signal on known failure modes. Catches regressions before players see them.
- Crash rate monitoring: Post-deployment signal on everything else. Catches regressions that tests did not cover, on real hardware in real usage conditions.
The 80/20 Rule for Game Testing
In game development, 80% of crashes and regressions come from 20% of the codebase: initialization, save/load, scene loading, and the input handling layer. These are the areas where automated tests pay the highest return on investment.
The remaining 80% of the codebase — the combat system, the dialogue tree, the crafting UI — is best tested manually by humans who can judge whether the game feels right, not just whether it runs without errors. Automated tests for this layer are expensive to write and maintain, and they test the wrong things (correctness of execution rather than quality of experience).
“Automate the tests for the things computers are good at checking. Use humans for the things humans are good at judging. Trying to automate everything is as wasteful as automating nothing.”
Start with the launch test, the scene load test, and the save/load roundtrip test. Run them on every build in CI. Watch your Bugnet crash dashboard after every deployment. Expand the test suite when you feel the pain of a regression that those three tests would not have caught. That is the practical starting point — not a comprehensive strategy designed in advance, but a system that grows with your actual testing needs.
Three tests that run automatically are worth more than thirty tests that you’ll run “when you have time.”