非同期処理の結果だけでなく内部状態もテストする(Unity/UniTask)

14270 ワード

Unity: 2021.2.8
Test Framework: 2.0.1-pre18
UniTask: 2.2.5

非同期処理を実施している最中の挙動を検証したい

長めの非同期処理を書いていると、テストコードで非同期処理を await している最中にも検証を走らせたい場合があるのではないでしょうか。

例えばカウントダウンする処理があるとします。

public async UniTask Countdown3To0Async(Text text) {
    text.text = "3";
    await UniTask.Delay(1000);
    text.text = "2";
    await UniTask.Delay(1000);
    text.text = "1";
    await UniTask.Delay(1000);
    text.text = "0";
}

内部で Text をいじっていますので、ちゃんと 3 2 1 となっているかテストしたいとします。

[Test]
// Test Framewor 2.0 以降だと Task のテストが書ける (記事投稿時点ではpreview)
public async Task カウントダウンが文字に反映されるかテスト() {
    var text = new GameObject().AddComponent<Text>();
    await Countdown3To0Async(text);
    // ここに来るのはカウントダウンがすべて終わった後
}

しかし、await するとカウントダウンが終わる3秒後に検証することになり、1秒ごとの Text の状態を検証することはできません。

UniTask.WhenAll で検証を同時に走らせる

そこで、実行と評価を同時並行させるため、 UniTask.WhenAll を使います。

[Test]
public async Task カウントダウンが文字に反映されるかテスト() {
    var text = new GameObject().AddComponent<Text>();
    // UniTask.Deferで評価処理を作成
    var assertAsync = UniTask.Defer(async () => {
        await UniTask.Yield(); // 評価を1フレーム後にずらす
        Assert.That(text.text, Is.EqualTo("3"));
        await UniTask.Delay(1000);
        Assert.That(text.text, Is.EqualTo("2"));
        await UniTask.Delay(1000);
        Assert.That(text.text, Is.EqualTo("1"));
    });
    // UniTask.WhenAll で実行と評価を並列に実施
    await UniTask.WhenAll(Countdown3To0Async(text), assertAsync);
    Assert.That(text.text, Is.EqualTo("0"));
}

これで、await と平行してテストコードで検証を行うことができます

もっといい書き方知ってる人いないですか?

内部状態の検証はできるようになるので解決ですが、テストコードの流れが前後するのでこの書き方は好きではないです。コードは上から下に記述するべきという考えに則ると、こんな風に書きたい。

[Test]
public async Task カウントダウンが文字に反映されるかテスト() {
    var text = new GameObject().AddComponent<Text>();
    // UniTask.WhenAll で実行と評価を並列に実施
    await Countdown3To0Async(text).なんかいい感じの拡張メソッド(async () => {
        Assert.That(text.text, Is.EqualTo("3"));
        await UniTask.Delay(1000);
        Assert.That(text.text, Is.EqualTo("2"));
        await UniTask.Delay(1000);
        Assert.That(text.text, Is.EqualTo("1"));
    });
    Assert.That(text.text, Is.EqualTo("0"));
}