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("檢測到測試用例結果失敗!終止構建!");
        }

      // 省略以下構建代碼
    }
}


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 文章來自:http://blog.csdn.net/mj813/article/details/52451355 ...
    好大一只鵬閱讀 9,215評論 2 126
  • 1.測試與軟件模型 軟件開發生命周期模型指的是軟件開發全過程、活動和任務的結構性框架。軟件項目的開發包括:需求、設...
    Mr希靈閱讀 21,987評論 7 278
  • 1.測試與軟件模型 軟件開發生命周期模型指的是軟件開發全過程、活動和任務的結構性框架。軟件項目的開發包括:需求、設...
    宇文臭臭閱讀 6,751評論 5 100
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,268評論 25 708
  • 我文采不好,但是有些事憋心里久了,也會壓抑,不舒服,我一直記得小學操場上,初中操場上的籃球背影 是個白白凈凈的小伙...
    5e6c800525b7閱讀 247評論 0 0