我如何填充 HttpWebResponse.GetResponseAsync() 进行单元测试?
How can I shim HttpWebResponse.GetResponseAsync() for unit tests?
我被指派为我无权修改的应用程序编写单元测试。我要进行单元测试的方法调用如下:
HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;
在这个项目中,我已经使用 Microsoft Fakes 进行了数千次其他测试,所以我想我也会在这里做同样的事情。在我看来,最简单和最干净的解决方案是填充 request.GetResponseAsync()
方法。然后我可以 return 一些虚假内容并确保该方法正确处理它而无需实际发出请求。
GetResponseAsync()
return 一个 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.'
我知道该类型已过时,我们现在应该使用不同的东西,但我正在尝试测试的遗留代码不允许我这么奢侈。我看过很多关于这个主题的问题,但 none 似乎回答了这个问题。
你的问题与Shim无关。使用反射绕过陈旧问题
HttpWebResponse toRet = Activator.CreateInstance<HttpWebResponse>();
我会将我的回答分成两部分;第一部分是您正在寻找的解决方案......在第二部分中,我将讨论你在 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. 使用基于代理的框架 例如;最小起订量、Rhinomocks 等
4. 使用代码编织工具(因为你已经使用过...) 例如; Msfakes、Typemock Isolator 等
还有一个选项:创建集成测试而不是 UT...
1.反思:
HttpWebResponse
有 3 个客户;(public, internal, protected)
要使用 internal\public C'tors 你必须使用反射,但在大多数情况下它不会开箱即用,然后你将不得不违反一些 UT 规则(小、快等等...)
反射开箱即用的 2 种情况是:
您没有在实例上调用任何有问题的 property/method 并且 SUT(被测对象)只是将此实例传递给他的依赖项之一
您将使用更多反射来初始化实例字段。
虽然第一个是简单的情况(如果这是你的情况那么你应该使用反射)第二个是 UT 上下文中的不良做法;您的 UT 不会 small/readable/maintainable,执行时间会增加,Microsoft 可能会进行一些重构,这可能会破坏您的 UT。
对于受保护的,您应该通过继承调用它(编译与运行时...)。
2。继承:
受保护的 C'tor 也有过时的属性但是 属性 IsError 设置为 false,这允许你继承这个 class 然后你就可以改变 methods/properties 的行为;虚拟 - 覆盖,非虚拟 - 仅当您有权访问 class 成员(或反射)
此选项的主要缺点是您必须生成的代码量及其复杂性。
3。使用基于代理的框架:
在幕后,这些工具使用反射创建基于 class(组合选项 1 和 2)的继承实例
这些工具有一些内置的方法来制作你的假代码 smaller/readable/maintainable.
缺点:(我不会指出基于代理的工具的所有缺点...)
您仍然无法更改非虚拟方法的行为。
您没有访问实例成员的权限。
这2个可以使用代码编织工具来解决。
4.代码编织工具:
这些工具几乎可以让你做任何事情,这就是为什么这些工具最适合一般情况的原因(我可以用一句话总结主要缺点 - 能力越大,责任越大......)。
在您的情况下,他们会为您提供最佳解决方案;
由于 HttpWebResponse
有一个非虚拟方法并且您不想重构您的代码,这对您来说是正确的解决方案。
但是 Msfakes 不会为你提供额外的 methods/functionality 用于 UT(AssertsWasCalled
,计数等等),所以除非你打算用其他工具替换该工具,IMO你应该将它与代理基础工具结合起来(免费工具!!!!)
我被指派为我无权修改的应用程序编写单元测试。我要进行单元测试的方法调用如下:
HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse;
在这个项目中,我已经使用 Microsoft Fakes 进行了数千次其他测试,所以我想我也会在这里做同样的事情。在我看来,最简单和最干净的解决方案是填充 request.GetResponseAsync()
方法。然后我可以 return 一些虚假内容并确保该方法正确处理它而无需实际发出请求。
GetResponseAsync()
return 一个 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.'
我知道该类型已过时,我们现在应该使用不同的东西,但我正在尝试测试的遗留代码不允许我这么奢侈。我看过很多关于这个主题的问题,但 none 似乎回答了这个问题。
你的问题与Shim无关。使用反射绕过陈旧问题
HttpWebResponse toRet = Activator.CreateInstance<HttpWebResponse>();
我会将我的回答分成两部分;第一部分是您正在寻找的解决方案......在第二部分中,我将讨论你在 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. 使用基于代理的框架 例如;最小起订量、Rhinomocks 等
4. 使用代码编织工具(因为你已经使用过...) 例如; Msfakes、Typemock Isolator 等
还有一个选项:创建集成测试而不是 UT...
1.反思:
HttpWebResponse
有 3 个客户;(public, internal, protected)
要使用 internal\public C'tors 你必须使用反射,但在大多数情况下它不会开箱即用,然后你将不得不违反一些 UT 规则(小、快等等...)
反射开箱即用的 2 种情况是:
您没有在实例上调用任何有问题的 property/method 并且 SUT(被测对象)只是将此实例传递给他的依赖项之一
您将使用更多反射来初始化实例字段。
虽然第一个是简单的情况(如果这是你的情况那么你应该使用反射)第二个是 UT 上下文中的不良做法;您的 UT 不会 small/readable/maintainable,执行时间会增加,Microsoft 可能会进行一些重构,这可能会破坏您的 UT。
对于受保护的,您应该通过继承调用它(编译与运行时...)。
2。继承:
受保护的 C'tor 也有过时的属性但是 属性 IsError 设置为 false,这允许你继承这个 class 然后你就可以改变 methods/properties 的行为;虚拟 - 覆盖,非虚拟 - 仅当您有权访问 class 成员(或反射)
此选项的主要缺点是您必须生成的代码量及其复杂性。
3。使用基于代理的框架:
在幕后,这些工具使用反射创建基于 class(组合选项 1 和 2)的继承实例 这些工具有一些内置的方法来制作你的假代码 smaller/readable/maintainable.
缺点:(我不会指出基于代理的工具的所有缺点...)
您仍然无法更改非虚拟方法的行为。
您没有访问实例成员的权限。
这2个可以使用代码编织工具来解决。
4.代码编织工具:
这些工具几乎可以让你做任何事情,这就是为什么这些工具最适合一般情况的原因(我可以用一句话总结主要缺点 - 能力越大,责任越大......)。
在您的情况下,他们会为您提供最佳解决方案;
由于 HttpWebResponse
有一个非虚拟方法并且您不想重构您的代码,这对您来说是正确的解决方案。
但是 Msfakes 不会为你提供额外的 methods/functionality 用于 UT(AssertsWasCalled
,计数等等),所以除非你打算用其他工具替换该工具,IMO你应该将它与代理基础工具结合起来(免费工具!!!!)