那今天的內容是延續昨天的商業邏輯所撰寫的測試碼,那接下來要探討的議題是當 Load 方法要注入 Exception 的情境時,針對非同步處理要如何撰寫,那在 NUnit3 的套件中有提供一個方法叫 Assert.ThrowsAsync,那其方法是類似於 Assert.Throws,應用於非同步的情境,測試碼如下:
[Test]
public void FailedGet_Ver1()
{
// Arrange
var storage = Substitute.For<IStorage>();
storage.Load("key").Throws<TimeoutException>();
var cache = new DemoCache(storage);
// Act + Assert
Assert.ThrowsAsync<TimeoutException>(async () => await cache.Get("key"));
}
從這邊我們可以看到,若使用 Assert.ThrowsAsync 的語法,在 Lambda 結構裡就會去執行非同步處理;因此,從 Lambda 中就會取得相對應的值(範例為 string),所以在宣告的時候並不是用 async Task<string>,而是 void 方法。但倘若我們還要驗證共執行幾次,如下:
[Test]
public async Task FailedGet_Ver2()
{
// Arrange
var storage = Substitute.For<IStorage>();
storage.Load("key").Throws<TimeoutException>();
var cache = new DemoCache(storage);
// Act + Assert
Assert.ThrowsAsync<TimeoutException>(async () => await cache.Get("key"));
await storage.Received(1).Load("key");
}
因 Received() 需要等 cache.Get 執行完,因此就需要使用非同步語法。
接下來要探討當 Returns 含有 Multiple Values 的時候,在搭配 Reload 的概念,把 Cache 裡面的東西從 Storage 取出,每當執行 Load 的概念時,則會往後取一個數值,若已到最後一個時,則會固定在該數值,測試馬如下:
[Test]
public async Task SingleGet_WithReload()
{
// Round 1
// Arrange
var storage = Substitute.For<IStorage>();
storage.Load("key").Returns("value1", "value2", "value3");
var cache = new DemoCache(storage);
// Act
var result1 = await cache.Get("key");
var result2 = await cache.Get("key");
// Assert
Assert.AreEqual(result1, "value1");
Assert.AreEqual(result2, "value1");
// Round 2
// Act
var result3 = await cache.Get("key");
// Assert
Assert.AreEqual(result3, "value2");
// Round 3
// Act
var result4 = await cache.Get("key");
var result5 = await cache.Get("key");
// Assert
Assert.AreEqual(result4, "value3");
Assert.AreEqual(result5, "value3");
// Round 4
// Act
var result6 = await cache.Get("key");
var result7 = await cache.Get("key");
// Assert
Assert.AreEqual(result6, "value3");
Assert.AreEqual(result7, "value3");
}
原參考來源是只測試兩次,那為了示意每 Reload 會往後延一個以及固定在最後一個數值;因此,這邊測試碼寫了 Reload 4 Round,並且在 Round 3 就已到第三個數值,Round 4 依舊在第三個數值。而最後的測試碼是測試當有多個非同步執行的時候,兩邊執行的次數是否符合預期,測試碼如下:
[Test]
public async Task MultipleGets_WithReload()
{
// Arrange
var storage = Substitute.For<IStorage>();
var cache = new DemoCache(storage);
// Act
await cache.Get("key1");
await cache.Get("key1");
await cache.Get("key2");
await cache.Get("key1");
await cache.Get("key2");
await cache.Get("key2");
await cache.Get("key2");
await cache.Get("key2");
// Assert
await storage.Received(2).Load("key1");
await storage.Received(3).Load("key2");
}
從這兩天的範例可以看出測試碼驗證非同步處理與商業邏輯一樣,都是要採用 Task、async 與 await 語法;但如果把 Act 的方法直接寫在 Assert 的 Lambda 方法中,則宣告的時候則直接採用 void 即可(如 Exception 篇章),以及探討 Returns 的用碼。