單元測試 – 重構測試

重構test的重要性

好的測試應該要容易維護,容易閱讀,
不應包含程式邏輯在內,因此像是if, while, for迴圈等都不應該出現在測試裡
如果我們驗證的內容會和資料有關,則建議使用Substitute,這樣可以讓我們能夠在每一個測試裡增加不同的資料
而且可以直接在每個測試裡看到資料的內容是什麼
下面是一個範例

91的課程中建議每一次寫成功一個測試案例,就可以先做重構,這樣之後的的開發速度可以更加速

重構測試的步驟

  • 抽出field: mock object(繼承假物件去創建假物件), stub(用Substitute)
  • Setup: SUT初始化(或者[TestInitialize])
  • 抽出方法(用Given為開頭):定義mock object行為,代表假如在跑這個scenario時…
  • Extra Method with “Shouldxxx()” => SUT行為+Assertion

也就是盡可能讓我們的重構能夠符合3A原則,
3A pattern: Arrange, Act, Assert
上面重構後Arrange就用Given…,然後Act就是SUT行為,Assert就是Assertion。

重構的測試的範例

下面為重構後的程式碼:

單元測試 – 隔離框架Substitute.For

STUB的功能

這邊是NSubstitute的說明:http://nsubstitute.github.io/help/getting-started/
Substitute是.NET裡的一個隔離框架,若要使用,需要額外在測試專案用NUGET去安裝NSubstitute

1. 動態產生假物件
2. 模擬回傳值
3. 測試監聽事件
4. 驗證傳入參數是否正確

使用Subsitute(Sub)
使用方法如下

設定呼叫某個方法應該回傳的值

下面可以驗證是否Add這個FUNC有被呼叫到

下面的程式能夠判別傳入的參數是不是正確

驗證回傳值是否正確

使用Substitute來針對不同狀況實作假介面


這是一個用MOCK方法的範例
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using RsaSecureToken;
using Assert = NUnit.Framework.Assert;

namespace RsaSecureToken.Tests
{
[TestFixture]
public class AuthenticationServiceTests
{
[Test()]
public void IsValidTest()
{
var target = new AuthenticationService(new FakeProfile(), new FakeToken());

var actual = target.IsValid(“joey”, “91000000”);

//always failed
Assert.IsTrue(actual);
}
}

public class FakeProfile:IProfile
{

public string GetPassword(string account)
{
if (account == “joey”)
{
return “91”;
}

throw new Exception();
}
}

public class FakeToken:IRsaToken
{
public string GetRandom(string account)
{
return “000000”;
}
}
}

上面做法有什麼問題?

  • 每一個不同的依賴案例就要製作一個不同的FakeObject,會讓寫測試的時間太久
  • 沒辦法直接從程式碼知道為什麼這樣會是Vaild

動態產生物件的使用完整範例

定義假物件行為(stub)

驗證某個函數是否被呼叫

使用mock object assertion
需求:驗證是非法的時候要記一個log

不要用太多mock就算要使用要避免過度指定,也就是當prod code小小變動就導致測試程式壞掉

下面的程式碼可以驗證當呼叫SyncBookOrders()時是不是會呼叫Insert這個函數兩次:

驗證傳入參數

驗證傳入的參數是否包含某些關鍵字

單元測試 – Code Coverage的意義

如何用Code Coverage來衡量單元測試的成果

  • 在build的時候要自動去跑測試(使用CI,CD當作工具)> 大於0%
  • 關心相對趨勢 大於 絕對數字,衡量相對的數字,評估code coverage的數值有沒有比昨天高,每一次針對PROD的程式碼修改都要加上測試,這樣這個數值只會往上不會往下。鼓勵持續的COMMIT > 持續整合,每天都要COMMIT個5~6次或7~8次。
  • 那些測試的投資報酬率高:為什麼要寫測試?目標是要提升產品品質。假設這段PROD CODE沒有BUG,其實不需要為它寫測試。那什麼東西的投資報酬率最高?
    • 1. 要修正的BUG(BUG越晚發現修正成本就更高)
    • 2. 實務上常跑到的scenario
    • 3. 最主要的情境
    • 4.和錢有關的
    • 5.和人命有關的(EX:自動駕駛系統)
    • 6. 最常改到的CODE,可以避免被改錯
  • 那code coverage的意義如下:
    • 觀察沒有被含蓋到的情境:判斷要不要補測試
    • Dead Code:根本就不會跑到的Code(代表這一段PROD CODE不會跑到)

最好導入的方式

從上面看來,兩個最好導入的方式:1. 針對所有BUG的修改去寫測試,2. 針對新的專案去寫測試
然後要確認code coverage不可以往下掉。
假如現在有一陀爛CODE,要增加新功能在那堆爛CODE裡,先把爛CODE抽成method,然後再抽成新的Class
針對爛CODE的public的情境先寫測試,會讓需要寫測試的範圍變小很多。

測試的品質

  • 測試的程式一定要重構
  • 測試的語意一定要清楚明白,一般Assert都會抽出成一個func,這是為了讓測試更容易理解,可以很簡單的的從測試的程式碼由語意就能理解這個測試要做什麼
  • 寫測試的難度會反應程式的好壞