---
tags: 單元測試
---
# Unit Test in Unity(筆記1)
## 前言
為什麼需要做測試?一個沒有經過測試或驗證的程式碼,都是💩(開玩笑)ㄈ
利用測試驅動開發,先撰寫測試在撰寫程式碼的方式,除了可以確保程式碼是可測的之外,他也能保證在測試案例範圍內是不會有錯的,也因此也能利用測試文件作為規格書使用。
## 術語介紹
* SUT(System Under Test/Software Under Test)
* 待測程式
* 例如一個待測試的方法
* DOC(Depended-on Component)
* 相依元件
* 例如待側元件中包含其他的元件
* Test Double(測試替身)
* Dummy Object
* 不包含實作的物件,在測試中需要傳入但不會用到的參數
* null
* Test Stub
* 回傳固定值的實作,不因測試情況改變
* Test Spy
* 類似Stub
* 會記錄自身的狀態,例如被呼叫的資訊,如何被呼叫,被呼叫的次數等等
* Mock Object
* 模擬真實物件
* Fake Object
* 真實物件的簡化版
* AAA(Arrange...Act...Assert)
* Given...When...Then
## Unity Test Runner
### 介紹
利用unity內建的test runner製作單元測試可測unity兩種環境,Play Mode和Editor Mode,我將會分別介紹如何撰寫兩種測試。
### 簡單示範單元測試範例:
#### EditorMode
1. 須在Editor資料夾中建立測試腳本
2. 撰寫測試程式碼
```csharp=
public class TestScript
{
[Test]
[TestCase(10,'+',2,12)]
[TestCase(10, '-', 2, 8)]
[TestCase(10, '*', 2, 20)]
[TestCase(500, '/', 10, 50)]
public void Calculate_Test(float x, char pointer, float y,float result)
{
var Calculator = new Calculator(x,y,pointer);
var score = Calculator.ScoreCount();
Assert.That(score,Is.EqualTo(result));
}
}
```
```csharp=
public class Calculator
{
float x;
float y;
char pointer;
public Calculator(float x,float y,char pointer)
{
this.x = x;
this.y = y;
this.pointer = pointer;
}
public float ScoreCount()
{
float _value = 0;
switch (pointer)
{
case '+':
_value = x + y;
break;
case '-':
_value = x - y;
break;
case '*':
_value = x * y;
break;
case '/':
_value = (x / y);
break;
default:
break;
}
return _value;
}
}
```
![](https://i.imgur.com/rggmohM.png)
3. 跑Unity Test Runner
![](https://i.imgur.com/ycVFVMg.png)
#### PlayMode
1. 利用Test Runner建立PlayMode測試資料夾
![](https://i.imgur.com/C7HIze7.png)
2. 撰寫測試程式碼
```csharp=
[UnityTest]
public IEnumerator SceneLoading(){
//store test scene
Scene currentScene = SceneManager.GetActiveScene();
//load scene
yield return SceneManager.LoadSceneAsync("testScene",LoadSceneMode.Additive);
//After it is loaded,set the secne as Active
SceneManager.SetActiveScene(SceneManager.GetSceneByName("testScene"));
//Assert that the game scene has been set to active.
Assert.IsTrue(SceneManager.GetActiveScene().name == "testScene");
//Clean up
SceneManager.SetActiveScene(currentScene);
yield return SceneManager.UnloadSceneAsync("testScene");
}
```
![](https://i.imgur.com/yoZbaed.png)
3. 跑Unity Test Runner
![](https://i.imgur.com/0rcDTON.png)
### 用NSUBSTITUTE做單元測試
NSubstitute為Mocking Object,可以模擬DOC製作出一個假物件進行測試。需注意的是,必須有Virtual才能做替換
1. 到[Github](https://github.com/nsubstitute/NSubstitute)下載Nsubstitute DLL,請注意版本須符合Unity的C# .Net版本[或點此下載](https://drive.google.com/open?id=1DF1tcLVX_5WtIxj1VKK6S2lXUCnEGmoa)
2. 將DLL丟入Unity編譯
3. 撰寫測試程式碼
4. 於Unity跑測試
:::info
一個SUT
:::
```csharp=
public interface ICalculator
{
int Add(int x,int y);
string Mode{get;set;}
event EventHandler AnEventAction;
}
```
#### 範例一
:::info
示範如何自訂輸出SUT資料
:::
```csharp=
[Test]
public void NSubstitute_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(1,2).Returns(3);
//Assert
Assert.That(calculator.Add(1,2),Is.EqualTo(3));
}
```
***結果:***
![](https://i.imgur.com/BiuPE0k.png)
#### 範例二
:::info
自訂SUT輸入參數
:::
```csharp=
[Test]
public void NSubstitute_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(1,2);
//Assert
calculator.Received().Add(1,2);
calculator.DidNotReceive().Add(5,7);
}
```
***結果:***
![](https://i.imgur.com/FTkP6Pc.png)
***
:::danger
失敗案例
:::
```csharp=
[Test]
public void NSubstitute_Method_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(5,7);
//Assert
calculator.Received().Add(1,2);
calculator.DidNotReceive().Add(5,7);
}
```
***結果:***
![](https://i.imgur.com/5FAcngu.png)
#### 範例三
:::info
自訂屬性,可用兩種方式定義
:::
```csharp=
[Test]
public void NSubstitute_Property_Test(){
//1.
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Mode.Returns("Decimal");
//Assert
Assert.That(calculator.Mode == "Decimal");
//2.
//Act
calculator.Mode ="Hex";
//Assert
Assert.That(calculator.Mode== "Hex");
}
```
***結果***
![](https://i.imgur.com/1meF0vI.png)
#### 範例四
:::info
支援參數檢查
:::
```csharp=
[Test]
public void NSubstitute_ArgsCheck_Test(){
//1.
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(1,5);
//Assert
calculator.Received().Add(1,Arg.Is<int>(x=>x<6));
}
```
***結果:***
![](https://i.imgur.com/stuksFI.png)
:::danger
失敗案例
:::
```csharp=
[Test]
public void NSubstitute_ArgsCheck_Test(){
//1.
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(1,10);
//Assert
calculator.Received().Add(1,Arg.Is<int>(x=>x<6));
}
```
***結果:***
![](https://i.imgur.com/uUkK69N.png)
#### 範例五
:::info
在參數做判斷(炫技寫法)
:::
```csharp=
[Test]
public void NSubstitute_ArgsCheck_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Add(Arg.Is<int>((x)=>x>0),Arg.Is<int>((x)=>x>0))
.Returns(x=>(int)x[0]+(int)x[1]);
//Assert
Assert.That(calculator.Add(5,10) == 15);
}
```
***結果:***
![](https://i.imgur.com/xN1IuaA.png)
#### 範例六
:::info
Return可一次定義多回傳值
:::
:::warning
回傳值具有順序性
:::
```csharp=
[Test]
public void NSubstitute_Return_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Mode.Returns("Hex","Dec","Bin");
//Assert
Assert.That(calculator.Mode=="Hex");
Assert.That(calculator.Mode=="Dec");
Assert.That(calculator.Mode=="Bin");
}
```
***結果:***
![](https://i.imgur.com/N0GdqDa.png)
:::info
若對調順序
:::
```csharp=
[Test]
public void NSubstitute_Return_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
//Act
calculator.Mode.Returns("Hex","Dec","Bin");
//Assert
Assert.That(calculator.Mode=="Bin");
Assert.That(calculator.Mode=="Hex");
Assert.That(calculator.Mode=="Dec");
}
```
***結果:***
![](https://i.imgur.com/b0J6RY6.png)
#### 範例七
:::info
事件測試
:::
```csharp=
[Test]
public void NSubstitute_Event_Test(){
//Arrange
ICalculator calculator = Substitute.For<ICalculator>();
bool isEventTrigger = false;
//Act
calculator.AnEventAction += (sender,args)=>{isEventTrigger = true;};
calculator.AnEventAction += Raise.Event();
//Assert
Assert.That(isEventTrigger);
}
```
***結果:***
![](https://i.imgur.com/Dc1kkc8.png)
## Unity Test Framework
待續...
## 參考資料
[泰迪](http://teddy-chen-tw.blogspot.com/2014/09/test-double1.html)
[肉豬](https://matthung0807.blogspot.com/2018/03/sutdoc.html)
[Nsubstitute Github](https://nsubstitute.github.io/help/getting-started/)
[Unit testing MonoBehaviours(UnityBlog)](https://blogs.unity3d.com/2014/06/03/unit-testing-part-2-unit-testing-monobehaviours/)
[Unity Test Framework](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/index.html)
[Performance Benchmarking in Unity](https://blogs.unity3d.com/2018/09/25/performance-benchmarking-in-unity-how-to-get-started/)