C# 对 System.Reactive 可观察对象进行单元测试

假设程序的某一部分正在使用 IObservable<T>,而你需要想办法对它进行单元测试。

System.Reactive 有一些可以生成序列的运算符(比如 Return),还有可以将响应式序列转换为常规集合或项的其他运算符(比如 SingleAsync)。可以使用 Return 之类的运算符来创建可观察依赖项的存根,使用 SingleAsync 之类的运算符来测试输出。

观察下面的代码,其中将 HTTP 服务作为依赖项并对 HTTP 请求运用了超时:

public interface IHttpService
    {
        IObservable<string> GetString(string url);
    }
    
    public class MyTimeoutClass
    {
        private readonly IHttpService _httpService;
    
        public MyTimeoutClass(IHttpService httpService)
        {
            _httpService = httpService;
        }
    
        public IObservable<string> GetStringWithTimeout(string url)
        {
            return _httpService.GetString(url)
                .Timeout(TimeSpan.FromSeconds(1));
        }
    }

被测试的系统是 MyTimeoutClass,它消耗了可观察对象的依赖项,并生成了一个可观察对象作为输出。

Return 运算符创建的冷序列只包含一个元素,通过 Return 可以创建简单的存根。SingleAsync 运算符返回的 Task<T> 会在下一个事件到来时完成。SingleAsync 可以用在类似下面的简单单元测试中:

class SuccessHttpServiceStub : IHttpService
    {
        public IObservable<string> GetString(string url)
        {
            return Observable.Return("stub");
        }
    }
    
    [TestMethod]
    public async Task MyTimeoutClass_SuccessfulGet_ReturnsResult()
    {
        var stub = new SuccessHttpServiceStub();
        var my = new MyTimeoutClass(stub);
    
        var result = await my.GetStringWithTimeout("http://exampleurl")
            .SingleAsync();
        Assert.AreEqual("stub", result);
    }

在存根代码中,另一个重要的运算符是 Throw,它返回一个以错误结束的可观察对象。该运算符还可以对错误用例进行单元测试,下例使用了 7.2 节中的 ThrowsAsync 辅助方法。

private class FailureHttpServiceStub : IHttpService
    {
        public IObservable<string> GetString(string url)
        {
            return Observable.Throw<string>(new HttpRequestException());
        }
    }
    
    [TestMethod]
    public async Task MyTimeoutClass_FailedGet_PropagatesFailure()
    {
        var stub = new FailureHttpServiceStub();
        var my = new MyTimeoutClass(stub);
    
        await ThrowsAsync<HttpRequestException>(async () =>
        {
            await my.GetStringWithTimeout("http://exampleurl")
                .SingleAsync();
        });
    }

Return 和 Throw 是创建可观察存根的极好选择,SingleAsync 则是通过 async 单元测试来测试可观察对象的便捷之道。可以将它们组合起来,这非常适合用于简单的可观察对象,但一旦涉及时间,它们就不那么好用了。假如测试 MyTimeoutClass 的超时负荷,单元测试必须等待对应的时长。然而这个办法很糟糕:由于引入了竞争条件,因此单元测试变得不可靠,而且当添加更多单元测试后,其扩展性也不佳。

 (完)

相关阅读:


C# 对 async 方法进行单元测试

C# 对预期失败的 async 方法进行单元测试

C# 对 async void 方法进行单元测试

C# 对数据流网格进行单元测试

C# 对 System.Reactive 可观察对象进行单元测试

C# 通过伪造调度对 System.Reactive 可观察对象进行单元测试

评论

此博客中的热门博文

in 参数(C# 7.2)

C# ref 局部变量和 ref return

类 ref 结构体(C# 7.2)