iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Software Development

單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)系列 第 18

Day 18-隔離框架 (isolation Framework) - NSubstitute 基本介紹-3 (核心技術-10)

NSubstitute 基本語法前言-2

今天會是基本介紹 NSubstitute 的最後一個篇章 XD (含今天花了四天的篇幅介紹,還有一些比較深的寫法看之後能否抽出時間補上),那今天還會介紹幾個也算是常見的語法 DidNotReceive、ReturnsForAnyArgs、AndDoes、When...Do、Callback builder 與 Throwing Exceptions,商業邏輯主角跟昨天一樣是官方網站提供的計算機,分別有模式 Mode 屬性與 Add 方法,程式碼如下:

namespace CalculatorLibrary
{
    public interface ICalculator
    {
        string Mode { get; set; }

        int Add(int a, int b);
    }
}

除此之外,今天會因應模擬物件繼承不會有回傳值的介面,提供另一個商業邏輯程式碼,如下:

public interface IFoo {
    void SayHello(string to);
}

NSubstitute 基本語法-6:DidNotReceive

在昨天我們一起認識了 Received 方法,來確認是否有執行,細一點可以確認執行幾次;那 DidNotReceive 就是 指驗證是否沒有接收,用 Received 的寫法就是 Received(0)(接收 0 次),範例如下:

[Test]
public void DemoDidNotReceiveTest()
{
    // Arrange
    var calculator = Substitute.For<ICalculator>();

    // Act
    calculator.Add(1, 2);
    calculator.Add(-100, 100);

    // Arrange
    calculator.DidNotReceive()
              .Add(Arg.Any<int>(), Arg.Is<int>(x => x >= 500));
}

NSubstitute 基本語法-7:ReturnsForAnyArgs()

很多時候在撰寫驗證商業邏輯的測試時,其實引用的第三方套件我們不太在意其中間流程,只在乎最終結果(如回傳值)是符合我們預期的就好;此時,ReturnsForAnyArgs 方法就可以幫助我們,這隻方法是輸入的方法不管是什麼,所回傳的數值必然是我們所設定的期望值,其範例如下:

[Test]
public void DemoReturnsForAnyArgsTest()
{
    // Arrange
    var calculator = Substitute.For<ICalculator>();

    calculator.Add(1, 2).ReturnsForAnyArgs(100);
    // calculator.Add(default, default).ReturnsForAnyArgs(100);

    // Act + Assert
    Assert.AreEqual(100, calculator.Add(1, 2));
}

PS:面對 ReturnsForAnyArgs 的重點是在最後的回傳值,其參數可使用 C# 提供的 default (預設值運算式) 做處理。


NSubstitute 基本語法-8:AndDoes()

在談論 AndDoes 之前,必須先瞭解一個概念:CallBack,而 CallBack 簡單來說就是指一個程式執行完再去執行另一個程式 (參考來源:什麼是Callback函式),而 AndDoes 就是指要執行的下一隻程式碼,範例如下:

[Test]
public void DemoAndDoesTest()
{
    // Arrange
    var counter = 0;
    var calculator = Substitute.For<ICalculator>();

    calculator.Add(default, default)
              .ReturnsForAnyArgs(x => 0)
              .AndDoes(x => counter++);

    // Act
    calculator.Add(7, 3);
    calculator.Add(2, 2);

    // Assert
    Assert.AreEqual(counter, 2);
}

NSubstitute 基本語法-9:When.....Do

When..Do 總共需要設定兩個設定來啟動 CallBack 機制;第一步,去呼叫方法(大多時候是 void 方法,但其實可以用在有回傳值的方法,但不建議其理由是有回傳值的方法建議用 Returns() 方法,讓撰寫的測試法可以區別哪些是有回傳值,那些則不);其次,利用 Do() 方法啟動下一個方法,示範的程式碼如下:

[Test]
public void DemoWhenDoTest() {
    // Arrange
    var counter = 0;
    var foo = Substitute.For<IFoo>();
    
    foo.When(x => x.SayHello("World"))
       .Do(x => counter++);

    // Act
    foo.SayHello("World");
    foo.SayHello("World");
    
    // Arrange
    Assert.AreEqual(2, counter);
}

NSubstitute 基本語法-10:Callback builder for more complex callbacks

倘若我們要在 When..Do 方法裡面的 Do 方法撰寫一系列的 CallBack 方法們,則可以建置一個 CallBack Builder,建置完後再執行 CallBack Builder 的子方法(因篇幅關係,這邊就先不探討子方法的細節了),示範程式碼如下:

[Test]
public void DemoCallbackBuilderTest() {
    // Arrange
    var sub = Substitute.For<ISomething>();

    var calls = new List<string>();
    var counter = 0;
    
    sub.When(x => x.Something())
       .Do(
           Callback.First(x => calls.Add("1"))
                   .Then(x => calls.Add("2"))
                   .Then(x => calls.Add("3"))
                   .ThenKeepDoing(x => calls.Add("+"))
                   .AndAlways(x => counter++)
           );

    // Act
    for (int i = 0; i < 5; i++)
    {
        sub.Something();
    }
    
    // Arrange
    Assert.That(String.Concat(calls), Is.EqualTo("123++"));
}

NSubstitute 基本語法-11:Throwing Exception

那今天最後要提到的就是如何撰寫例外處理,搭配有回傳值與無回傳值的方法提供兩種寫法,示範的程式碼如下:

[Test]
public void DemoThrowingExceptionWithReturnsTest()
{
    // Arrange
    var calculator = Substitute.For<ICalculator>();

    calculator.Add(-1, -1).Returns(x => { throw new Exception(); });

    // Act + Assert
    Assert.Throws<Exception>(() => calculator.Add(-1, -1));
}
[Test]
public void DemoThrowingExceptionWithWhenDoTest()
{
    // Arrange
    var calculator = Substitute.For<ICalculator>();

    calculator.When(x => x.Add(-2, -2))
              .Do(x => { throw new Exception(); });

    // Act + Assert
    Assert.Throws<Exception>(() => calculator.Add(-2, -2));
}

終於算把常用的 NSubstitute 語法告一個段落了,這幾天介紹的語法其最終的目的就是要協助我們快速建置假物件,省下撰寫假物件的時間;那接下來就要討論單元測試必然要討論的議題:重構(Refactoring)與接縫(Seam)。

PS:又是一個很深的大坑/images/emoticon/emoticon06.gif


上一篇
Day 17-隔離框架 (isolation Framework) - NSubstitute 基本介紹-2 (核心技術-9)
下一篇
Day 19-重構 (Refactoring) 與接縫 (Seam) - 1 (核心技術-11)
系列文
單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言