激情六月丁香婷婷|亚洲色图AV二区|丝袜AV日韩AV|久草视频在线分类|伊人九九精品视频|国产精品一级电影|久草视频在线99|在线看的av网址|伊人99精品无码|午夜无码视频在线

高校合作1:010-59833514 ?咨詢電話:400-810-1418 服務(wù)與監(jiān)督電話:400-810-1418轉(zhuǎn)接2

使用NUnit為Unity3D編寫高質(zhì)量單元測試的思考

發(fā)布時(shí)間:2024-04-13 16:00:01 瀏覽量:198次

一、單元測試Pro & Con

最近嘗試在我參與的游戲項(xiàng)目中引入TDD(測試驅(qū)動(dòng)開發(fā))的開發(fā)模式,因此單元測試便變得十分必要。這篇文章就來聊一聊這段時(shí)間的感悟和想法。由于 游戲開發(fā)和傳統(tǒng)軟件開發(fā)之間的差異,因此在開發(fā)游戲,特別是使用Unity3D開發(fā)游戲的過程中編寫單元測試往往會面臨兩個(gè)主要的問題:

1、游戲開發(fā)中會涉及到很多的I/O操作處理,以及視覺和UI的處理,而這個(gè)部分是單元測試中比較難以處理的部分。

2、具體到使用Unity3D開發(fā)游戲,我們自然而然的希望能夠?qū)y試的框架集成到Unity3D的編輯器中,這樣更加容易操作。

但是,單元測試的好處也十分多。

1、TDD,測試驅(qū)動(dòng)開發(fā)。編寫單元測試將使我們從調(diào)用者觀察、思考。特別是先寫測試,迫使我們把程序設(shè)計(jì)成易于調(diào)用和可測試的,即迫使我們解除軟件中的耦合??梢詫⑷蝿?wù)的粒度降低。當(dāng)然TDD是否適合游戲開發(fā)尚有爭論,但是單元測試的必要性是無需置疑的。

2、單元測試是一種無價(jià)的文檔,它是展示方法或類如何使用的最佳文檔。這份文檔是可編譯、可運(yùn)行的,并且它保持最新,永遠(yuǎn)與代碼同步。

3、更加適合應(yīng)對需求的經(jīng)常性變更。身處游戲開發(fā)行業(yè)的從業(yè)人員都不能否認(rèn)的一點(diǎn)便是游戲開發(fā)中需求變更是一件不可避免甚至是必不可少的事情,而單元測試另一個(gè)好處便是一旦因?yàn)樾枨笞兏霈F(xiàn)bug,能夠很快的發(fā)現(xiàn),進(jìn)而解決問題。

二、Unity3D中常用的測試工具

針對問題1,由于對I/O處理以及UI視覺方面的操作比較難以實(shí)施單元測試,所以我們單元測試的主要對象是邏輯操作以及數(shù)據(jù)存取的部分。

針對問題2,Unity5.3.x已經(jīng)在editor中集成了測試模塊。該測試模塊依托了NUnit框架(NUnit是一個(gè)單元測試框架,專門針對于.NET來寫的.其實(shí)在前面有JUnit(Java),CPPUnit(C++),他們都是xUnit的一員.最初,它是從JUnit而來.U3d使用的版本是2.6.4)。

在Unity Editor中實(shí)現(xiàn)測試而不是在IDE中進(jìn)行測試的原因在于,一些Unity的API需要在Unity的環(huán)境中來運(yùn)行,而無法直接在外部的IDE中實(shí)現(xiàn),例如實(shí)例化GameObject。

而且除了Unity5.3.x自帶的單元測試模塊之外,Unity官方還推出了一款測試插件Unity Test Tool(基于NSubstitute),除了單元測試之外還包括:

1.單元測試

2.集成測試

3.斷言組件

需要指出的是Unity Test Tool基于NSubstitute這個(gè)庫。

三、初識單元測試

既然本文的主題是單元測試,那么我們就必須先對單元測試下一個(gè)定義:

一個(gè)單元測試是一段自動(dòng)化的代碼,這段代碼調(diào)用被測試的工作單元,之后對這個(gè)單元的單個(gè)最終結(jié)果的某些假設(shè)進(jìn)行檢驗(yàn)。單元測試使用單元測試框架編寫,并要求單元測試可靠、可讀并且可維護(hù)。只要產(chǎn)品代碼不發(fā)生變化,單元測試的結(jié)果是穩(wěn)定的。

既然有了單元測試的定義,下面我們就嘗試在Unity項(xiàng)目中寫單元測試吧。

一個(gè)單元測試的小例子:

編寫單元測試用例時(shí),使用的主要是Unity Editor自帶的單元測試模塊,因此單元測試是基于NUnit框架的。

借助NUnit,我們可以:

1.編寫結(jié)構(gòu)化的測試。

2.自動(dòng)執(zhí)行選中的或全部的單元測試。

3.查看測試運(yùn)行的結(jié)果。

因此這就要求編寫Unity3D項(xiàng)目的單元測試時(shí),要引入NUnit.Framework命名空間,且單元測試類要加上[TestFixture]屬性,單元測試方法要加上[Test]屬性,并將測試用例的文件放在Editor文件夾下。

下面是一個(gè)例子:

  1. using UnityEngine;

  2. using System.Collections;

  3. using NUnit.Framework;


  4. [TestFixture]

  5. public class HpCompTests

  6. {

  7. //測試被攻擊之后傷害數(shù)值是否和預(yù)期值相等

  8. [Test]

  9. public void TakeDamage_BeAttacked_HpEqual

  10. {

  11. //Arrange

  12. HpComp health = new HpComp;

  13. health.currentHp = 100;

  14. //Act

  15. health.TakeDamage(50);

  16. //Assert

  17. Assert.AreEqual(50f, health.currentHp);

  18. }

  19. }

該例子是測試英雄受到傷害之后,血量是否和預(yù)期的相等。

測試框架會創(chuàng)建這個(gè)測試用例類,并且調(diào)用
TakeDamage_BeAttacked_HpEqual方法來和其交互,最后使用Nunit的Assert類來斷言是否通過測試。

四、單元測試的結(jié)構(gòu)

通過上面的小例子,我們可以發(fā)現(xiàn)單元測試其實(shí)是有結(jié)構(gòu)的。下面我們就來具體分析一下:

使用NUnit提供的特性來標(biāo)識測試代碼

NUnit使用C#的特性機(jī)制識別和加載測試。這些特性就像是書簽,用來幫助測試框架識別哪些部分是需要調(diào)用的測試。

如果要使用NUnit的特性,我們需要在測試代碼中首先引入NUnit.Framework命名空間。

而NUnit運(yùn)行器至少需要兩個(gè)特性才知道需要運(yùn)行什么。

1.[TestFixture]:標(biāo)識一個(gè)自動(dòng)化NUnit測試的類。

2.[Test]:可以加在一個(gè)方法上,標(biāo)識這個(gè)方法是一個(gè)需要調(diào)用的自動(dòng)化測試。

當(dāng)然,還有一些別的特性供我們使用,來方便我們更好的控制測試代碼,例如[Category]特性可以將測試分類、[Ignore]特性可以忽略測試。

常用的NUnit屬性見下表:

  1. [SetUp]

  2. [TearDown]

  3. [TestFixture]

  4. [Test]

  5. [TestCase]

  6. [Category]

  7. [Ignore]

測試命名和布局標(biāo)準(zhǔn)

測試類的命名:

對應(yīng)被測試項(xiàng)目中的一個(gè)類,創(chuàng)建一個(gè)名為[ClassName]Tests的類。

工作單元的命名:

對每個(gè)工作單元(測試),測試方法的方法名由三部分組成,并且按照如下規(guī)則命名:[被測試的方法名]_[測試進(jìn)行的假設(shè)條件]_[對測試方法的預(yù)期]。

具體來說:

1、被測試的方法名

2、測試進(jìn)行的假設(shè)條件,例如“登入失敗”、“無效用戶”、“密碼正確”。

3、對測試方法的預(yù)期:在測試場景指定的條件下,我們對被測試方法的行為的預(yù)期。

其中,對測試方法的預(yù)期會有三種可能的結(jié)果:

1、返回一個(gè)值(數(shù)值、布爾值等等)。

2、改變被測試的系統(tǒng)的一個(gè)狀態(tài)。

3、調(diào)用一個(gè)第三方系統(tǒng)。

可以看出,我們的測試代碼在格式上與標(biāo)準(zhǔn)的代碼有所不同,測試名可以很長,但是在編寫測試代碼時(shí),可讀性是最為重要的方面之一,而測試名中的下劃線 可以令我們不會遺漏所有的重要信息,我們甚至可以將測試方法名當(dāng)做一個(gè)句子來讀,這樣就會使得這個(gè)測試方法的測試目標(biāo)、場景以及預(yù)期都十分明確,無需額外 的注釋。

測試單元的行為——3A原則

有了NUnit屬性可以標(biāo)識可以自動(dòng)運(yùn)行的測試代碼和測試代碼的一些命名規(guī)則,下面我們就來看看如何測試自己的代碼。

一個(gè)單元測試通常包含三個(gè)行為,可以歸納為3A原則即:

1.Arrange,準(zhǔn)備對象,創(chuàng)建對象并進(jìn)行必要的設(shè)置。

2.Act,操作對象。

3.Assert,斷言某件事情是預(yù)期的。

下面是之前的那段簡單的代碼,包含了以上的NUnit的屬性、命名規(guī)范以及3A原則下的行為,其中斷言部分使用了NUnit框架提供的Assert類,被測試的類為HpComp,被測試的方法為TakeDamage。

  1. using NUnit.Framework;


  2. [TestFixture]

  3. public class HpCompTests

  4. {

  5. //測試被攻擊之后傷害數(shù)值是否和預(yù)期值相等

  6. [Test]

  7. public void TakeDamage_BeAttacked_HpEqual

  8. {

  9. //Arrange

  10. HpComp health = new HpComp;

  11. health.currentHp = 100;

  12. //Act

  13. health.TakeDamage(50);

  14. //Assert

  15. Assert.AreEqual(50f, health.currentHp);

  16. }

  17. }

單元測試的斷言——Assert類

NUnit框架提供了一個(gè)Assert類來處理斷言的相關(guān)功能。Asset類用于聲明某個(gè)特定的假設(shè)應(yīng)該成立,因此如果傳遞給Assert類的參數(shù)和我們斷言(預(yù)期)的值不同,則NUnit框架會認(rèn)為測試沒有通過。

Assert類會提供一些靜態(tài)方法,供我們使用。

例如:

  1. Assert.AreEqual(預(yù)期值,實(shí)際值);

  2. Assert.AreEqual(1,2 - 1);

關(guān)于Assert類的靜態(tài)方法,各位可以直接在代碼中看。

五、單元測試的可靠性

我們的目標(biāo)是寫出可靠、可維護(hù)、可讀的測試。

因此,除了遵循單元測試結(jié)構(gòu)規(guī)范編寫單元測試之外,我們還需要注意可靠性、可維護(hù)性以及可讀性這些方面。因此,一些原則我們也需要注意。

不輕易刪除和修改測試

一旦測試寫好了并且通過了,就不應(yīng)該輕易的修改和刪除這些測試。因?yàn)檫@些測試是對應(yīng)系統(tǒng)代碼的保護(hù)傘,在修改系統(tǒng)代碼時(shí),這些測試會告訴我們修改后的代碼是否會破壞已有的功能。

盡量避免測試中的邏輯

隨著測試中的邏輯增多,測試代碼出現(xiàn)缺陷的幾率也會增大。而且由于我們往往相信測試是可靠的,因此一旦測試出現(xiàn)缺陷我們往往不會首先考慮是測試的問題,可能會浪費(fèi)時(shí)間去修改系統(tǒng)代碼。而單元測試中,最好保持邏輯的簡單,因此盡量避免使用下面的邏輯控制代碼。

1.switch、if

2.foreach、for、while

一個(gè)單元測試應(yīng)該是一系列的方法調(diào)用和斷言,但是不應(yīng)該包含控制流語句。

只測試一個(gè)關(guān)注點(diǎn)

在一個(gè)單元測試中驗(yàn)證多個(gè)關(guān)注點(diǎn)會使得測試代碼變得復(fù)雜,但卻沒有價(jià)值。相反,我們應(yīng)該在分開的、獨(dú)立的單元中驗(yàn)證多余的關(guān)注點(diǎn),這樣才能發(fā)現(xiàn)真正導(dǎo)致失敗的地方。

六、單元測試的可維護(hù)性

去除重復(fù)代碼

和系統(tǒng)中的重復(fù)代碼一樣,在單元測試中重復(fù)代碼同樣意味著測試對象某方面改變時(shí)要修改更多的測試代碼。

如果測試看上去都一樣,僅僅是參數(shù)不同,那么我們完全可以使用參數(shù)化測試即使用[TestCase]特性將不同的數(shù)據(jù)作為參數(shù)傳入測試方法。

實(shí)施測試隔離

所謂的測試隔離,指的是一個(gè)測試和其他的測試隔離,甚至不知道其他測試的存在,而只在自己的小世界中運(yùn)行。

將測試隔離的目的是防止測試之間的互相影響,常見的測試之間互相影響的情況可以總結(jié)如下:

1、強(qiáng)制的測試順序:測試要以某種順序執(zhí)行,后一個(gè)測試需要前面的測試結(jié)果,這種情況有可能會導(dǎo)致問題的原因是因?yàn)镹Unit不能保證測試按照某種特定的順序執(zhí)行,因此今天通過的測試,明天可能就不好用了

2、隱藏的測試調(diào)用:測試調(diào)用其他測試

3、共享狀態(tài)被破壞:測試要共享狀態(tài),但是在一個(gè)測試完成之后沒有重置狀態(tài),進(jìn)而影響后面的測試

七、單元測試的可讀性

正如概述中所說單元測試是一種無價(jià)的文檔,它是展示方法或類如何使用的最佳文檔。因此,可讀性這條要求的重要性便可見一斑。試想一下即便是幾個(gè)月之后別的程序員都可以通過單元測試來理解一個(gè)系統(tǒng)的組成以及使用方法,并能夠很快的理解他們要做的工作以及在哪里切入。

單元測試命名

在單元測試的結(jié)構(gòu)中已經(jīng)有過要求和介紹。參考那部分。

單元測試中的變量命名

通過合理的命名變量,可以提高可讀性,使得閱讀測試的人員可以盡快的理解你要驗(yàn)證的內(nèi)容。

還是看看上面的例子

  1. [Test]

  2. public void TakeDamage_BeAttacked_HpEqual

  3. {

  4. //Arrange

  5. HpComp health = new HpComp;

  6. health.currentHp = 100;

  7. //Act

  8. health.TakeDamage(50);

  9. //Assert

  10. Assert.AreEqual(50f, health.currentHp);

  11. }

這段代碼中的斷言使用了一個(gè)魔數(shù)50,但是這個(gè)數(shù)字并沒有使用描述性的名字,因此我們無法盡快的知道這個(gè)數(shù)字預(yù)期的是什么。因此,我們盡可能不要直接使用數(shù)字和結(jié)果比較,而是使用一個(gè)有意義命名的變量來和結(jié)果進(jìn)行比較。

  1. [Test]

  2. public void TakeDamage_BeAttacked_HpEqual

  3. {

  4. HpComp health = new HpComp;

  5. health.currentHp = 100;


  6. health.TakeDamage(50);


  7. float leftHp = 50f;


  8. Assert.AreEqual(leftHp, health.currentHp);

  9. }

八、在Untiy編輯器中寫單元測試

在Unity編輯器中編寫單元測試用例時(shí),使用的主要是Unity編輯器自帶的單元測試模塊,因此單元測試是基于NUnit框架的。

這就要求編寫單元測試時(shí),要引入NUnit.Framework命名空間,且單元測試類要加上[TestFixture]屬性,單元測試方法要加上[Test]屬性,并將測試用例的文件放在Editor文件夾下。

測試用例的編寫結(jié)構(gòu)要遵循3A原則,即Arrange, Act, Assert。

即先要設(shè)置測試環(huán)境,例如實(shí)例化測試類,為測試類的字段賦值。

之后寫測試的行為。

最后是判斷是否通過測試。

下面是一個(gè)例子:

  1. using UnityEngine;

  2. using System.Collections;

  3. using NUnit.Framework;


  4. [TestFixture]

  5. public class HealthComponentTests

  6. {

  7. //測試傷害之后,血的值是否比0大

  8. [Test]

  9. public void TakeDamage_BeAttacked_BiggerZero

  10. {

  11. //Arrange

  12. UnMonoHealthClass health = new UnMonoHealthClass;

  13. health.healthAmount = 50f;


  14. //Act

  15. health.TakeDamage(60f);


  16. //Assert

  17. Assert.GreaterOrEqual(health.healthAmount, 0);

  18. }

  19. }

該例子是測試英雄受到傷害之后,血量是否會越界出現(xiàn)負(fù)值。

測試框架會創(chuàng)建這個(gè)測試用例類,并且調(diào)用
TakeDamage_BeAttacked_BiggerZero方法來和其交互,最后使用Nunit的Assert類來斷言是否通過測試。

使用Editor Tests Runner開始單元測試:

寫完了單元測試用例之后,我們就可以在Unity5.3.x的editor中開始單元測試了。如圖所示:

在這里,我們既可以跑單獨(dú)的測試用例,也可以跑所有的測試用例,通過的是綠色標(biāo)識,未通過的是紅色標(biāo)識。

而在最上面的一行,則是我們可以操作的部分:

Run All:測試全部用例

Run Selected:測試選中的用例

Rerun Failed: 重新測試上一次未通過的測試用例

搜索框:可以搜索用例

種類過濾器:可以根據(jù)種類來篩選用例。種類需要在測試代碼中使用CategoryAttribute來標(biāo)識。

測試結(jié)果篩選器:可以按照通過、失敗以及忽略來篩選用例

在這里我們還可以設(shè)置在編譯前自動(dòng)運(yùn)行單元測試。

使用命令行運(yùn)行單元測試:

除了能夠在Editor中使用單元測試,我們自然更希望能夠?qū)卧獪y試也納入自動(dòng)集成的流水線中,因此有必要從U3D外部調(diào)用測試。不過好在U3D也提供了外部調(diào)用的方式,這樣將單元測試也加入到我們的自動(dòng)集成的流水線中是可行的。

Unity3D 5.3.x版本中提供的命令行選項(xiàng)如下:

  1. runEditorTests 必須,運(yùn)行editor test的選項(xiàng)

  2. editorTestsResultFile 用來保存測試結(jié)果

  3. editorTestsFilter 根據(jù)用例名稱,來運(yùn)行指定的用例

  4. editorTestsCategories 根據(jù)用例種類,來運(yùn)行指定的用例

  5. editorTestsVerboseLog 打印更加詳細(xì)的日志

  6. projectPath 工程目錄

  7. 所以在命令行中開啟測試可以這樣寫:

  8. Unity -runEditorTests -projectPath /Users/fanyou/UnitTest -editorTestsResultFile /Users/fanyou/UnitTest/test.xml -batchmode -quit

九、后記

以上便是關(guān)于在U3D中引入單元測試的一些思考,當(dāng)然,游戲開發(fā)是否適合TDD,換言之是否要先寫單元測試后實(shí)現(xiàn)功能是值得討論的事情,但是單元測試本身是十分有必要在工程中使用的。在代碼結(jié)構(gòu)設(shè)計(jì)、日后的重構(gòu)都會很有幫助。

熱門課程推薦

熱門資訊

請綁定手機(jī)號

x

同學(xué)您好!

您已成功報(bào)名0元試學(xué)活動(dòng),老師會在第一時(shí)間與您取得聯(lián)系,請保持電話暢通!
確定