Skip to main content
 首页 » 编程设计

c#之如何为单元测试填充 HttpWebResponse.GetResponseAsync()

2024年01月21日28Terrylee

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

HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse; 

我已经在这个项目中使用 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; 
        }); 
    } 
} 

问题是上面没有编译,因为

'System.Net.HttpWebResponse.HttpWebResponse()' is obsolete: 'This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.'



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

请您参考如下方法:

我将把我的答案分成两部分;第一部分是您正在寻找的解决方案......在第二部分中,我将讨论您在 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); 
    } 
} 

假货配置:
<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> 

只是总结本节; IMO,对于一般情况,使用代码编织工具(MsFakes)是处理这种情况的正确方法。(我将在下一节中详细解释)

如我所见,您有 4 个选项来创建 HttpWebResponse 的新实例。 :

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

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

3. 使用基于代理的框架起订量,犀牛等。

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

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

1. 反射(reflection):
HttpWebResponse有 3 个 C'tors;( public , internal , protected )

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

反射将开箱即用的两种情况是:
  • 您不会在实例上调用任何有问题的属性/方法,而 SUT(被测对象)只需将此实例传递给他的依赖项之一
  • 您将使用更多反射来初始化实例字段。

  • 虽然第一个是一个简单的案例(如果这是你的情况,那么你应该使用反射)第二个在 UT 的上下文中是一个不好的做法;您的 UT 不会很小/可读/可维护,执行时间将会增加,Microsoft 可能会进行一些重构,这可能会破坏您的 UT。

    对于 protected ,您应该通过继承调用它(编译与运行时......)。

    2. 继承:

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

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

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

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

    缺点:(我不会指出基于代理的工具的所有缺点......)
  • 你仍然不能改变非虚方法的行为。
  • 您无权访问实例成员。

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

    4. 织码工具:

    这些工具几乎可以让你做任何事情,这就是为什么这些工具最适合一般情况的原因(我可以用一句话总结主要缺点 - 能力越大,责任越大......)。
    在您的情况下,他们为您提供最佳解决方案;
    由于 HttpWebResponse有一个非虚拟方法,你不想重构你的代码,这对你来说是正确的解决方案。

    然而,Msfakes 并没有为您提供额外的 UT 方法/功能( AssertsWasCalled、计数等...),因此除非您打算用其他工具替换该工具,否则 IMO 您应该将其与代理基础工具结合使用(免费工具!!!!)