TDD在Unity3D游戲項目開發中的實踐

来源:http://www.cnblogs.com/murongxiaopifu/archive/2016/04/04/5342509.html
-Advertisement-
Play Games

0x00 前言 關於TDD測試驅動開發的文章已經有很多了,但是在游戲開發尤其是使用Unity3D開發游戲時,卻聽不到特別多關於TDD的聲音。那麼本文就來簡單聊一聊TDD如何在U3D項目中使用以及如何使用U3D 5.3.X之後版本已經集成的單元測試模塊Editor Test Runner。 0x01 ...


0x00 前言

關於TDD測試驅動開發的文章已經有很多了,但是在游戲開發尤其是使用Unity3D開發游戲時,卻聽不到特別多關於TDD的聲音。那麼本文就來簡單聊一聊TDD如何在U3D項目中使用以及如何使用U3D 5.3.X之後版本已經集成的單元測試模塊Editor Test Runner。

0x01 你好,TDD

TDD,測試驅動開發改變了我們常見的工作流程,不要求先寫邏輯代碼,反而要求先完成測試代碼。待測試代碼完成之後,我們再將目光轉移到邏輯代碼,根據測試的要求,完成邏輯代碼,使之能夠通過經過拆分後粒度已經很小的測試。這樣做有什麼好處呢?

  1. 要將任務拆分成可測試的各個測試用例,這就要求我們在完成邏輯代碼時要將代碼的功能儘可能細分,換句話說就是讓一個類/方法只負責單一責任,當這個類/方法需要承擔其他類型/方法的責任的時候,就需要分解這個類/方法。這就迫使我們要把程式設計成易於調用和可測試的,即迫使我們解除軟體中的耦合。
  2. 更加適合應對需求的經常性變更。身處游戲開發行業的從業人員都不能否認的一點便是游戲開發中需求變更是一件不可避免甚至是必不可少的事情,而基於測試驅動開發的另一個好處便是一旦因為需求變更而出現bug,能夠很快的發現,進而解決問題。
  3. 單元測試是一種無價的文檔,它是展示方法或類如何使用的最佳文檔。這份文檔是可編譯、可運行的,並且它保持最新,永遠與代碼同步。

0x02 流程,驅動

為了進行TDD測試驅動開發,我們需要瞭解TDD的流程或者說技巧,大體上可以將其步驟簡單的歸納為:紅燈->綠燈->重構。
但是測試是什麼?測試是誰執行的?測試又是如何驅動開發的呢?下麵我們就通過一個小例子來聊一聊這個問題。
程式是什麼?簡單的說就是一段有預期輸出的代碼。我們可以執行這段程式,並獲得程式的輸出。而所謂的測試,便是這樣的一段程式,它會自動調用執行另一段需要被測試的代碼(在這裡我們依靠一些測試框架來實現,例如針對C#的測試框架NUnit),並且根據輸出的可見結果來驗證某些假設是否成立,例如輸出的結果證明假設成立,則測試通過。
簡單的瞭解了測試之後,我們通過一個小例子來看看測試驅動開發的思路和流程是怎樣的,並且一探“驅動”的具體含義。

紅燈

下麵,我們就利用NUnit來編寫我們的第一個測試,來看看測試是如何驅動開發的:

  //測試被攻擊之後傷害數值是否和預期值相等
  [Test]
  public void TakeDamage_BeAttacked_HpEqual()
    {
      HpComp health = new HpComp();
      health.currentHp = 100;

      health.TakeDamage(50);

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

首先可以看到測試代碼的方法名很長,而且測試名中還包括下劃線來保證我們不會漏掉關於這個測試的重要信息(被測試的方法_測試進行的條件_預期結果),因為在編寫測試代碼時,可讀性是重要的考量之一。
繼續看測試代碼,我們現在測試的類是HpComp,它包括一個欄位currentHp保存了現在的血量值,還有一個方法TakeDamage。最開始我們會將currentHp初始化為100,之後調用TakeDamage方法,最後使用NUnit的Assert類所提供的靜態方法AreEqual來斷言假設是否成立,也即判斷是否通過測試。
此時,由於我們還沒有聲明一個叫HpComp的類來處理和血量相關的邏輯,也沒有一個叫currentHp的欄位來保存現在的血量,更沒有一個叫TakeDamage的方法,因此我們運行這個測試的結果便是失敗。換言之,我們現在處於紅燈階段。

綠燈

測試寫完了,此時是紅燈,而此時將這個紅燈變成綠燈的要求,便驅使著我們進行開發。所幸的是,我們要開發的內容,已經在測試中體現了出來:

  1. 實現一個叫做HpComp的類
  2. 為HpComp增加一個欄位currentHp,用來保存現在的血量
  3. 實現一個叫做TakeDamage的方法,而在這個測試中事實上只要求TakeDamage方法將currentHp的值變成50即可。

只要滿足這3點,我們就可以很輕易的使紅燈變成綠燈。所以,為了滿足測試條件,我們可以十分簡單粗暴的寫出如下的代碼:

public class HpComp
{
  public float currentHp;

  public void TakeDamage(float damage)
    {
      this.currentHp = 50f;
    }
}

好了,在上面的測試代碼中只要調用TakeDamage方法,currentHp的值便被設置為了50,和斷言中的預期符合,因此測試通過,狀態也由紅燈變成了綠燈。當然,我們簡單的實現就通過了第一個測試,此時如果有優化代碼的需求,我們就需要對代碼進行重構,使得代碼更加乾凈。

再來幾次

我們的第一個測試用例驅動開發出的代碼顯然滿足了第一個測試的需求,但是如果我們重新回到原點,並且思考一下除了滿足第一個測試中提供的數據,我們的代碼還能做什麼,如果換一個測試條件結果會變得怎樣呢?
我們來完成一個新的測試:

  //測試被攻擊之後傷害數值是否和預期值相等
  [Test]
  public void TakeDamage_BeAttacked_HpEqual2()
    {
      HpComp health = new HpComp();
      health.currentHp = 150;

      health.TakeDamage(10);

      Assert.AreEqual(140f, health.currentHp);
    }

這是一個新的測試(暫時叫做測試2),這就意味著TakeDamage方法除了通過第一個測試之外,還必須通過這個新的測試2。此時,我們最初的TakeDamage的實現,顯然無法通過測試2,因此測試2是紅燈狀態。
這也就是說,隨著我們的測試增加,會帶來更多的預期和要求,從而驅動我們開發出滿足這些預期和要求的代碼來。隨著測試2的出現,我們將TakeDamage方法編程了下麵這個樣子:

  public void TakeDamage(float damage)
    {
      this.currentHp -= damage;
    }

這樣,它不僅通過了測試1,同時也通過了測試2。
但是如果我們重覆上面的流程,提出更多的測試呢?也許我們還會發現TakeDamage方法可能會出現越界的情況,或者是輸入不合法的情況等等。當然,這些都可以通過更多的測試來驅動我們開發出更健康的代碼。

TDD流程小結

通過上面的小例子,我們可以看到TDD的流程或者說開發技巧並不難理解:

  1. 編寫一個會失敗的測試,以證明產品中的代碼或功能的缺陷。
  2. 編寫符合測試預期的代碼。
  3. 重構代碼,如果測試通過了,就可以選擇重構,目標是使代碼的可讀性更強、減少重覆代碼。如果不重構,則可以開始編寫下一個測試,即重覆第4步。
  4. 重覆以上過程。

0x03 問題,方案

由於游戲開發和傳統軟體開發之間的差異,因此在開發游戲的過程中編寫單元測試,會面臨兩個主要的問題:
1.游戲開發中會涉及到很多的I/O操作處理,以及視覺和UI的處理,而這個部分是單元測試中比較難以處理的部分。
2.具體到使用Unity3D開發游戲,我們自然而然的希望能夠將測試的框架集成到Unity3D的編輯器中,這樣更加容易操作。

針對問題1,由於對I/O處理以及UI視覺方面的操作比較難以實施單元測試,所以我們單元測試的主要對象是邏輯操作以及數據存取的部分。
針對問題2,Unity5.3.x已經在editor中集成了測試模塊。該測試模塊依托了NUnit框架(NUnit是一個單元測試框架,專門針對於.NET來寫的.其實在前面有JUnit(Java),CPPUnit(C++),他們都是xUnit的一員.最初,它是從JUnit而來.U3d使用的版本是2.6.4)。
而且除了Unity5.3.x自帶的單元測試模塊之外,Unity官方還推出了一款測試插件Unity Test Tool(基於NSubstitute)。

0x04 實踐,U3D中的單元測試

在Untiy編輯器中寫單元測試:

編寫單元測試用例時,使用的主要是Unity Editor自帶的單元測試模塊,因此單元測試是基於NUnit框架的。
這就要求編寫單元測試時,要引入NUnit.Framework命名空間,且單元測試類要加上[TestFixture]屬性,單元測試方法要加上[Test]屬性,並將測試用例的文件放在Editor文件夾下。
測試用例的編寫結構要遵循3A原則,即Arrange, Act, Assert。
即先要設置測試環境,例如實例化測試類,為測試類的欄位賦值。
之後操作對象,即寫測試的行為。
最後是斷言某件事情是預期的,即判斷是否通過測試。
下麵是一個例子:

using UnityEngine;
using System.Collections;
using NUnit.Framework;

[TestFixture]
public class HpCompTests
{
  //測試被攻擊之後傷害數值是否和預期值相等
  [Test]
  public void TakeDamage_BeAttacked_HpEqual()
    {
      HpComp health = new HpComp();
      health.currentHp = 100;

      health.TakeDamage(50);

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

完成之後,我們就可以打開Unity 5.3.x中集成的單元測試模塊來進行自動化測試了。

好了,本文到此就暫時打住了,之後有新的體驗和想法,還會繼續這個話題的總結,也歡迎各位討論。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 模擬一個方法 Controller: namespace WebApplication1.Controllers { public class TestController : Controller { public TestController(IService service) { this.S ...
  • 一、引言 在前一篇文章已經詳細介紹了SignalR了,並且簡單介紹它在Asp.net MVC 和WPF中的應用。在上篇博文介紹的都是群發消息的實現,然而,對於SignalR是為了實時聊天而生的,自然少了不像QQ一樣的端對端的聊天了。本篇博文將介紹如何使用SignalR來實現類似QQ聊天的功能。 二、 ...
  • 作者:[美]Adam Freeman 來源:《精通ASP.NET MVC 4》 本文將繼續構建 SportsStore 示例應用程式。在上一章中,添加了對購物車的基本支持,現在打算改善並完成其功能。 1.使用模型綁定 MVC 框架使用了一個叫作“模型綁定”的系統,以便通過 HTTP 請求來創建一些 ...
  • ASP.NET Core在啟動以及後續針對每個請求的處理過程中的各個環節都需要相應的組件提供相應的服務,為了方便對這些組件進行定製,ASP.NET通過定義介面的方式對它們進行了“標準化”,我們將這些標準化的組件稱為服務,ASP.NET在內部專門維護了一個DI容器來提供所需的服務。要瞭解這個DI容器以... ...
  • ??二元操作符在對first??second求值時,大致會經歷以下步驟: 1)對first進行求值; 2)如果結果非空,則該結果就是整個表達式的結果; 3)否則求second的值,其結果作為整個表達式的結果。 例如: ...
  • 當我們初學Winform的時候被其神奇的事件功能所吸引,當點擊一個按鈕時,便會跳到我們所寫的點擊方法當中去。然而這並不符合我們對方法的理解,究竟.net在後面幫助我們實現了什麼。我們怎樣模擬其事件的實現呢。下麵先從Button的Click方法說起。 1.首先查看設計器自動生成的代碼 EventHan ...
  • 目錄結構: 1. SVN伺服器搭建和使用-VisualSVNServer 2. SVN客戶端安裝和使用-TortoiseSVN 3. TortoiseSVN使用方法 SVN簡介 SVN是Subversion的簡稱,是一個開放源代碼的版本控制系統,相較於RCS、CVS,它採用了分支管理系統,它的設計目 ...
  • 1,.NET Framework:是開發平臺,包含兩大部分: ①龐大的代碼庫(類庫),可以在客戶語言(C#,VB)中來使用這些代碼 ②Common Language Runtime,負責管理應用程式的執行 2,使用.NET Framework編寫應用程式,就是使用.NET 代碼庫編寫程式。 3,C# ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...