UnityTestTool实用解释

UnityTestTool实用解释

概述

以下的场景是否似曾相识:

  • 你说:“这模块我不熟啊!让我去改,会不会引起其他问题啊?算了,都review好几遍跑好几遍了应该没问题。就让测试同学去测好了。”然后,然后这个改动没被测出并引起了外网的crash。
  • 你说:“这个模块怎么用啊?看了注释、文档后还是一头雾水。唉,还是去搜搜别人的用到这个模块的代码吧。”然后,然后你花了20分钟,从散落四处的使用者代码,终于总结出咋用这个模块。
  • 你说: “只花了一天时间,就设计好、写好一个模块了,我太棒了!”,用你模块的同学说:“你这个模块的接口不好用啊,应该那样更好吧!”,然后,然后你又花了一天重新设计了接口。
    测试用例能够解决上面这些的问题。在Unity中,UnityTestTool是编写测试用例的工具。

UnityTestTool简介

UnityTestTool是Unity提供的官方测试工具,它的核心功能包括:

  • 测试用例工具
    • 单元测试(Unit Test):测试纯代码的用例。单元测试“运行于Editor”,即不依赖于任何场景,不需场景在Play状态。
      UnitTest.png

      比如上图,每一行都是一个测试函数,以及每个函数的测试结果。
    • 集成测试(Integration Test):测试GameObject(以下简称GO)的用例。集成测试“运行于Engine”,即依赖于场景且需要场景在Play状态才能测。
      IntegrationTest.png

      比如上图左部场景里,右边有测试结果icon的GO都是集成测试用例(比如图中的“Test2-TimeOut”)。需要被测试的GO(比如图中的“Sphere”)作为测试用例GO的孩子节点。集成测试用例GO本身挂接了进行测试的脚本代码,对被测试GO进行判断以决定测试用例是否成功。一个场景里允许有多个集成测试用例,但一个时刻只有一个测试用例是Activated的(留意图中只有“Test3 - Failur”是白色,其他是灰色的)。
  • 其他工具
    • 断言组件(AssertionComponent):往GameObject添加AssertionComponent,以监控GameObject的指定状态。当AssertionComponent监控的状态不符合断言时,将抛出异常。


      AssertionComponent.png

      上图为挂接在MainCamera的一个断言组件,它断言MainCamera的drag值必须等于一个GameObject的drag,否则,将抛出异常。抛出异常后,如果在console面板设置了“Pause on error”,将自动暂停场景,实现了类似断点的功能;也正因可以抛出异常,所以断言组件可以和集成测试用例配合使用。

测试用例

测试用例能做什么

测试用例之所以能解决一开始提出的那些问题,是因为:

  1. 测试用例可以验证功能实现是否正常:前提是测试用例本身设计正确。
  2. 测试用例和功能实现是高度匹配的:模块A的功能实现和模块A的测试用例,是由同一个(批)人,在同一个时间段内编写的。另外,更改功能的接口时,需要同时更改测试用例。
  3. 测试用例本身就是实用文档:测试用例直接使用代码的形式描述了该怎么使用模块、和使用模块的注意点(因为测试用例使用抛出异常的方法来描述不正确使用模块的情况)。
  4. 测试驱动:测试用例甚至可以反过来驱动功能实现。测试用例代码比功能实现代码简单得多,且直接面对功能模块的接口。所以先写测试用例再写功能实现能帮助开发者整理思路、设计更加稳妥的接口。
  5. 使用测试用例进行测试是简单的、可自动化的:工具如果设计恰当,只需简单点击、甚至使用脚本自动化,就完成了测试。另,可以在迭代的某些关键时刻进行自动化测试,比如:每次组员提交代码的时刻、每整点、每次构建版本时。
  6. 测试用例是永久性的:不同于口口相传的瞬时性。(当然文档、代码注释也是永久性的)。

测试用例不能做什么

测试用例只是开发阶段的测试,其不能代替后面测试阶段的真正人肉集成测试。

测试用例的心理纠结

“写测试用例太麻烦、太浪费时间啦!用不用测试用例呢?”。
其实就是一个“重要事情”和“紧急事情”的权衡。都是有生活经验的人了,如何权衡,应该都懂。
总之,建议,在没养成写测试用例的习惯之前、在没体验过测试用例到底花费多少时间之前,都不要武断给下结论“不写测试用例”。

UnityTestTool使用方法

准备工具

下载UnityTestTool,并导入到你的unity项目工程中

编写单元测试

  • 创建单元测试代码文件。注意由于单元测试是不依赖于场景的,所以需要放在Editor目录下(因为项目中任意一个“Editor”目录都被Unity认为是特殊的“编辑器目录”)。


    create_unit_test.png
  • 编辑单元测试代码

    1. 引用NUnit.Framework
    2. 给需要被测试的类添加[TestFixture]标签,给需要被测试的方法添加[Test]标签。
    3. 被测试的方法注意是无参数、无返回的函数。
    4. 如果认为有异常发生,通过throw new Exception("异常描述");
using NUnit.Framework;
using System;
using MoreFun.Collections;

namespace MoreFun.Editor.Test.Collections
{
    [TestFixture]
    class BasicTreeTest
    {
        [Test]
        public void AddAndRemoveChild()
        {
            TreeData data = new TreeData();
            BasicTree tree1 = new BasicTree(data);

            TreeData data2 = new TreeData();
            BasicTree tree2 = new BasicTree(data2);

            tree1.AddChild(tree2);
            if (tree1.GetChildrenCount() != 1)
            {
                throw new Exception("测试失败");
            }

            //省略剩下代码...
        }
    }
}
  • 在Unity打开UnityTestTool的单元测试面板,会发现刚写的测试代码已被自动添加到面板中。点击单元测试面板的播放键,就进行(需要人手UI操作的)单元测试了。


    2014-0714-1607-15-9.png

编写集成测试

  • 创建测试场景


    2014-0714-1607-59-10.png
  • 在Unity打开UnityTestTool的集成测试面板。点击“+”号,在这个测试场景添加一个测试用例GO(比如上面的BattleMessageTest和ActorTest)


    2014-0714-1607-35-11.png
  • 给测试用例GO(比如ActorTest)下添加具体的GO(比如图中的“GameObject”),以及给GO添加测试脚本(比如图中的ActorTest)
2014-0714-1607-25-12.png
  • 在测试脚本调用IntegrationTest.Pass(),让测试用例通过;在测试脚本抛出异常,让测试不通过。
  • 也可以配置测试用例GO里的具体参数


    2014-0714-1707-42-13.png

脚本自动化

使用脚本自动化的关键无非下面几点:

  • Unity可以以命令行模式进行运行,而且这时能够执行任意一个静态类的静态方法(详见这里)。
  • UnityTestTool提供了脚本执行的接口UnityTest.Batch.RunUnitTests()UnityTest.Batch.RunIntegrationTests()

注:RunIntegrationTests()本只能是从命令行取得需要测试的场景列表。建议修改RunIntegrationTests()的参数,允许以函数参数的方式传入需要测试的场景列表。

  • 自动测试的结果会以xml的格式输出成文件。所以自动测试的后续流程(比如构建流程)可以依赖于生成的xml文件,判断测试是否通过,再决定是否真正执行。

具体脚本可参见最下面的附录。

更多

更多详细用法,可以阅读导入到工程的UnityTestTool/Docs/下的pdf。

附录

自动执行单元测试用例、集成测试用例的bat脚本:

echo "Start Unity Test Case"
%unity% -batchmode -projectPath %projectPath% -executeMethod CommandBuild.RunUnitTests -resultFilePath=%buildPath%/BuildTemp -quit -logFile %buildPath%/BuildTemp/RunUnitTests.log
%unity% -batchmode -projectPath %projectPath% -executeMethod CommandBuild.RunIntegrationTests -targetPlatform=%testTargetPlatform% -resultsFileDirectory=%buildPath%/BuildTemp -quit -logFile %buildPath%/BuildTemp/RunIntegrationTests.log
echo "End Unity Test Case, please see log: BuildTemp/RunUnitTests.log, BuildTemp/RunIntegrationTests.log"


echo "Start Build Unity to App"
%unity% -batchmode -projectPath %projectPath% -executeMethod CommandBuild.PreBuild %debugParam% -quit -logFile %buildPath%/BuildTemp/PreBuild.log
%unity% -batchmode -projectPath %projectPath% -executeMethod CommandBuild.Build %debugParam% -android -buildPath=%buildPath% -quit -logFile ./BuildTemp/Build.log
echo "End Build, please see log BuildTemp/PreBuild.log and BuildTemp/Build.log"

自动执行单元测试用例、集成测试用例的C#代码:

using UnityEngine;
using UnityEditor;

public class CommandBuild
{
    private static string[] ms_scenes =
    {
        "Assets/Scenes/KillerStarter.unity"
    };

    private static System.Collections.Generic.List<string> ms_lstTestScenes = new System.Collections.Generic.List<string>()
    {
        "Assets/KillerInteTest/TestBattle/TestBattle.unity",
        "Assets/KillerInteTest/TestUIBase/TestUIBase.unity"
    };


    /// <summary>
    /// 执行UnityTestTool的单元测试
    /// </summary>
    public static void RunUnitTests()
    {
        UnityTest.Batch.RunUnitTests();
    }
    /// <summary>
    /// 执行UnityTestTool的集成测试
    /// </summary>
    public static void RunIntegrationTests()
    {
        UnityTest.Batch.RunIntegrationTests(ms_lstTestScenes);
    }
    /// <summary>
    /// 检测单元测试、集成测试输出的所有xml
    /// </summary>
    /// <returns></returns>
    private static bool CheckTestResult()
    {
        UpdateBuildTempFolderPath();
        string[] lstXml = System.IO.Directory.GetFiles(ms_buildTempFolder, "*.xml");
        if(0 == lstXml.Length)
        {
            Debug.Log("在" + ms_buildTempFolder + "目录未找到任何单元测试结果xml文件!");
            return false;
        }
        else
        {
            foreach(string oneXmlFile in lstXml)
            {
                string oneXmlContent = System.IO.File.ReadAllText(oneXmlFile).ToLower();
                if (oneXmlContent.Contains("success=\"false\"") ||
                    oneXmlContent.Contains("result=\"error\"")
                    )
                {
                    Debug.Log("找到单元测试失败结果!在" + oneXmlFile + "。请查阅单元测试结果xml文件!");
                    return false;
                }
            }
        }

        Debug.Log("未检测到单元测试失败结果!检测文件列表:\n" + lstXml.JoinToString("\n"));
        return true;
    }

    public static void Build()
    {
        Debug.Log("Build");

        if (false == CheckTestResult())
        {
            throw new System.Exception("检测到测试用例结果失败!终止构建!");
        }

      // 省略以下构建代码
    }
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容

  • 文章来自:http://blog.csdn.net/mj813/article/details/52451355 ...
    好大一只鹏阅读 9,193评论 2 126
  • 1.测试与软件模型 软件开发生命周期模型指的是软件开发全过程、活动和任务的结构性框架。软件项目的开发包括:需求、设...
    Mr希灵阅读 21,963评论 7 278
  • 1.测试与软件模型 软件开发生命周期模型指的是软件开发全过程、活动和任务的结构性框架。软件项目的开发包括:需求、设...
    宇文臭臭阅读 6,727评论 5 100
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,245评论 25 707
  • 我文采不好,但是有些事憋心里久了,也会压抑,不舒服,我一直记得小学操场上,初中操场上的篮球背影 是个白白净净的小伙...
    5e6c800525b7阅读 241评论 0 0