Day 25-Unit Test 应用於 Async Code-1 (情境及应用-5)

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");
}

後面还有其他的测试情况,会在明天叙述。


<<:  [Day10] Hold Shift to Check Multiple Checkboxes

>>:  【把玩Azure DevOps】Day13 Pipeline与Artifacts应用:Build nuget package上传到Private nuget

[Day13] Node.js & NPM

从头到尾硬干一个网站是非常费功费时的事情,为了节省开发时间就会尽量引用现成的套件,随着引用的套件越来...

Cloud Monitor

Cloud Monitor 如果使用了GCP平台,要如何捕捉以及监控错误,我想大概多半会使用Clo...

食谱系统制作_下

制作目标 完成系统 发现问题 Icebear将readline的终止程序放在回圈以前,造成输入料理名...

冒险村27 - Concern

27 - Concern 最後整理的方式再来讲到 Rails 提供功能,主要目的在把相同逻辑 cod...

Re: 新手让网页 act 起来: Day02 - 永远的起点 Hello world!

在开始使用 React 之前,先来回想一下刚学习原生 JS 的时候,我们是怎麽在页面产生 DOM N...