--- 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/)