今天的文章主要參考於 NSubstitute官方網站,正所謂工欲善其事,必先利其器,我們若想要透過 NSub 自動化寫出好的假物件,那就要先了解 NSub(就跟在使用 NUnit3 就要了解它常用的特性 SetUp 等等語法一樣,詳情可見 Day-6、Day-7)。
所以,接下來會介紹幾個比較常見的語法:建置 Substitute、Return()、Received()、Arg.Any()、Arg.Is(T value),而今天的商業邏輯主角就是官方網站提供的計算機方法,分別有模式 Mode 屬性與 Add 方法,程式碼如下:
namespace CalculatorLibrary
{
public interface ICalculator
{
string Mode { get; set; }
int Add(int a, int b);
}
}
在 NSubstitute 中,最一開始我們要利用 NSub 建置假物件,而建置假物件的基本語法如下:
var substitute = Substitute.For<ISomeInterface>();
因此,若要建置我們上面提到的計算機假物件,則測試碼如下:
var calculator = Substitute.For<ICalculator>();
Substitute 除了基本的假物件建置外,也可以有建構子、複數個介面產製相對應及委派的寫法,範例如下:
// 帶有建構子
var substitute = Substitute.For<SomeClassWithCtorArgs>(5, "hello world");
// 複數個介面
var substitute = Substitute.For<IInterface1, IInterface2>();
// 委派
var substitute = Substitute.For<Func<string>>();
Return 方法,顧名思義就是指新增回傳的方法,我們在設立假物件的時候,為了讓商業邏輯能順利運作,通常會給定我們預期的值,在 Day-9 ~ Day-14 的時候,花了六天的時候幫各種假物件刻寫各種預期結果,而在 NSub 之後,可以利用 Return 來預期我們要的值,範例如下:
[Test]
public void DemoReturnTest()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
calculator.Add(1, 2).Returns(3);
// Act + Assert
Assert.AreEqual(calculator.Add(1, 2), 3);
}
可以看到我們把 calculator 裡面的 Add,預設呼叫 Add(1, 2) 的時候要回傳數值 3。
當然,我們可以寫複數個 Return 結果,如下:
[Test]
public void DemoReturnTest2()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
calculator.Mode.Returns("HEX", "DEC", "BIN");
// Act + Assert
Assert.AreEqual(calculator.Mode, "HEX");
Assert.AreEqual(calculator.Mode, "DEC");
Assert.AreEqual(calculator.Mode, "BIN");
}
Received 方法,從字面上解讀是接受到了什麼東西,那在 NSub 看到這個詞所代表在執行測試時,該測試總共執行了多少次該方法;換言之,我們可以去確認有沒有執行該方法(模擬物件的好幫手),執行了多少次,來看這段範例:
[Test]
public void DemoReceivedTest()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
calculator.Add(1, 2).Returns(3);
// Act
calculator.Add(1, 2);
calculator.Add(1, 2);
// Assert
calculator.Received().Add(1, 2);
// calculator.Received(2).Add(1, 2);
// calculator.Received(4).Add(1, 2);
}
可以看到最後 Assert 階段,我們來確認是否有執行 Add 方法(Assert 第一行),再進階一點則是驗證執行幾次(如 Assert 第二行與第三行)。順帶一提,若執行第三行的時候,會發生失敗(因為在 Act 階段總共只執行了兩次;但在驗證就說需要四次,兩邊不符,Visual Studio 會跳出以下的錯誤訊息。)
Arg.Any() 是可協助我們輸入任何的參數,很多時候在呼叫第三方套件會需要設定一些參數,但對於商業邏輯的撰寫其實並沒有直接影響的時候,就可以利用 Arg.Any() 幫助我們忽略對這些參數的設定,那示範的程式碼如下:
[Test]
public void DemoArgAnyTest()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
// Act
calculator.Add(10, -5);
// Assert
calculator.Received().Add(10, Arg.Any<int>());
}
從該段程式碼可以看出,我們只在意在執行 Add 方法的時候,第一個參數有代入 10,而第二個參數是什麼,其實不是這次測試的關注點,僅只要符合介面要輸入的參數即可。
除了寫在驗證上,我們也可以在 Act 階段使用,如下:
[Test]
public void DemoArgAnyTest2()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
calculator.Add(Arg.Any<int>(), Arg.Any<int>()).Returns(x => (int)x[0] + (int)x[1]);
// Act + Assert
Assert.AreEqual(calculator.Add(5, 10), 15);
}
但倘若我們要對參數要帶有設定的話,NSub 也有提供相對應的方法 —— Arg.Is(T value),其中,通常後面的 T value 會有兩種呈現方式,給定值或利用 Lambda 語法帶出 T value 的條件,直接看測試碼,如下:
[Test]
public void DemoArgIsTest1()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
// Act
calculator.Add(10, -5);
// Assert
calculator.Received().Add(10, Arg.Is(-5));
}
[Test]
public void DemoArgIsTest2()
{
// Arrange
var calculator = Substitute.For<ICalculator>();
// Act
calculator.Add(10, -5);
// Assert
calculator.Received().Add(10, Arg.Is<int>(x => x < 0));
}