iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0

Unit Test 應用於 Async Code-1 - 前言

今天的文章內容是參考於 Testing C# Async Code with NUnit and NSubstitute,其目的在於想了解如何程式碼帶有非同步的方法時,則測試是否有需要改寫或注意的地方。大概簡述非同步方法的概念與語法,非同步方法是指執行某特定任務,無需等該特定任務完成就去執行其他的任務(比如泡咖啡要等咖啡出來的時候做早餐),而我們採用的是 Task-Based 的方式撰寫,其中會用到 async 與 await,詳細內容可參見async 與 await,僅節錄其中一段撰寫的語法:

static async Task<string> MyDownloadPageAsync(string url)
{
    using (var webClient = new WebClient())
    {
        Task<string> task = webClient.DownloadStringTaskAsync(url);
        
        string content = await task;
        
        return content;
    }
}

Unit Test 應用於 Async Code-1 - 用程式碼講故事(商業邏輯)

因此,從參考影片中提供了一個故事情節,其目的是我們取 Cache 中指定 Key 的數值,而 Cache 未有該 Key 的數值或點擊超過兩次,則會從 Storage 取得最新的值。而 IStorage 提供了兩隻 Task 方法:Save 與 Load,分別作為儲存與載入,程式碼如下:

public class DemoCache
{
    private readonly IStorage Storage;

    private readonly Dictionary<string, int> Hits 
                   = new Dictionary<string, int>();

    private readonly Dictionary<string, string> Cache 
                   = new Dictionary<string, string>();

    public DemoCache(IStorage inStorage) {
        Storage = inStorage;
    }

    public async Task<string> Get(string key)
    {
        if (!Hits.ContainsKey(key) || Hits[key] >= 2)
        {
            string value = await Storage.Load(key);

            Cache[key] = value;
            Hits[key] = 1;

            return value;
        }
        else
        {
            Hits[key]++;

            return Cache[key];
        }
    }
}

public interface IStorage
{
    Task Save(string key, string value);

    Task<string> Load(string key);
}

Unit Test 應用於 Async Code-1 - 用程式碼講故事(測試碼)

於是乎,我們就可以開始撰寫測試碼,而測試的情境要考量數種情況,分別如下:

  1. 單獨取得數值,從中我們可以看到撰寫 Receive 的時候因 Task 的 Get 方法尚未跑完,如果沒添加 await 則會顯示失敗,但 result 因在建立變數的時候就已經建設 await,所以不需要再額外撰寫語法。
[Test]
public async Task SingleGet()
{
    // Arrange
    var storage = Substitute.For<IStorage>();

    storage.Load("key").Returns("value1");

    var cache = new DemoCache(storage);

    // Act
    var result = await cache.Get("key");

    // Assert
    await storage.Received(1).Load("key");
    
    Assert.AreEqual(result, "value1");
}
  1. 示範若要取得多個數值,其情境為 cache 各取 key1 與 key2 各一次,則測試碼如下:
[Test]
public async Task MultipleGets_WithDifferentKeys()
{
    // Arrange
    var storage = Substitute.For<IStorage>();
    var cache = new DemoCache(storage);

    // Act
    await cache.Get("key1");
    await cache.Get("key2");

    // Assert
    await storage.Received(1).Load("key1");
    await storage.Received(1).Load("key2");
}
  1. 於是乎,為了保險執行多次的情況,那如果使用同一個 cache 採用 3 次的 Get 方法,其第二次理論上不應該執行 Storage.Load(key),所以若執行三次的 cache.Get("key1") 則應該只會 Received 兩次的結果,其測試碼如下:
[Test]
public async Task MultipleGets_WithSametKeys()
{
    // Arrange
    var storage = Substitute.For<IStorage>();
    var cache = new DemoCache(storage);

    // Act
    await cache.Get("key1");
    await cache.Get("key1");
    await cache.Get("key1");

    // Assert
    await storage.Received(2).Load("key1");
}

後面還有其他的測試情況,會在明天敘述。


上一篇
Day 24-Unit Test 應用於 ORM (以 Entity Framework 為例) (情境及應用-4)
下一篇
Day 26-Unit Test 應用於 Async Code-2 (情境及應用-6)
系列文
單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言