본문으로 건너뛰기
cccskills
무료GitHub에 공개

unity-testing

Unity 6 testing guide. Use when writing unit tests, integration tests, or doing TDD with Unity Test Framework. Covers Edit Mode and Play Mode tests, NUnit attributes ([Test], [UnityTest], [SetUp], [TearDown]), testing MonoBehaviours and coroutines, and CI/CD integration. Based on Unity 6.3 LTS documentation.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구Unity
설치 방법 보기

포함된 파일(2)

  • SKILL.md15.5 KB
  • references/test-framework.md16.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Unity Test Framework

Test Framework Overview

The Unity Test Framework (UTF) is Unity's built-in testing solution, integrating a custom version of NUnit (based on NUnit 3.5) adapted for Unity. It enables testing C# code in Edit Mode, Play Mode, and on target platforms (Standalone, Android, iOS).

UTF extends NUnit with Unity-specific attributes ([UnityTest], [UnitySetUp], [UnityTearDown]) that support coroutines, yield instructions, and frame-based execution.

Key capabilities:

  • Edit Mode tests for editor extensions and pure logic
  • Play Mode tests for gameplay, physics, and coroutine-based code
  • Standalone player test builds for platform-specific validation
  • Command-line execution for CI/CD pipelines
  • NUnit XML result output

Source: Unity Test Framework 2.0 Manual


Setup and Configuration

Installing the Test Framework

UTF ships with Unity by default. Open the Test Runner via Window > General > Test Runner.

Creating a Test Assembly

Test code must live in its own assembly definition (.asmdef) that references NUnit.

Create via Window > General > Test Runner (click Create a new Test Assembly Folder) or Assets > Create > Testing > Test Assembly Folder.

This creates a Tests folder with an .asmdef file preconfigured with references to:

  • nunit.framework.dll
  • UnityEngine.TestRunner
  • UnityEditor.TestRunner (Edit Mode only)

Edit Mode Assembly Definition (.asmdef)

{
    "name": "Tests",
    "references": [
        "UnityEngine.TestRunner",
        "UnityEditor.TestRunner"
    ],
    "includePlatforms": ["Editor"],
    "defineConstraints": ["UNITY_INCLUDE_TESTS"],
    "autoReferenced": false
}

Play Mode Assembly Definition (.asmdef)

{
    "name": "Tests.PlayMode",
    "references": [
        "UnityEngine.TestRunner",
        "UnityEditor.TestRunner",
        "MyGameAssembly"
    ],
    "includePlatforms": [],
    "optionalUnityReferences": ["TestAssemblies"],
    "defineConstraints": ["UNITY_INCLUDE_TESTS"],
    "autoReferenced": false
}

Important: To test your game code, add a reference to the assembly containing the code under test in the references array.

Source: Creating Test Assemblies


Edit Mode vs Play Mode Tests

AspectEdit ModePlay Mode
Execution contextEditorApplication.update callback loopCoroutine on a MonoBehaviour
Editor accessFull Editor + game codeGame code only (unless in Editor)
Coroutine supportyield return null skips one updateFull yield instructions (WaitForSeconds, etc.)
Platform targetsEditor onlyEditor Play Mode + standalone players
Use casesEditor extensions, pure logic, serializationPhysics, gameplay, MonoBehaviour lifecycle
Assembly platform"includePlatforms": ["Editor"]"includePlatforms": []

When to use Edit Mode:

  • Testing editor tools, custom inspectors, ScriptableObject logic
  • Pure C# logic that does not depend on runtime systems
  • Tests that need access to UnityEditor APIs

When to use Play Mode:

  • Testing MonoBehaviour lifecycle (Awake, Start, Update)
  • Physics interactions requiring WaitForFixedUpdate
  • Coroutine and async workflows
  • Scene loading and object instantiation at runtime

Best practice: Use NUnit [Test] instead of [UnityTest] unless you need yield instructions. [Test] runs faster since it does not require coroutine overhead.

Source: Edit Mode vs Play Mode Tests


Writing Tests

Create test scripts via Assets > Create > Testing > C# Test Script or using the Test Runner's Create a new Test Script button.

Basic Test Structure

using NUnit.Framework;

[TestFixture]
public class HealthSystemTests
{
    private HealthSystem _health;

    [SetUp]
    public void SetUp() => _health = new HealthSystem(maxHealth: 100);

    [TearDown]
    public void TearDown() => _health = null;

    [Test]
    public void TakeDamage_ReducesHealth()
    {
        _health.TakeDamage(30);
        Assert.AreEqual(70, _health.CurrentHealth);
    }

    [Test]
    public void TakeDamage_CannotGoBelowZero()
    {
        _health.TakeDamage(150);
        Assert.AreEqual(0, _health.CurrentHealth);
    }

    [Test]
    public void Heal_IncreasesHealth()
    {
        _health.TakeDamage(50);
        _health.Heal(20);
        Assert.AreEqual(70, _health.CurrentHealth);
    }
}

NUnit Attributes Reference

AttributePurpose
[Test]Marks a method as a synchronous test
[TestFixture]Marks a class as containing tests
[SetUp]Runs before each test method
[TearDown]Runs after each test method
[OneTimeSetUp]Runs once before all tests in the fixture
[OneTimeTearDown]Runs once after all tests in the fixture
[TestCase(args)]Parameterized test with inline data
[Values(args)]Provides values for a test parameter
[Category("name")]Categorizes tests for filtering
[Ignore("reason")]Skips a test with a reason
[Timeout(ms)]Sets a timeout for the test

Unity-Specific Attributes

AttributePurpose
[UnityTest]Coroutine-based test supporting yield instructions
[UnitySetUp]Coroutine-based setup (supports yield)
[UnityTearDown]Coroutine-based teardown (supports yield)
[RequiresPlayMode]Forces test to run in Play Mode (true) or Edit Mode (false)
[UnityPlatform]Restricts test to specific platforms
[TestMustExpectAllLogs]Requires all log messages to be expected
[PrebuildSetup]Runs setup before player build
[PostBuildCleanup]Runs cleanup after player build
[ConditionalIgnore]Conditionally ignores tests
[TestRunCallback]Subscribes to test progress updates

Common Assertions

Assert.AreEqual(expected, actual);           // Equality
Assert.IsTrue(condition);                    // Boolean
Assert.IsNotNull(obj);                       // Null check
Assert.Throws<ArgumentException>(() => F()); // Exception
Assert.AreEqual(1.0f, actual, 0.01f);       // Float tolerance
StringAssert.Contains("sub", actualString);  // String
CollectionAssert.Contains(list, item);       // Collection

See references/test-framework.md for the full assertion API reference.

Source: Creating Tests


Testing MonoBehaviours

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using System.Collections;

[TestFixture]
public class PlayerControllerTests
{
    private GameObject _playerObject;
    private PlayerController _player;

    [UnitySetUp]
    public IEnumerator SetUp()
    {
        _playerObject = new GameObject("Player");
        _player = _playerObject.AddComponent<PlayerController>();
        yield return null; // Wait one frame for Awake/Start
    }

    [UnityTearDown]
    public IEnumerator TearDown()
    {
        Object.Destroy(_playerObject);
        yield return null; // Wait for destruction
    }

    [UnityTest]
    public IEnumerator Player_MovesForward_WhenInputApplied()
    {
        var startPos = _player.transform.position;
        _player.Move(Vector3.forward);

        yield return new WaitForFixedUpdate();

        Assert.Greater(_player.transform.position.z, startPos.z);
    }
}

Testing Coroutines and Async

Edit Mode UnityTest (yields in EditorApplication.update)

[UnityTest]
public IEnumerator EditorUtility_WhenExecuted_ReturnsSuccess()
{
    var utility = RunEditorUtilityInTheBackground();
    while (utility.isRunning) { yield return null; }
    Assert.IsTrue(utility.isSuccess);
}

Play Mode UnityTest (coroutine-based)

[UnityTest]
public IEnumerator GameObject_WithRigidBody_WillBeAffectedByPhysics()
{
    var go = new GameObject();
    go.AddComponent<Rigidbody>();
    var originalPosition = go.transform.position.y;
    yield return new WaitForFixedUpdate();
    Assert.AreNotEqual(originalPosition, go.transform.position.y);
}

Available Yield Instructions (Play Mode)

Yield InstructionEffect
yield return nullSkip one frame
yield return new WaitForSeconds(t)Wait for t seconds (scaled time)
yield return new WaitForSecondsRealtime(t)Wait for t real-time seconds
yield return new WaitForFixedUpdate()Wait for next physics update
yield return new WaitForEndOfFrame()Wait until end of frame
yield return new WaitUntil(() => cond)Wait until condition is true
yield return new WaitWhile(() => cond)Wait while condition is true

Testing with LogAssert

[Test]
public void LogWarning_IsExpected()
{
    LogAssert.Expect(LogType.Warning, "Expected warning message");
    Debug.LogWarning("Expected warning message");
}

Use LogAssert.NoUnexpectedReceived() to assert no unexpected log messages were emitted. Debug.LogError and Debug.LogException cause automatic test failure unless expected.


Common Test Patterns

Arrange-Act-Assert (AAA)

[Test]
public void Inventory_AddItem_IncreasesCount()
{
    // Arrange
    var inventory = new Inventory(maxSlots: 10);
    var item = new Item("Sword");

    // Act
    inventory.Add(item);

    // Assert
    Assert.AreEqual(1, inventory.Count);
}

Parameterized Tests

[TestCase(100, 30, 70)]
[TestCase(100, 100, 0)]
[TestCase(100, 150, 0)]
[TestCase(50, 10, 40)]
public void TakeDamage_CalculatesCorrectly(int maxHp, int damage, int expectedHp)
{
    var health = new HealthSystem(maxHp);
    health.TakeDamage(damage);

    Assert.AreEqual(expectedHp, health.CurrentHealth);
}

Testing Exceptions

[Test]
public void Inventory_AddNull_ThrowsException()
{
    var inventory = new Inventory(maxSlots: 10);

    Assert.Throws<ArgumentNullException>(() =>
    {
        inventory.Add(null);
    });
}

Scene-Based Testing

[UnityTest]
public IEnumerator Scene_Loads_Successfully()
{
    SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
    yield return new WaitUntil(() => SceneManager.GetActiveScene().name == "GameScene");
    Assert.IsTrue(SceneManager.GetActiveScene().isLoaded);
}

Testing with Fakes (Dependency Injection)

public class FakeAudioService : IAudioService
{
    public bool PlaySoundCalled { get; private set; }
    public void PlaySound(string name) => PlaySoundCalled = true;
}

[Test]
public void Attack_PlaysSound()
{
    var fakeAudio = new FakeAudioService();
    var weapon = new Weapon(fakeAudio);
    weapon.Attack();
    Assert.IsTrue(fakeAudio.PlaySoundCalled);
}

Running Tests

From the Test Runner Window

Open Window > General > Test Runner. Toggle EditMode / PlayMode checkboxes. Run tests via Run All, Run Selected, double-click, or right-click context menu. Filter using the search box or result icon buttons.

From the Command Line (CI/CD)

# Run Edit Mode tests
Unity -runTests -batchmode -projectPath /path/to/project \
  -testPlatform EditMode -testResults /path/to/results.xml

# Run Play Mode tests
Unity -runTests -batchmode -projectPath /path/to/project \
  -testPlatform PlayMode -testResults /path/to/results.xml

# Run filtered tests by name and category
Unity -runTests -batchmode -projectPath /path/to/project \
  -testFilter "HealthSystemTests" -testCategory "Unit;Integration" \
  -testResults /path/to/results.xml

Command-Line Arguments Reference

ArgumentDescription
-runTestsRequired flag to run tests
-testPlatformEditMode, PlayMode, or any BuildTarget value
-testFilterSemicolon-separated test names or regex pattern; prefix with ! to negate
-testCategorySemicolon-separated category names; prefix with ! to negate
-testResultsOutput path for NUnit XML results (default: project root)
-testNamesFull test names in FixtureName.TestName format
-assemblyNamesFilter by specific test assembly names
-batchmodeRun without UI (required for CI/CD)
-runSynchronouslyExecute all tests in one editor update (EditMode only)
-playerHeartbeatTimeoutSeconds to wait for player heartbeat (default: 600)

IDE Integration

JetBrains Rider supports running Unity tests directly from the IDE with full debugging support.

Source: Running Tests, Command Line Reference


Anti-Patterns

1. Using [UnityTest] when [Test] suffices

[UnityTest] adds coroutine overhead. Use [Test] for synchronous logic.

// BAD - unnecessary coroutine overhead
[UnityTest]
public IEnumerator Add_ReturnsSum()
{
    var result = Calculator.Add(2, 3);
    yield return null;
    Assert.AreEqual(5, result);
}

// GOOD - simple synchronous test
[Test]
public void Add_ReturnsSum()
{
    var result = Calculator.Add(2, 3);
    Assert.AreEqual(5, result);
}

2. Not cleaning up GameObjects

Leaked objects contaminate other tests. Always destroy GameObjects in [UnityTearDown] or [TearDown]. Create objects in setup, destroy in teardown.

3. Testing private methods directly

Test through public interfaces. If you must, use [assembly: InternalsVisibleTo("Tests")].

4. Hardcoding time-based waits

Use WaitUntil or WaitForFixedUpdate instead of arbitrary WaitForSeconds.

// BAD - brittle, slow
yield return new WaitForSeconds(5f);
Assert.IsTrue(enemy.IsDead);

// GOOD - deterministic
yield return new WaitUntil(() => enemy.IsDead);
Assert.IsTrue(enemy.IsDead);

5. Tests depending on execution order

Each test must be independent. Use [SetUp] / [TearDown] to establish clean state.

6. Ignoring log errors

Unexpected Debug.LogError calls cause test failures by default. Use LogAssert.Expect() for expected errors or LogAssert.ignoreFailingMessages = true only when necessary.

7. Placing tests in production assemblies

Test assemblies use UNITY_INCLUDE_TESTS define constraint and are excluded from production builds. Never mix test code with game code assemblies.


Related Skills

  • unity-scripting -- C# scripting fundamentals, MonoBehaviour lifecycle, coroutines
  • unity-editor-tools -- Custom editors, inspectors, and editor extensions

Additional Resources

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

adopt

무료

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

art-bible

무료

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

IdoCohen560/claude-unity-game-studio222026년 7월 8일 업데이트

IdoCohen560의 스킬 모두 보기

이 스킬의 문제 신고