如何为单元测试提供HttpWebResponse.GetResponseAsync()?

nho*_*er9 1 c# unit-testing mocking httpwebrequest microsoft-fakes

我被指派为我无权修改的应用程序编写单元测试.我想要单元测试的方法进行如下调用:

HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;
Run Code Online (Sandbox Code Playgroud)

我在这个项目中使用过Microsoft Fakes进行了数以千计的其他测试,所以我想我会在这里做同样的事情.在我看来,最简单,最干净的解决方案是使用该request.GetResponseAsync()方法.然后我可以返回一些虚假内容并确保该方法正确处理它而不实际发出请求.

GetResponseAsync()返回一个Task<WebResponse>对象.所以我通常会这样做:

using (ShimsContext.Create())
{
    System.Net.Fakes.ShimWebRequest.AllInstances.GetResponseAsync = (x) =>
    {
        return new Task<WebResponse>(() =>
        {
            HttpWebResponse toRet = new HttpWebResponse();
            return toRet;
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是上面没有编译因为

'System.Net.HttpWebResponse.HttpWebResponse()'已过时:'此API支持.NET Framework基础结构,不能直接在您的代码中使用.

我知道这种类型已经过时,我们现在应该使用不同的东西,但我试图测试的遗留代码不允许我这么奢侈.我已经看过很多关于这个主题的问题,但似乎没有人回答这个问题.

Old*_*Fox 7

我将我的答案分为两部分; 第一部分是您正在寻找的解决方案......在第二部分我将讨论您在UT背景下的其他选项(所以这个答案将有助于其他人......)

由于您已经使用过MsFakes,因此可以使用Shims创建实例.以下代码段是一个示例,显示了初始化和使用的方法ShimHttpWebResponse:

[Test]
public async Task InitializeShimHttpWebResponse()
{
    using (ShimsContext.Create())
    {
        ShimWebRequest.AllInstances.GetResponseAsync = (x) =>
        {
/* you can replace the var with WebResponse if you aren't going to set any behavior */
            var res = new ShimHttpWebResponse(); 
            return Task.FromResult((WebResponse)res);
        };

        ShimWebRequest.CreateString = uri =>
        {
            WebRequest req = new ShimFileWebRequest();
            return req;
        };

        var request = WebRequest.Create("");
        var response = await request.GetResponseAsync() as HttpWebResponse;

        Assert.IsNotNull(response);
    }
}
Run Code Online (Sandbox Code Playgroud)

假配置:

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
  <Assembly Name="System" Version="4.0.0.0"/>
  <ShimGeneration>
    <Add FullName="System.Net.HttpWebResponse"/>
    <Add FullName="System.Net.WebRequest"/>
    <Add FullName="System.Net.HttpWebRequest"/>
    <Add FullName="System.Net.FileWebRequest"/>    
  </ShimGeneration>
</Fakes>
Run Code Online (Sandbox Code Playgroud)

只是总结一下这一部分; IMO,对于通用案例,使用代码编织工具(MsFakes)是处理这种情况的正确方法.(我将在下一节中进一步解释)

我看到你有4个选项来创建一个新的实例HttpWebResponse:

1.使用反射 - 在这种情况下一个坏主意(UT ..)

2.继承 - 自定义模拟...

3.使用基于代理的框架例如; Moq,Rhinomocks等

4.使用代码编织工具(正如您已经使用过的那样......)例如; Msfakes,Typemock隔离器等

还有一个选项:创建集成测试而不是UT ...

1.反思:

HttpWebResponse有3个C'tors;(公共,内部,受保护)

要使用内部\ public C'tors,你必须使用反射,但是在大多数情况下它不会开箱即用,然后你必须违反一些UT规则(小,快等等). .)

反射将开箱即用的2种情况是:

  1. 您不会在实例上调用任何有问题的属性/方法,并且SUT(正在测试的主题)只是将此实例传递给他的一个依赖项

  2. 您将使用更多反射初始化实例字段.

虽然第一个是一个简单的情况(如果这是你的情况,那么你应该使用反射)第二个是在UT的背景下的坏习惯; 你的UT不会小/可读/可维护,执行时间会增加,微软可能会进行一些重构,这可能会破坏你的UT.

对于受保护的,您应该通过继承(编译与运行时...)来调用它.

2.继承:

受保护的C'tor也具有过时的属性,但是属性IsError设置为false,这允许您继承此类,然后您将能够更改方法/属性的行为; 虚拟 - 覆盖,非虚拟 - 仅当您有权访问类成员(或反射)时

此选项的主要缺点是您必须生成的代码量及其复杂性.

3.使用基于代理的框架:

在场景背后,这些工具使用反射来创建基于类的继承实例(组合选项1和2)这些工具具有一些内置方法,可以使您的假代码更小/可读/可维护.

缺点:(我不打算指出基于代理的工具的所有缺点......)

  1. 你仍然无法改变非虚方法的行为.

  2. 您没有访问实例成员的权限.

这两个可以通过使用代码编织工具来解决.

4.编码编织工具:

这些工具可以让你做几乎任何事情,这就是为什么这些工具对于通用情况最好的原因(我可以总结一下句子的主要缺点 - 强大的力量带来了很大的责任......).在您的情况下,他们为您提供最佳解决方案; 由于HttpWebResponse具有非虚方法,并且您不想重构代码,因此这对您来说是正确的解决方案.

但是,Msfakes没有为UT提供额外的方法/功能(AssertsWasCalled计数等等),所以除非你要用其他工具替换工具,否则你应该将它与代理基础工具结合起来(免费工具) !!!!)