NUnit的使用
NUnit是C#中最著名的测试框架。编写好测试用例之后,在Window/General/TestRunner中可以看见测试用例。测试用例应该放在Editor文件夹下(因为测试case与Unity Editor相关,所以应该放在Editor目录下面)。 如果不把测试用例放在Editor文件夹下,也可以随意一个目录,然后添加以下内容:xxx.asmdef
{
"name": "Tests",
"optionalUnityReferences": [
"TestAssemblies"
]
}
写一段代码:
using UnityEngine;
using System.Collections;
using NUnit.Framework;
[TestFixture]
public class HealthComponentTests
{
//测试伤害之后,血的值是否比0大
[Test]
public void TakeDamage_BeAttacked_BiggerZero()
{
//Arrange
UnMonoHealthClass health = new UnMonoHealthClass();
health.healthAmount = 50f;
//Act
health.TakeDamage(60f);
//Assert
Assert.GreaterOrEqual(health.healthAmount, 0);
}
}
常见的NUnit属性如下,对应的有UnitySetUp、UnityTearDown等为Unity进行特殊定制的属性。
[SetUp]
[TearDown]
[TestFixture]
[Test]
[TestCase]
[Category]
[Ignore]
在游戏中,UI、交互方面的测试比较难以实现,只能在逻辑部分进行一些单元测试。
单元测试有两类:Edit模式和play模式 在Edit模式下,是本地测试。 在Play模式下,是运行在Android系统。 如果创建的是Edit模式的测试,则应该放在Editor目录下面。 如果创建的是Play模式的测试,则可以放在任意一个目录下面,并且添加xxxx.asdef文件。这里面的关键点是optionalUnityReferences设置为TestAssemblies。只有这样才能够引用到一些测试库。 如果要测试的是另一个assembly,则应该添加references才能引用到另外一个单元。
{
"name": "Tests",
"references": [
"XXXX.Platform"
],
"optionalUnityReferences": [
"TestAssemblies"
]
}
asmdef.json
asmdef.json是unity特有的程序集描述文件。许多工程下面都有xxxx.asmdef文件。
{
"schema": "http://json-schema.org/draft-06/schema#",
"title": "Unity Assembly Definition",
"description": "Defines an assembly in the Unity compilation pipeline",
"type": "object",
"properties": {
"name": {
"description": "The name of the assembly being defined",
"type": "string",
"minLength": 1
},
"rootNamespace": {
"description": "The root namespace of the assembly. Requires Unity 2020.2",
"type": "string",
"minLength": 1
},
"references": {
"description": "A list of assembly names or assembly definition asset GUIDs to reference",
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"uniqueItems": true
},
"includePlatforms": {
"description": "Platforms to target",
"$ref": "#/definitions/platformValues"
},
"excludePlatforms": {
"description": "Platforms that are explicitly not targeted",
"$ref": "#/definitions/platformValues"
},
"allowUnsafeCode": {
"description": "Allow unsafe code",
"type": "boolean",
"default": false
},
"autoReferenced": {
"description": "When true, this assembly definition is automatically referenced by predefined assemblies (Assembly-CSharp, Assembly-CSharp-firstpass, etc.)",
"type": "boolean",
"default": true
},
"noEngineReferences": {
"description": "When true, no references to UnityEngine or UnityEditor will be added to this assembly project",
"type": "boolean",
"default": false
},
"overrideReferences": {
"description": "When true, any references to precompiled assembly assets are manually chosen. When false, all precompiled assembly assets are automatically referenced",
"type": "boolean",
"default": "false"
},
"precompiledReferences": {
"description": "A list of precompiled assembly assets that will be referenced. Only valid when overrideReferences is true",
"type": "array",
"uniqueItems": true
},
"defineConstraints": {
"description": "A list of the C# compiler define symbols that must evaluate to true in order for the assembly to be compiled or referenced. Absence of a symbol can be checked with a bang symbol (!DEFINE)",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"optionalUnityReferences": {
"description": "Additional optional Unity features to reference. Not supported since 2019.3",
"type": "array",
"items": {
"enum": [
"TestAssemblies"
]
},
"uniqueItems": true
},
"versionDefines": {
"description": "A set of expressions that will define a symbol in the C# project if a package or module version matches the given expression",
"type": "array",
"uniqueItems": true,
"items": {
"type": "object",
"properties": {
"name": {
"description": "The package or module that will provide the version to be checked in the expression",
"type": "string",
"minLength": 1
},
"expression": {
"description": "The semantic version range of the chosen package or module",
"type": "string"
},
"define": {
"description": "The name of the define that is added to the project file when expression evaluates to true",
"type": "string"
}
},
"required": [ "name", "expression", "define" ],
"minLength": 1
}
}
},
"definitions": {
"platformValues": {
"type": "array",
"uniqueItems": true,
"items": {
"enum": [
"Android",
"CloudRendering",
"Editor",
"GameCoreXboxOne",
"iOS",
"LinuxStandalone64",
"Lumin",
"macOSStandalone",
"PS4",
"PS5",
"Stadia",
"Switch",
"tvOS",
"WSA",
"WebGL",
"WindowsStandalone32",
"WindowsStandalone64",
"XboxOne",
"GameCoreScarlett",
"LinuxStandalone32",
"LinuxStandaloneUniversal",
"Nintendo3DS",
"PSMobile",
"PSVita",
"Tizen",
"WiiU"
]
}
}
},
"required": ["name"],
"anyOf": [
{
"properties": {
"includePlatforms": {
"minItems": 1
},
"excludePlatforms": {
"maxItems": 0
}
}
},
{
"properties": {
"includePlatforms": {
"maxItems": 0
},
"excludePlatforms": {
"minItems": 1
}
}
},
{
"properties": {
"includePlatforms": {
"maxItems": 0
},
"excludePlatforms": {
"maxItems": 0
}
}
}
]
}
测试的asm
{
"name": "Tests",
"references": [
"Pico.Platform"
],
"optionalUnityReferences": [
"TestAssemblies"
]
}
Mock和Stub有何区别?
Mock与Stub的区别:
Mock:关注行为验证。细粒度的测试,即代码的逻辑,多数情况下用于单元测试。
Stub:关注状态验证。粗粒度的测试,在某个依赖系统不存在或者还没实现或者难以测试的情况下使用,例如访问文件系统,数据库连接,远程协议等。
使用注解
断言抛出某种类型的异常
[ExpectedException(typeof(NegativeHealthException))]
设定运行时长
使用命令运行单元测试
Unity程序如果实现自动化测试,例如CI,在代码提交之后自动执行,那么就需要使用命令行运行测试,而不是使用TestRunner窗口。
Unity运行时支持以下参数:
runEditorTests
editorTestsResultFile
editorTestsFilter
editorTestsCategories
editorTestsVerboseLog
写法实例
OneTimeSetUp:全局执行一次 SetUp、TearDown:每个用例都要执行一次
using UnityEngine; //基于 Unity 引擎,必须引用
using NUnit.Framework; //引用NUnit测试框架
[TestFixture, Description("测试套")] //一个类对应一个测试套,通常一个测试特性对应一个测试套。
public class UnitTestDemoTest
{
[OneTimeSetUp] //在执行该测试套时首先会执行该函数,在整个测试套中只执行一次。
public void OneTimeSetUp()
{
Debug.Log("OneTimeSetUp");
}
[OneTimeTearDown] //在执行该测试套时最后会执行该函数,在整个测试套中只执行一次。
public void OneTimeTearDown()
{
Debug.Log("OneTimeTearDown");
}
[SetUp]
public void SetUp() //在执行每个用例之前都会执行一次该函数
{
Debug.Log("SetUp");
}
[TearDown] //在执行完每个用例之后都会执行一次该函数
public void TearDown()
{
Debug.Log("TearDown");
}
[TestCase, Description("测试用例1")] //这个函数内部写测试用例
public void TestCase1()
{
Debug.Log("TestCase1");
}
[TestCase, Description("测试用例2")] //这个函数内部写测试用例
public void TestCase2()
{
Debug.Log("TestCase2");
}
}
TestCase和Test的区别
TestCase可以接收参数。并且可以指定多组输入数据。
// 多测试数据的GetTextLength测试
[TestCase("", 0)]
[TestCase("Hello World", 11)]
public void GetTextLength_MultiTestData(string data, int exResult)
{
int result = GameUtils.GetTextLength(data);
Assert.AreEqual(exResult, result);
}