1. 程式人生 > >【Unity遊戲開發】淺談Unity遊戲開發中的單元測試

【Unity遊戲開發】淺談Unity遊戲開發中的單元測試

可靠 屬於 sin 自定義類型 允許 ogr 兩個 階段 ast

一、單元測試的定義與作用

  單元測試定義:單元測試在傳統軟件開發中是非常重要的工具,它是指對軟件中的最小可測試單元進行檢查和驗證,一般情況下就是對代碼中的一個函數去進行驗證,檢查它的正確性。一個單元測試是一段自動化的代碼,這段代碼調用被測試的工作單元,之後對這個單元的單個最終結果的某些假設進行檢驗。單元測試使用單元測試框架編寫,並要求單元測試可靠、可讀並且可維護。只要產品代碼不發生變化,單元測試的結果是穩定的。(百度的)

  單元測試可以讓你在軟件開發的早期階段發現 Bug,而不必到集成測試的時候才發現,開發完成一個模塊(類、函數)就對應地做一個單元測試,盡早發現並處理掉bug,提高代碼的質量。(反正單元測試就是杠杠好!)

二、在Unity中使用NUnit進行單元測試

  話說,馬三在工作的過程中,極少地發現周圍的同事會對自己編寫功能進行單元測試。一般都是開發完功能以後,隨便寫兩段測試的代碼(有的甚至都不測一下),一看沒有問題就丟到SVN或者Git倉庫裏面了。結果當遊戲出包以後,測試團隊總會反饋回很多完全可以提前規避掉的低級bug。當然可能他們本身沒有單元測試的習慣或者由於活多、工期太緊等種種原因,才不做單元測試的。(活都要幹不完了,還做毛測試,Delay不扣錢啊!?)

  好了,閑話扯完該說說咱們這個單元測試了。單元測試目前有很多成熟的框架可以供我們使用,我比較推薦的就是Unity Editor自帶的Editor Tests Runner,功能不多,但是已經夠用了,使用也很方便。Editor Tests Runner是開源單元測試工具NUnit在Unity引擎中的實現,目前Unity中使用的NUnit版本是2.6.4。

  Editor Tests Runner可以通過Window -> Editor Tests Runner菜單打開,它的樣子如下圖所示:

  技術分享

  在這個窗口中顯示了當前添加的單元測試用例,以及他們通過的情況。首先,你需要點擊窗口左上角的Run All按鈕來執行所有的單元測試。綠色的對號表示這個用例通過了單元測試,紅色的禁止符號表示未通過單元測試。

  下面我們來看一下如何編寫單元測試的代碼。單元測試代碼和遊戲運行時代碼是分開保存的,它只在Editor環境下可用,因此你需要把它放到Editor目錄下。

  首先為了下面的測試,我們先定義一個自定義類型的錯誤異常,提前備用。只需要讓它直接繼承 ApplicationException 就可以了,代碼如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

/// <summary>
/// 自定義的異常類型
/// </summary>
class NegativeHealthException : ApplicationException
{

}

  下面編寫我們的需要進行被測試的模塊或者代碼。假設遊戲代碼中存在一個Player類來代表主角色,裏面有幾個函數用來在玩家受到傷害時減少血量,或者通過藥水回復血量。其中Damage函數寫了三個版本,一個是正確的,兩個是返回錯誤結果的。在正確的函數中,當 Health 的值小於 100 的時候,會拋出一個剛才我們自定義的異常。代碼如下所示:

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using UnityEngine;
 4 
 5 public class Player{
 6 
 7     public float Health { get; set; }
 8 
 9 #region 正確的方法
10 
11     public void Damage(float value)
12     {
13         Health -= value;
14         if (Health < 0)
15         {
16             throw new NegativeHealthException();
17         }
18     }
19 
20     public void Recover(float value)
21     {
22         Health += value;
23     }
24 #endregion
25 
26 #region 錯誤的方法
27 
28     public void DamageWrong(float value)
29     {
30         Health -= value + 1;
31     }
32 
33     public void DamageNoException(float value)
34     {
35         Health -= value;
36     }
37 #endregion
38 }

  接著我們來編寫單元測試代碼。這裏我們創建了一個叫做PlayerTest的類,裏面寫了兩個函數分別代表兩個測試用例。為了讓Unity識別這兩個函數是測試用例,我們需要在函數前加上 [Test] 的屬性,這樣所有帶有 [Test] 屬性的函數都會成為一個測試用例,代碼如下。

 1 using System.Collections;
 2 using System.Collections.Generic;
 3 using NUnit.Framework;
 4 using UnityEngine;
 5 
 6 public class PlayerTest{
 7 
 8     [Test]
 9     public void TestHealth()
10     {
11         Player player = new Player();
12         player.Health = 1000.0f;
13         
14         //通過Assert斷言來判斷這個函數的返回結果是否符合預期
15         player.Damage(200);
16         Assert.AreEqual(800,player.Health);
17 
18         player.Recover(150);
19         Assert.AreEqual(950,player.Health);
20     }
21 
22     [Test]
23     [ExpectedException(typeof(NegativeHealthException))]
24     public void NegativeHealth()
25     {
26         Player player = new Player();
27         player.Health = 1000;
28 
29         player.Damage(500);
30         player.Damage(600);
31     }
32 
33 }

  相信大家在寫到 [ExpectedException(typeof(NegativeHealthException))] 的時候,VS肯定會報紅,提示找不到 ExpectedException 這個標簽,這是因為,ExpectedException這個標簽是屬於VS的單元測試的內容,在 NUnit.Framework 這個命名空間中,因此我們還需要使用 using NUnit.Framework; 來引入VS的單元測試模塊。但是如果你會發現這個模塊無法引入,VS沒有自動補全這個命名空間,就算手動寫上了還是提示找不到。這是為什麽呢?

  眾所周知,Unity的.NET是基於 Mono 的,因為一些原因,導致Mono並不是包含了所有微軟原生的.NET庫中的內容。也就是說有些你在Winform、WPF等工程中用到的類庫並不能完美地在Mono中使用,這也就是為什麽會發生上述找不到單元測試的模塊的問題。其實這個問題也很好解決,我們只要把 VS 中的單元測試模塊的DLL找到(名為 Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll ),手動拷貝到Unity工程中,再在IDE裏面引入它就可以使用了。具體的操作步驟如下:

  1.找到VS中的單元測試模塊DLL的所在位置,經過在 Stackoverflow 上面查詢,我們得知Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 一般在 C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 路徑下。如下圖所示:

  技術分享

  2.把這個DLL手動拷貝到Unity的工程中,並在我們的解決方案中引用它。在我們的Unity項目中新建一個名為 “Plugins” 的文件夾,然後把上面的Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 拷貝到該文件夾下,再重新打開我們的VS解決方案,就可以發現,這個模塊已經自動被引用進來了,之後就可以放心地使用單元測試相關的代碼了。

  技術分享

  技術分享

  一般在傳統的C#項目中,我們引用某個DLL的時候,都是通過在VS解決方案的引用項目上右鍵 -> 添加新引用來導入某個DLL,但是在Unity的項目中,我們在引用選項上右鍵卻發現沒有這個選項。其實,只要像上述的那樣直接把dll 拷貝到 "Plugins"目錄下,VS就會自動把DLL引用到我們的項目中了,非常方便。

  在上面的測試函數中,假如我們想測試Damage這個函數是否正常工作,需要使用 Assert.AreEqual 來判斷這個函數的返回結果是否與預期的結果一致。如果Assert.AreEqual判斷結果是正確的,就會在Tests Runner窗口中用一個綠色的對號表示這個測試通過了,反之就會用紅色的禁止符號表示失敗。第二個名為 NegativeHealth 測試用例函數,是用來判斷判斷這個函數有沒有正常地拋出異常,如果沒有按照預期拋出異常也會被認為是失敗的測試用例。如果你需要捕獲拋出異常與你的預期值是否一致,還需要在函數前添加另外一個屬性 [ExpectedException(typeof(NegativeHealthException))],這樣這段代碼就會判斷拋出的異常是否正確了。通過下圖可以看到,我們所編寫的兩個測試函數用例都通過了,顯示為綠色。

  技術分享
  這時候大家可能發現了,上面的腳本對應了測試結果中PlayerTest這一部分,另外還有一個PlayerTestWrong的分組並沒有出現。這是因為我們可以在Editor目錄下添加多個測試腳本,這些測試腳本可以測試同一個模塊的代碼,也可以同時測試不同模塊的代碼。下面讓我們來看一下PlayerTestWrong的腳本如何編寫,它的內容和剛才的測試代碼非常相似,只不過調用了返回錯誤值的函數。

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using NUnit.Framework;
 6 
 7 
 8 class PlayerTestWrong
 9 {
10     [Test]
11     public void TestHealthWrong()
12     {
13         Player player = new Player();
14         player.Health = 1000.0f;
15         
16         player.DamageWrong(200);
17         Assert.AreEqual(800,player.Health);
18 
19         player.DamageWrong(150);
20         Assert.AreEqual(950,player.Health);
21     }
22 
23     [Test]
24     [ExpectedException(typeof (NegativeHealthException))]
25     public void NegativeHealthNoException()
26     {
27         Player player = new Player();
28         player.Health = 1000;
29 
30         player.DamageNoException(500);
31         player.DamageNoException(600);
32     }
33 }

  Editor Tests Runner的基本使用方法介紹到這裏就結束了,下面介紹一個小技巧。如果你想實現全自動的單元測試的話,可能會考慮使用批處理來自動化執行測試,為此Unity也提供了批處理的方式。如果你需要使用這個功能的話,只需要在運行Unity的時候傳入以下參數,每個參數的含義請查看 Unity官方文檔 ,本篇博客中就不進行介紹了。

  • runEditorTests
  • editorTestsResultFile
  • editorTestsFilter
  • editorTestsCategories
  • editorTestsVerboseLog

三、小結

  對於遊戲開發者來說,單元測試可能比較陌生。不過現在隨著遊戲復雜度的逐漸提升,另外很多有一定規模的公司都會同時開發多個項目。我們會發現其實有很多功能都被封裝為通用的工具庫。在這種情況下如果我們再不重視代碼的質量,就會導致一個Bug可能同時影響多個項目的開發進度。因此我們還是建議在時間允許的情況下,對比較重要的模塊,以及重用性比較高的代碼增加單元測試。

  最後放上本篇博客中演示的項目源碼:

  Github地址:https://github.com/XINCGer/Unity3DTraining/tree/master/Unit4Unity/Editor%20Test%20Runner 歡迎fork!

  引用資料:

https://stackoverflow.com/questions/3293317/where-is-the-microsoft-visualstudio-testtools-unittesting-namespace-on-vs2010

作者:馬三小夥兒
出處:http://www.cnblogs.com/msxh/p/7354229.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

【Unity遊戲開發】淺談Unity遊戲開發中的單元測試