【问题标题】:Async unit testing with portable class libraries使用可移植类库进行异步单元测试
【发布时间】:2013-02-26 19:47:38
【问题描述】:

我有一个至少需要针对 .net 4.5 和 Silverlight 5 的可移植类库。我在尝试在 VS 2012 中编写 MSTest 单元测试时遇到了问题,因为我的库不使用新的 async/await 范例.有什么方法可以测试这个方法吗?

public static void Get(string uri, string acceptHeader, Action<string> callback)
{
    var request = (HttpWebRequest)WebRequest.Create(uri);
    request.Accept = acceptHeader;        

    request.BeginGetResponse(o =>
    {
        var r = o.AsyncState as HttpWebRequest;
        try
        {
            var response = r.EndGetResponse(o);
            using (var sr = new StreamReader(response.GetResponseStream()))
            {
                callback(sr.ReadToEnd());
            }
        }
        catch (Exception ex)
        {
            throw new WebException(string.Format("Unable to access {0}", uri), ex);
        }
    }, request);
}

【问题讨论】:

    标签: c# unit-testing mstest portable-class-library


    【解决方案1】:

    首先,我建议你重新考虑async/await。这是未来的浪潮。 Microsoft.Bcl.Async 为面向 .NET 4.5 和 SL5 的可移植库提供 async 支持。

    但如果你不想这样做,你仍然可以使用async 单元测试:

    [TestMethod]
    public async Task Get_RetrievesExpectedString()
    {
      var tcs = new TaskCompletionSource<string>();
      var client = new ... // arrange
    
      client.Get(uri, acceptHeader, result =>
      {
        tcs.SetResult(result);
      });
      var actual = await tcs.Task;
    
      Assert.AreEqual(expected, actual);
    }
    

    或者,如果你愿意,你也可以“老派”:

    [TestMethod]
    public void Get_RetrievesExpectedString()
    {
      var mre = new ManualResetEvent(initialState: false);
      string actual = null;
      var client = new ... // arrange
    
      client.Get(uri, acceptHeader, result =>
      {
        actual = result;
        mre.Set();
      });
      mre.WaitOne();
    
      Assert.AreEqual(expected, actual);
    }
    

    【讨论】:

    • 我实际上对使用 async/await 持开放态度,但在 PCL 的HttpWebRequest 上似乎缺少 *Async 方法。你会如何改变我的Get 方法?
    • Task.Factory.FromAsync 用于将 APM 转换为 async/await。
    • 在这种转换中,“正确”的方法是让方法返回我发送到回调中的字符串,对吗?
    • 否...正确的做法是让 Get 方法返回一个可等待的 Task,让调用等待 Get。然后,您只需在调用代码中调用您需要的内容,而不是提供回调。
    • @user140550:如果您的Get 方法是async Task&lt;string&gt; 方法,那么它可以只返回字符串。
    【解决方案2】:

    无法抗拒重构代码。您可以使用闭包来执行以下操作

    public static void Get(string uri, string acceptHeader, Action<string> callback)
    {
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.Accept = acceptHeader;        
    
        request.BeginGetResponse(o =>
        {
            try
            {
                var response = request.EndGetResponse(o);
                using (var sr = new StreamReader(response.GetResponseStream()))
                {
                    callback(sr.ReadToEnd());
                }
            }
            catch (Exception ex)
            {
                throw new WebException(string.Format("Unable to access {0}", uri), ex);
            }
        }, null);
    }
    

    不过,最终您可以执行以下操作

    public async static void Get(string uri, string acceptHeader, Action<string> callback)
    {
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.Accept = acceptHeader;        
        var response = await Task.Factory.FromAsync(
                                 request.BeginGetRequestStream ,
                                 request.EndGetRequestStream ,
                                 uri,
                                 null);
        using (var sr = new StreamReader(response))
        {
            callback(sr.ReadToEnd());
        }
    }
    

    好的,这就是我的做法

    void Main()
    {
        {...}
        var request = (HttpWebRequest)WebRequest.Create(uri);
        request.Accept = acceptHeader;
        var response = await request.DownloadStringTaskAwait();
        DoSomeStuff(response);
    }
    
    // Define other methods and classes here
    public static class HttpWebRequestExtension
    {
        public async Task<string> DownloadStringTaskAwait(this HttpWebRequest request)
        {
            var response = await Task.Factory.FromAsync<Stream>(
                                request.BeginGetRequestStream,
                                request.EndGetRequestStream,
                                null);
                        using (var sr = new StreamReader(response))
                        {
                             return sr.ReadToEnd();
                        }
                }
    
    }
    

    【讨论】:

    • 您的方法编译为 PCL?
    • 感谢重构。最初回调是一个单独的方法,因此很麻烦。
    • 你应该避免async void。在这种情况下,如果下载失败,它会干扰您的错误处理。
    • 其实之前的错误处理就搞砸了。投掷在错误的线程上。但是,是的,足够公平。您确实想改用 async Task ,但这会渗透到您的整个代码中。
    • 是的,现在还是一团糟。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-20
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多