如何在单元测试中对HttpWebResponse.GetResponseAsync()进行模拟?

3

我被指派为一个应用程序编写单元测试,但该应用程序不允许我进行修改。我想要对这个方法进行单元测试,该方法调用如下:

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()'已被弃用:'该API支持.NET Framework基础架构,不应直接从您的代码中使用。'
我知道这种类型已经过时,现在应该使用其他方法,但是我正在尝试测试的旧代码不允许我这么做。我查看了许多有关此主题的问题,但没有一个能回答这个问题。
2个回答

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

简单总结一下这部分内容:在我看来,对于通用情况,使用代码织入工具(MsFakes)是处理这种情况的正确方法。(我在下一节中会更详细地解释)

我认为你有四种选项来创建HttpWebResponse的新实例:

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

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

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

4. 使用代码织入工具(就像你已经使用过的...)例如Msfakes、Typemock Isolator等。

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

1. 反射:

HttpWebResponse有3个构造函数;(公共, 内部, 受保护)

要使用内部\公共构造函数,您将需要使用反射,但在大多数情况下,它不会直接奏效,然后您将不得不违反一些UT规则(小型、快速等等...)

反射将直接奏效的两种情况是:

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

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

虽然第一个是一个简单的情况(如果这是您的情况,那么您应该使用反射),但第二个是UT上下文中的不良实践;您的UTs将不会是小型/可读/可维护的,执行时间将增加,Microsoft可能会进行一些重构,这可能会破坏您的UTs。

对于受保护的构造函数,您应该通过继承调用它(编译时与运行时...)。
2. 继承:
受保护的 C'tor 也有过时属性,但属性 IsError 被设置为 false,这允许您继承此类,然后您将能够更改方法/属性的行为; 虚拟 - 覆盖,非虚拟 - 只有在您访问类成员(或反射)时才能使用。
这种选择的主要缺点是您需要生成的代码量及其复杂性。
3. 使用基于代理的框架:
在幕后,这些工具使用反射基于类创建一个继承实例(结合选项 1 和 2)。这些工具具有一些内置方法,可使您的虚假代码变得更小/可读/易于维护。
缺点:(我不会指出所有基于代理的工具的缺点...)
1. 您仍然无法更改非虚拟方法的行为。 2. 您无法访问实例成员。
这两个问题可以通过使用代码编织工具来解决。

4. 代码编织工具:

这些工具可以让你做几乎任何事情,这也是为什么这些工具在通用情况下最好的原因(我可以用一句话概括主要缺点——伴随着强大的力量而来的是巨大的责任...)。对于你的情况,它们提供了最佳解决方案;由于 HttpWebResponse 具有非虚拟方法且你不想重构代码,因此这是你的正确解决方案。

然而,Msfakes 不提供额外的 UT 方法/功能(例如:AssertsWasCalled、计数等),因此,除非你打算使用其他工具替换该工具,否则我认为你应该将其与代理基础工具结合使用(免费工具!!!)。


无论我尝试什么,都无法生成ShimXXX类。我正在尝试。 - Ziggler
我通过简单地关闭我的VS 2019,打开,清理项目,重新构建项目来解决了我的问题。 - Ziggler

1

您的问题与Shim无关。使用反射来绕过过时的问题。

HttpWebResponse toRet = Activator.CreateInstance<HttpWebResponse>();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接