UnityUnitTest单元测试(1)
HowToRunUnityUnitTest
说明
本⽂使⽤Unity Editor⾃带的单元测试⼯具Unity Test Runner进⾏单元测试。
本⽂主要是对中的教程的中⽂翻译与补充。
参考
教程:
Unity官⽅⽤户⼿册:
环境
C# 7.2
Unity 2018.3
项⽬
下的为未添加测试的原始项⽬,供⼤家按照教程为项⽬添加测试。
下的为按照教程添加测试后的项⽬。
也可以参考本⼈在学习教程时创建的项⽬,⾥⾯的测试代码附有较为详细的注释。
教程:Unity单元测试简介
1. 什么是单元测试?
单元测试是指对软件中的最⼩可测试单元进⾏检查和验证。对于单元测试中单元的含义,⼀般来说,要根据实际情况去判定其具体含义。总的来说,单元就是⼈为规定的最⼩的被测功能模块,单元测试应该⼀次只测试⼀个“事物”。
测试⼈员应该设计⼀个单元测试来验证⼀个⼩的逻辑代码⽚段是否完全按照预期执⾏。
请参考以下⽰例:
public string name = ""
public void UpdateNameWithCharacter(char: character)
{
// 1
if (!Char.IsLetter(character))
{
return;
}
// 2
if (name.Length > 10)
{
return;
}
// 3
name += character;
}
1. 如果character不是字母,则会提前退出函数,并且不会将字符添加到字符串中。
2. 如果name的长度⼤于10,则会阻⽌⽤户添加另⼀个字符。
3. 否则将character添加到name的结尾。
这个⽅法是⼀个可以进⾏单元测试的很好的例⼦。
⽰例单元测试
对于UpdateNameWithCharacter⽅法,需要仔细考虑测试要进⾏的⼯作,并为它们提供名称。名称应清楚说明测试的内容:UpdateNameDoesntAllowCharacterAddingToNameIfNameIsTenOrMoreCharactersInLength
UpdateNameAllowsLettersToBeAddedToName
UpdateNameDoesntAllowNonLettersToBeAddedToName
测试套件
⼀个测试套件包含⼀组相关的单元测试(如战⽃模块单元测试)。如果测试套件中的任何单个测试失败,则整个测试套件将失败。
2. 运⾏游戏
在Unity中打开项⽬,然后打开Assets / RW / Scenes中的Game场景。
单击Play以启动Crashteroids,然后单击START GAME按钮开始游戏。使⽤←和→箭头键左右移动宇宙飞船。
按空格键激发激光。如果激光击中⼩⾏星,则分数将增加1。如果⼀颗⼩⾏星撞击船只,那么船就会爆炸并且游戏结束(可以选择重新开始)。
尝试玩⼀会⼉,然后确保船被⼩⾏星击中以触发Game Over事件。
3. Unity Test Runner⼊门
Unity Test Runner是Unity Editor⼯具,可以在PlayMode(运⾏模式)和EditMode(编辑模式)下测试代码,也可以在⽬标平台上测试,例如Standalon,Android或iOS。
访问Unity Test Runner,选择菜单栏中的Windows▸General▸Test Runner。
调出Unity Test Runner窗⼝。
Unity Test Runner使⽤集成了Unity的NUnit库,这是⼀个基于于.Net语⾔的开源单元测试库。有关NUnit的更多信息,请参阅和的。是Unity Test Runner标准NUnit库的主要补充。这是⼀种单元测试,允许您从测试中跳过⼀个框架(允许后台任务完成)。使
⽤UnityTestAttribute:
在Play Mode下:UnityTestAttribute作为执⾏。
在Editor Mode下:UnityTestAttribute在回调循环中执⾏。
PlayMode和EditMode有什么区别?
PlayMode
测试脚本需要:
已经初始化:Awake、Start等
测试运⾏时:Update、FixedUpdate等
物理
如:
在访问这个对象之前是否已经初始化所有组件?
这个循环是否会(在给定时间内)终⽌?
将bounciness设置为0.99,球会在X秒后停⽌跳跃吗?
EditMode
不需要使⽤PlayMode(Awake、Start、Update、FixedUpdate、物理等)
明确需要在进⼊PlayMode之前进⾏的测试
如:
只有⼀个Camera在场景中吗?
对于mixed reality:在进⼊PlayMode之前,Camera是否处于0, 0, 0位置?
Camera是否有⼀个PhysicsRaycaster组件使得接⼝IPointerXxx⼯作?
对于⼀些使⽤EditMode的test来说,需要在进⼊PlayMode之前进⾏的测试,⽽其余EditorMode下的test只是在这个模式下更快,因为在测试⼀些特定的东西之前不必初始化场景中的所有内容。
在本教程中只涉及PlayMode测试。
设置测试⽂件夹
为了运⾏测试,⾸先需要创建⼀个测试⽂件夹来保存测试。
选择RW⽂件夹,在⽂件夹下⾯创建⼀个⽂件夹,并命名为Tests。
点击Test Runner窗⼝下的PlayMode Tab,点击Create PlayMode Test Assembly Folder,将⽂件夹命名为PlayModeTests。
进⼊PlayModeTests⽂件夹,发现已经创建了⼀个名为PlayModeTests.asmdef的Assembly Definition⽂件。这是⼀个程序集定义⽂件,⽤于告诉Unity测试⽂件所在的位置。
如果出现Unity⽆法到测试⽂件的情况,则应该仔细检查⽂件夹是否包含程序集定义⽂件。
创建测试套件
测试套件是逻辑划分测试的地⽅,将测试代码划分为不同的逻辑套件(例如,物理测试套件和战⽃的单独测试套件)。对于本教程,只需要⼀个测试套件。
点击Test Runner窗⼝下Create Test Script in current Folder,在当前⽂件夹下⾯创建⼀个测试脚本,命名为TestSuite。
设置Assembly Definition
为确保测试代码可以访问游戏脚本,需要创建游戏脚本程序集定义⽂件并在测试程序集定义⽂件中设置引⽤。
选择Scripts⽂件夹,右键选择Create ▸ Assembly Definition,将⽂件命名为GameAssembly。
选择Tests / PlayMode / PlayModeTests测试程序集定义⽂件,在Inspector窗⼝中,单击Assembly Definition References下⾯的加号。
将Scripts⽂件夹下的GameAssembly拖到None(Assembly Definition Asset)上。
单击Inspector窗⼝中最下⽅的Apply按钮以保存这些更改。
如果没有按照这些步骤操作,则⽆法在单元测试脚本中引⽤游戏脚本。
4. 编写第⼀个单元测试
在代码编辑器中打开TestSuite.cs,使⽤以下代码替换所有代码:
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
public class TestSuite
{
}
在这个⼩⼩的Crashteroids游戏中,也有很多可以编写的测试,以确保⼀切按预期⼯作。本教程只关注命中检测和核⼼游戏机制的⼏个关键领域。然⽽,当在⽣产级产品上编写单元测试时,确实需要花时间考虑测试代码所有区域所需的所有边缘情况。
测试⼩⾏星向下移动
private Game game;
// This is an attribute. Attributes define special compiler behaviors.
// It tells the Unity compiler that this is a unit test.
// This will make it appear in the Test Runner when you run your tests.
[UnityTest]
// Test the asteroids actually move down.
public IEnumerator AsteroidsMoveDown()
{
// Use "Resources/Prefabs/Game" to create an instance of the "Game(GameObject)".
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
// Get "Game(Script)" as a component of "Game(GameObject)".
game = gameGameObject.GetComponent<Game>();
// Get "Spawner(Script)" as a component of "Spawner(Gamebject)" in "Game(Script)" of "Game(GameObject)".
// Use SpawnAsteroid() in Spawn class to create an astroid.
// The astroid has Move method and be called in Update method.
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
// Keep track of the initial position.
float initialYPos = ansform.position.y;
// Add a time-step of 0.1 seconds.
yield return new WaitForSeconds(0.1f);
// This is the assertion step where you are asserting
// that the position of the asteroid is less than the initial position (which means it moved down).
Assert.ansform.position.y, initialYPos);
// It’s always critical that you clean up (delete or reset) your code after a unit test so that when the next test runs there are no artifacts that can affect that test. // Deleting the game object is all you have left to do, since for each test you’re creating a whole new game instance for the next test.
Object.Destroy(game.gameObject);
}
通过测试
在Test Runner窗⼝中,展开UnityUnitTestingTutorial的所有箭头,可以看到带有灰⾊圆圈的测试AsteroidsMoveDown。
灰⾊圆圈表⽰测试尚未运⾏。当测试运⾏并通过时,它将显⽰绿⾊钩。如果测试失败,它将显⽰红⾊×。
单击RunAll按钮运⾏测试。编辑器将创建⼀个临时场景并运⾏测试。完成后应该显⽰测试通过。
⾄此,第⼀个单元测试通过,该测试断⾔产⽣的⼩⾏星向下移动。
5. 将测试添加到测试套件中
测试飞船撞到⼩⾏星后游戏结束
[UnityTest]
// Test game over when the ship crashes into an asteroid.
public IEnumerator GameOverOccurOnAsteroidCollision()
{
// Use "Resources/Prefabs/Game" to create an instance of the "Game(GameObject)".
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
// Get "Game(Script)" as a component of "Game(GameObject)".
game = gameGameObject.GetComponent<Game>();
// Get "Spawner(Script)" as a component of "Spawner(Gamebject)" in "Game(Script)" of "Game(GameObject)".
// Use SpawnAsteroid() in Spawn class to create an astroid.
// The astroid has Move method and be called in Update method.
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
// Set the asteroid to have the same position as the ship to make an asteroid and ship crash
// Add a time-step to ensure the Physics engine Collision event
yield return new WaitForSeconds(0.1f);
// Check that the isGameOver flag in the Game script has been set to true
Assert.True(game.isGameOver);
// Delete the "game(GameObject)"
Object.Destroy(game.gameObject);
}
通过测试
查看Test Runner窗⼝,可以看到带有灰⾊圆圈的测试AsteroidsMoveDown。
这⼀次,只需要运⾏此测试⽽不需要运⾏整个测试套件。单击GameOverOccursOnAsteroidCollision,然后单击Run Selected按钮。绿⾊钩表明测试通过。
Set Up和Tear Down属性
如上,在创建Game的GameObject以及引⽤Game脚本时存在⼀些重复代码:
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
以及在Game的GameObject被销毁也存在重复代码:
Object.Destroy(game.gameObject);
这在测试中很常见。在运⾏单元测试时,实际上有两个阶段:Set Up和Tear Down。
Setup()⽅法中的任何代码都将在该套件中的单元测试之前运⾏,并且Teardown()⽅法中的任何代码将在该套件中的单元测试之后运⾏。打开代码编辑器,将以下代码添加到TestSuite⽂件的顶部,即第⼀个[UnityTest]属性的上⽅:
[SetUp]
public void Setup()
{
// Use "Resources/Prefabs/Game" to create an instance of the "Game(GameObject)".
GameObject gameGameObject =
MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
// Get "Game(Script)" as a component of "Game(GameObject)".
game = gameGameObject.GetComponent<Game>();
}
该SetUp属性指定在运⾏每个测试之前调⽤此⽅法。
再在该⽅法下⾯添加以下代码:
[TearDown]
public void Teardown()
{
Object.Destroy(game.gameObject);unity 教程
}
该TearDown属性指定在运⾏每个测试之后调⽤此⽅法。
最后删除或注释掉两个单元测试中出现的重复代码,最终如下所⽰:
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;
public class TestSuites
{
private Game game;
[SetUp]
public void Setup()
{
// Use "Resources/Prefabs/Game" to create an instance of the "Game(GameObject)".
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
// Get "Game(Script)" as a component of "Game(GameObject)".
game = gameGameObject.GetComponent<Game>();
}
[TearDown]
public void TearDown()
{
Object.Destroy(game.gameObject);
}
// This is an attribute. Attributes define special compiler behaviors.
// It tells the Unity compiler that this is a unit test.
// This will make it appear in the Test Runner when you run your tests.
[UnityTest]
// Test the asteroids actually move down.
public IEnumerator AsteroidsMoveDown()
{
/* // Use "Resources/Prefabs/Game" to create an instance of the "Game(GameObject)".
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
// Get "Game(Script)" as a component of "Game(GameObject)".
game = gameGameObject.GetComponent<Game>(); */
// Get "Spawner(Script)" as a component of "Spawner(Gamebject)" in "Game(Script)" of "Game(GameObject)".
// Use SpawnAsteroid() in Spawn class to create an astroid.
// The astroid has Move method and be called in Update method.
GameObject asteroid = game.GetSpawner().SpawnAsteroid();
// Keep track of the initial position.
float initialYPos = ansform.position.y;
// Add a time-step of 0.1 seconds.
yield return new WaitForSeconds(0.1f);
// This is the assertion step where you are asserting
// that the position of the asteroid is less than the initial position (which means it moved down).
Assert.ansform.position.y, initialYPos);
/* // It’s always critical that you clean up (delete or reset) your code after a unit test so that when the next test runs there are no artifacts that can affect that test. // Deleting the game object is all you have left to do, since for each test you’re creating a whole new game instance for the next test.
Object.Destroy(game.gameObject); */
}
[UnityTest]
// Test game over when the ship crashes into an asteroid.
public IEnumerator GameOverOccurOnAsteroidCollision()
{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论