This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# 6\. Asset Change Test
## Learning Objectives
This exercise will teach you a popular pattern in Game Tests to verify if Assets change over time.
## Exercise
As you noticed inside LostCrypt, when you pick up the Wand, your character equips armor.
Write a test that checks that after Sara picks up the wand, armor is equipped.
1. Create a `WandTests.cs` class and implement `MainScene\_CharacterReachesWandAndEquipsArmor` test.
2. Try to observe how `Sara Variant` or more specifically `puppet_sara` GameObject changes the moment you pick up the wand.
## Hints
* You can reuse code from [Reach Wand Test](./reach-wand-test.md) for the logic of the character picking up the wand. Or you can try to trigger this action programmatically.
* Remember that if some Unity internal APIs are not accessible for your test you might need to add a new reference inside the `PlayModeTests` assembly definition.
## Solution
PlayModeTests.asmdef
```
{
"name": "PlayModeTests",
"rootNamespace": "",
"references": [
"Unity.InputSystem",
"Unity.InputSystem.TestFramework",
"TestInputControl",
"UnityEngine.TestRunner",
"Unity.2D.Animation.Runtime"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
```
WandTests.cs
```
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
using UnityEngine.Experimental.U2D.Animation;
public class WandTests
{
private Transform _characterTransform;
private float _testTimeout = 25.0f;
private float _wandLocation = 21.080f;
[UnityTest]
public IEnumerator MainScene_CharacterReachesWandAndEquipsArmor()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
// Skip first frame so Sara have a chance to appear on the screen
yield return null;
var puppet = GameObject.Find("puppet_sara");
var spriteLibrary = puppet.GetComponent<SpriteLibrary>();
Assert.AreEqual(spriteLibrary.spriteLibraryAsset.name, "Sara");
var elapsedTime = 0.0f;
yield return GoRight();
while (GetCurrentCharacterPosition() <= _wandLocation)
{
yield return null;
elapsedTime += Time.deltaTime;
if (elapsedTime > _testTimeout)
{
Assert.Fail($"Character did not reach location position in {_testTimeout} seconds.");
}
}
// Wait for Wand pickup animation to be over.
yield return new WaitForSeconds(12);
Assert.AreEqual(spriteLibrary.spriteLibraryAsset.name, "Sara_var01");
}
private float GetCurrentCharacterPosition()
{
// Get Main character's Transform which is used to manipulate position.
if (_characterTransform == null)
{
_characterTransform = GameObject.Find("Sara Variant").transform;
}
return _characterTransform.position.x;
}
private IEnumerator GoRight()
{
TestInputControl.MoveLeft = false;
yield return null;
TestInputControl.MoveRight = true;
}
}
```

View File

@@ -0,0 +1,93 @@
# 5\. Collision Test
## Learning Objectives
Check for collisions and make sure that LostCrypt does not have bugs that allow your character to move outside of the map.
## Exercise
Take a look at a game object `Environment/Character Bounds - Left`. You can see that it is placed at the left side of our 2D map. It is meant to protect players from exiting the map and falling into textures. Let's see if it fulfills its purpose.
1. Add a new test `MainScene\_CharacterDoesNotFallIntoTextures` in `MovementTest.cs`.
2. Make your character move left and occasionally jump with some wait interval in between jumps.
3. In test, Assert that _Sara Variant_ game object position is within bounds of our current scene.
## Hints
* Similarly to the previous test, let's set some arbitrary amount of seconds as our timeout. Sara should stay within the bounds of the scene for the given time.
* You might want to use `WaitForSeconds(0.5f)` between jumps to emulate User behaviour better.
* Study the Scene and hardcode X, and Y position used for out of map check, or better - get it dynamically from `Character Bounds - Left` game object.
## Solution
MovementTest.cs
```
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
public class MovementTest
{
const float _testTimeout = 20.0f;
private Transform _characterTransform;
[UnityTest]
public IEnumerator MainScene_CharacterDoesNotFallIntoTextures()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
yield return waitForSceneLoad();
yield return GoLeft();
while (Time.timeSinceLevelLoad < _testTimeout)
{
yield return new WaitForSeconds(0.5f);
yield return Jump();
if (GetCurrentCharacterPosition().x < -75f && GetCurrentCharacterPosition().y < -10f)
{
Assert.Fail("Character escaped the map and fell into textures! :(");
}
}
}
private Vector3 GetCurrentCharacterPosition()
{
// Get Main character's Transform which is used to manipulate position.
if (_characterTransform == null)
{
_characterTransform = GameObject.Find("Sara Variant").transform;
}
return _characterTransform.position;
}
private IEnumerator Jump()
{
TestInputControl.Jump = true;
yield return null;
TestInputControl.Jump = false;
}
private IEnumerator GoLeft()
{
TestInputControl.MoveRight = false;
yield return null;
TestInputControl.MoveLeft = true;
}
private IEnumerator waitForSceneLoad()
{
while (SceneManager.GetActiveScene().buildIndex > 0)
{
yield return null;
}
}
}
```
Our test fails, we have a bug in one of our Sample Unity projects. How would you approach fixing this problem? There are plenty of possibilities, go ahead and try to fix it as part of this training:
* Introduce new Character Bounds Box collider that will prevent the bug from happening.
* Rework our Sara character collision logic.

View File

@@ -0,0 +1,46 @@
# 2\. Running a test in a LostCrypt
## Learning Objectives
Set up a simple Play Mode test for LostCrypt.
## Exercise
1. Go to the `Assets/Scripts` directory, and spend some time exploring the scripts necessary for LostCrypt to work properly.
2. Create a new directory `Assets/Tests`.
3. In the Test Runner window click **Create PlayModeTest Assembly Folder** and name a new folder `PlayModeTests`. You should end up with `Assets/Tests/PlayModeTests`.
4. Open the newly created folder and click **Create Test Script in current folder** in the Test Runner window.
5. Name the file `SceneSetupTests.cs`.
6. Write your first test that asserts that after loading the Main scene the current time is day.
## Hints
* In order to load scenes, please refer to [UnityEngine.SceneManagement](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.html) documentation.
* Inside `Scenes/Main.unity` [look for GameObject](https://docs.unity3d.com/ScriptReference/GameObject.Find.html) **FX - Day**.
## Solution
SceneSetupTests.cs
```
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
public class SceneSetupTests
{
[UnityTest]
public IEnumerator MainScene_LoadsCorrectlyAndItsDaytime()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
yield return null;
var fxDay = GameObject.Find("FX - Day");
Assert.IsTrue(fxDay != null, "should find the 'FX - Day' object in the scene");
}
}
```

View File

@@ -0,0 +1,134 @@
# 3\. Moving character
## Learning objectives
How to use the Unity InputSystem package to have a generic way of moving your character programmatically in tests.
## Exercise
Please make sure [InputSystem](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/QuickStartGuide.html) is installed in your Unity project. You can verify that by checking the Package Manager.
1. Create a new class called `MovementTest.cs` under `Assets/Tests/PlayModeTests`.
2. Attach the reference to `Unity.InputSystem` and `Unity.InputSystem.TestFramework` in your `PlayModeTests` assembly definition.
3. Create a new `InputControl` directory under Tests: `Assets/Tests/InputControl`.
4. Inside `InputControl` directory, create a new assembly definition: `TestInputControl.asmdef`.
5. Create a new class `TestInputControl.cs` where you implement following properties:
```
public static bool MoveLeft { get; set; }
public static bool MoveRight { get; set; }
public static bool Jump { get; set; }
```
6. Go back to your assembly definition `PlayModeTests` and attach the reference to newly created: `TestInputControl`.
7. Finally, we need to use our `TestInputControl` in actual LostCrypt code. Currently Unity's `InputSystem` does not support an easier way of programmatically doing mocks, please see this git diff to know what to change inside `CharacterController2D`:
```
diff --git a/Assets/Scripts/CharacterController2D.cs b/Assets/Scripts/CharacterController2D.cs
index f8a10cf2..e0a62878 100644
--- a/Assets/Scripts/CharacterController2D.cs
+++ b/Assets/Scripts/CharacterController2D.cs
@@ -81,15 +81,15 @@ public class CharacterController2D : MonoBehaviour
// Horizontal movement
float moveHorizontal = 0.0f;
- if (keyboard.leftArrowKey.isPressed || keyboard.aKey.isPressed)
+ if (keyboard.leftArrowKey.isPressed || keyboard.aKey.isPressed || TestInputControl.MoveLeft)
moveHorizontal = -1.0f;
- else if (keyboard.rightArrowKey.isPressed || keyboard.dKey.isPressed)
+ else if (keyboard.rightArrowKey.isPressed || keyboard.dKey.isPressed || TestInputControl.MoveRight)
moveHorizontal = 1.0f;
movementInput = new Vector2(moveHorizontal, 0);
// Jumping input
- if (!isJumping && keyboard.spaceKey.wasPressedThisFrame)
+ if (!isJumping && (keyboard.spaceKey.wasPressedThisFrame || TestInputControl.Jump))
jumpInput = true;
}
```
Now you are ready! Go back to `MovementTest.cs` and write a test that does not do any assertions (just yet), but only moves the Sara character and makes it occasionally jump.
## Hints
* You might want to use `WaitForSeconds` in your test, to deliberately make it run longer and see actual animation happening on your screen.
* In case of compilation issues, please make sure you follow the right folder structure:
```
Tests
InputControl
TestInputControl.asmdef
TestInputControl.cs
PlayModeTests
MovementTest.cs
PlayModeTest.asmdef
```
## Solution
PlayModeTests.asmdef
```
{
"name": "PlayModeTests",
"references": [
"Unity.InputSystem",
"Unity.InputSystem.TestFramework",
"TestInputControl"
],
"optionalUnityReferences": [
"TestAssemblies"
]
}
```
MovementTest.cs
```
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
public class MovementTest
{
[UnityTest]
public IEnumerator MainScene_CharacterIsAbleToJump()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
yield return waitForSceneLoad();
yield return GoRight();
yield return new WaitForSeconds(2);
yield return Jump();
yield return new WaitForSeconds(3);
yield return GoLeft();
yield return Jump();
yield return new WaitForSeconds(2);
}
private IEnumerator Jump()
{
TestInputControl.Jump = true;
yield return null;
TestInputControl.Jump = false;
}
private IEnumerator GoRight()
{
TestInputControl.MoveLeft = false;
yield return null;
TestInputControl.MoveRight = true;
}
private IEnumerator GoLeft()
{
TestInputControl.MoveRight = false;
yield return null;
TestInputControl.MoveLeft = true;
}
private IEnumerator waitForSceneLoad()
{
while (SceneManager.GetActiveScene().buildIndex > 0)
{
yield return null;
}
}
}
```

View File

@@ -0,0 +1,95 @@
# 8\. Performance Tests
## Learning Objectives
One final thing we'll explore is a package that extends Unity Test Framework with Performance Tests.
## Exercise
The Performance Testing package can be used to measure performance in our game. This is a great tool if we want to track various regressions/progressions that happen over time in our project. In this example, you'll learn how to create a test that measures game average frames.
1. LostCrypt does not include the Performance Testing package installed by default. Install it by following [these instructions](https://docs.unity3d.com/Packages/com.unity.test-framework.performance@2.8/manual/index.html).
2. Add the package as a dependency to the [project manifest](https://docs.unity3d.com/Manual/upm-manifestPrj.html).
3. When the package is installed, add a reference to `Unity.PerformanceTesting` in your **PlayModeTests** assembly definition to access the performance testing APIs.
4. Create a new C# class under **Assets/Tests/PlayModeTests** called **PerformanceTests.cs**.
You're now ready to complete your objective. In `PerformanceTests.cs` create a new function called `MainScene_MeasureAverageFrames()`. In this function move your character to the wand position and wait until the wand pickup effect is over. During all that time, measure the frames.
## Bonus
* Try to measure the average FPS in LostCrypt. You might need to use `Time.deltaTime` from UnityEngine API and `Measure.Custom` from the Performance Testing package API.
## Hints
* The first handful of frames after loading Scene are usually unstable, let's utilize the `Measure.Frames().Scope()` API to measure them into a separate scope.
* After your test finishes, performance results can be viewed under **Window > Analysis > Performance Test Report** or you can even hook into results using [Callback API](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/extension-get-test-results.html).
## Solution
PerformanceTests.cs
```
using System.Collections;
using Unity.PerformanceTesting;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
public class PerformanceTests
{
private Transform _characterTransform;
private float _wandLocation = 21.080f;
[UnityTest, Performance]
public IEnumerator MainScene_MeasureAverageFrames()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
using (Measure.Frames().Scope("Frames.MainSceneOnLoad.Unstable"))
{
for (var i = 0; i < 25; i++)
{
yield return null;
}
}
using (Measure.Frames().Scope("Frames.MainSceneGameplay"))
{
yield return GoRight();
while (GetCurrentCharacterPosition() <= _wandLocation)
{
yield return null;
}
StopMoving();
yield return new WaitForSeconds(15);
}
}
private float GetCurrentCharacterPosition()
{
// Get Main character's Transform which is used to manipulate position.
if (_characterTransform == null)
{
_characterTransform = GameObject.Find("Sara Variant").transform;
}
return _characterTransform.position.x;
}
private IEnumerator GoRight()
{
TestInputControl.MoveLeft = false;
yield return null;
TestInputControl.MoveRight = true;
}
private void StopMoving()
{
TestInputControl.MoveRight = false;
TestInputControl.MoveLeft = false;
}
}
```
Bonus Solution
`Measure.Custom("FPS", (int)(1f / Time.deltaTime));`

View File

@@ -0,0 +1,78 @@
# 4\. Reach Wand Test
## Learning Objectives
Perform Assertions on your character position and behavior.
## Exercise
1. Go back to the previous `MovementTest.cs` file.
2. Write a `MainScene\_CharacterReachesWand` test that makes your character move right, and checks if it reaches the wand location.
## Hints
* Look for _Altar_ and _Sara Variant_ game objects in your scene. You are interested in measuring the X position of your Transform objects.
* Wand location X position is equal float of **21.080**. Main Character X position is dynamic and it changes whenever it moves.
* Consider setting a timeout that makes the test fail if the Wand is not reached.
## Solution
```
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.SceneManagement;
public class MovementTest
{
private Transform _characterTransform;
private float _testTimeout = 25.0f;
private float _wandLocation = 21.080f;
[UnityTest]
public IEnumerator MainScene_CharacterReachesWand()
{
SceneManager.LoadScene("Assets/Scenes/Main.unity", LoadSceneMode.Single);
yield return waitForSceneLoad();
var elapsedTime = 0.0f;
yield return GoRight();
while (GetCurrentCharacterPosition() <= _wandLocation)
{
yield return null;
elapsedTime += Time.deltaTime;
if (elapsedTime > _testTimeout)
{
Assert.Fail($"Character did not reach location position in {_testTimeout} seconds.");
}
}
}
private float GetCurrentCharacterPosition()
{
// Get Main character's Transform which is used to manipulate position.
if (_characterTransform == null)
{
_characterTransform = GameObject.Find("Sara Variant").transform;
}
return _characterTransform.position.x;
}
private IEnumerator GoRight()
{
TestInputControl.MoveLeft = false;
yield return null;
TestInputControl.MoveRight = true;
}
private IEnumerator waitForSceneLoad()
{
while (SceneManager.GetActiveScene().buildIndex > 0)
{
yield return null;
}
}
}
```

View File

@@ -0,0 +1,92 @@
# 7\. Scene Validation Test
## Learning Objectives
Test scene for presence of Sara and Wand game object. Utilize Test Framework feature to make this test use all scenes as fixtures.
## Exercise
1. Create **ValidationTest.cs** file with a single namespace and two classes _SceneValidationTests_ and _GameplayScenesProvider_.
2. In the Tests class create **SaraAndWandArePresent** test to check that "Sara Variant" and "Wand" game objects are not null.
3. In the Fixture class `GameplayScenesProvider` implement `IEnumerable<string>` and in generator method yield all scenes from [EditorBuildSettings.scenes](https://docs.unity3d.com/ScriptReference/EditorBuildSettings-scenes.html).
4. Use `TestFixture` and [TestFixtureSource](https://docs.nunit.org/articles/nunit/writing-tests/attributes/testfixturesource.html) annotations on _SceneValidationTests_ class.
5. Create a new Empty Scene and attach it to `EditorBuildSettings` to verify if tests are created dynamically.
## Hints
* `TestFixture` and `TestFixtureSource` NUnit annotations require Test Class to be present inside Namespace.
* To attach a scene to `EditorBuildSettings`, you need to create a new Scene, and then add it to **File > Build Settings**.
## Solution
ValidationTests.cs
```
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
namespace ValidationTests
{
[TestFixture]
[TestFixtureSource(typeof(GameplayScenesProvider))]
public class SceneValidationTests
{
private readonly string _scenePath;
public SceneValidationTests(string scenePath)
{
_scenePath = scenePath;
}
[OneTimeSetUp]
public void LoadScene()
{
SceneManager.LoadScene(_scenePath);
}
[UnityTest]
public IEnumerator SaraAndWandArePresent()
{
yield return waitForSceneLoad();
var wand = GameObject.Find("Wand");
var sara = GameObject.Find("Sara Variant");
Assert.NotNull(wand, "Wand object exists");
Assert.NotNull(sara, "Sara object exists");
}
IEnumerator waitForSceneLoad()
{
while (!SceneManager.GetActiveScene().isLoaded)
{
yield return null;
}
}
}
public class GameplayScenesProvider : IEnumerable
{
public IEnumerator GetEnumerator()
{
foreach (var scene in EditorBuildSettings.scenes)
{
if (!scene.enabled || scene.path == null)
{
continue;
}
yield return scene.path;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
```

View File

@@ -0,0 +1,34 @@
# 1\. Setting up LostCrypt
## Learning Objectives
In this exercise you'll set up a simple Unity 2D project and import a sample project (LostCrypt).
## Prerequisites
1. **Unity 2020.3 LTS** - recommended version of Unity for this training session
2. **C# IDE** (for example [Rider](https://www.jetbrains.com/rider/download/) or [Visual Studio](https://visualstudio.microsoft.com)) - not necessary but highly recommended. This way you can use features like a debugger and reliable syntax autocompletion.
## Exercise
1. Open **Unity Hub** and click _New Project_. Select a blank 2D (or Core2D) project.
2. Enter a **Project Name** and click **Create**.
3. Visit the [LostCrypt](https://assetstore.unity.com/packages/essentials/tutorial-projects/lost-crypt-2d-sample-project-158673) asset page. Click _Add to my Assets_ -> _Open in Unity Editor_.
4. **Package Manager** window opens automatically. Find **Lost Crypt - 2D Sample Project**. Press _Download_ and then _Import_.
5. **Import Unity Package** window opens. Click _Import_ to add all additional packages and assets to your newly created project.
6. Restart Unity if needed.
Now confirm that LostCrypt works correctly.
1. From the **Project** tab open `Scenes/Main`.
2. Enter Play Mode by clicking Play button.
3. You should be able to move your character around.
## Further reading and Resources
You can read more about LostCrypt in [our blog post](https://www.google.com/url?q=https://blog.unity.com/technology/download-our-new-2d-sample-project-lost-crypt&source=gmail-html&ust=1653726008832000&usg=AOvVaw2RORHgX1nn7hE7KZW3e_lA).
## Hints (what can go wrong)
* There might be some dependency problems - please make sure LostCrypt is downloaded for the suggested Unity LTS version.
* Make sure you have the newest project packages in your Package Manager.

View File

@@ -0,0 +1,24 @@
# Testing Lost Crypt
Welcome to the this training material for the Unity Test Framework (UTF).
The training is structured with a selection of exercises, starting with more basic topics and then expanding on that knowledge.
Each section has a Learning Objectives section, which can help you pick what exercises will teach you new things. The exercises are grouped thematically and their difficulty varies.
**This course focus on testing an actual game. Our candidate is the [LostCrypt](https://assetstore.unity.com/packages/essentials/tutorial-projects/lost-crypt-2d-sample-project-158673) example project.**
## Course outline
0. [Setting up](./setting-up.md)
1. [Running a test in LostCrypt](./first-test.md)
2. [Moving character](./moving-character.md)
3. [Reach wand test](./reach-wand-test.md)
4. [Collision test](./collision-test.md)
5. [Asset change test](./asset-change-test.md)
6. [Scene validation test](./scene-validation-test.md)
7. [Performance tests](./performance-tests.md)
## Prerequisites
Although not technically a requirement, we strongly recommend you complete the [General Introduction](../welcome.md) course before attempting this one.