ASP.NET Core 中文文档 第五章 测试(5.2)集成测试
原文: Integration Testing
作者: Steve Smith
翻译: 王健
校对: 孟帅洋(书缘)
集成测试确保应用程序的组件组装在一起时正常工作。 ASP.NET Core支持使用单元测试框架和可用于处理没有网络开销请求的内置测试的网络主机集成测试。
章节:
集成测试介绍
集成测试验证应用程序不同的部位是否正确地组装在一起。不像单元测试,集成测试经常涉及到应用基础设施,如数据库,文件系统,网络资源或网页的请求和响应。单元测试用伪造或模拟对象代替这些问题,但集成测试的目的是为了确认该系统与这些系统的预期运行一致。
集成测试,因为它们执行较大的代码段,并且它们依赖于基础结构组件,往往要比单元测试慢几个数量级。因此,限制你写多少集成测试,特别是如果你可以测试与单元测试相同的行为,是一个不错的选择。
提示
如果某些行为可以使用一个单元测试或集成测试进行测试,优先单元测试,因为这几乎总是会更快的。你可能有几十或几百个单元测试有许多不同的输入,而只是一个集成测试覆盖了最重要的屈指可数的场景。
在您自己的方法中测试逻辑通常是单元测试的范畴。测试您的应用程序在它的框架内(例如ASP.NET),或是与一个数据库是否正常运行,是集成测试的工作。它并不需要太多的集成测试,以确认你能写一行,然后从数据库中读取一行。你并不需要测试的数据访问代码每一个可能的排列——您仅需要充足的测试来给您信心认为您的应用程序能够运行良好。
ASP.NET 集成测试
要建立运行集成测试,你需要创建一个测试项目,请参考ASP.NET的Web项目,并安装测试器。此过程在单元测试中有更详细的说明,为您命名您的测试和测试类提供了建议。
提示
单独的单元测试和集成测试使用不同的项目。这有助于确保您不小心将基础设施问题引入到您的单元测试中,让您轻松选择运行所有的测试,或是一组或其他。
测试宿主
ASP.NET包括可添加到集成测试项目的测试宿主和用于托管ASP.NET应用程序,用于处理测试请求,而不需要一个真实的虚拟宿主。所提供的示例包括被配置为使用 xUnit 的集成测试项目和测试主机,您可以从 project.json 文件中进行查看。
"dependencies": {
"PrimeWeb": "1.0.0",
"xunit": "2.1.0",
"dotnet-test-xunit": "1.0.0-rc2-build10025",
"Microsoft.AspNetCore.TestHost": "1.0.0"
},
当Microsoft.AspNet.TestHost包被包含在项目中,您将能够在您的测试中创建和配置TESTSERVER。下面的测试演示了如何验证一个对网站的根节点提出了请求并返回的“Hello World!”,并且应该利用Visual Studio中创建的默认ASP.NET空Web模板中成功运行。
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebDefaultRequestShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Hello World!",
responseString);
}
这些测试使用安排-执行-断言的模型,但是在这种情况下,所有的安排步骤都在构造器中完成了,它创建了一个 TestServer 的实例。当您创建 TestServer 时,有好几种不同的方式来配置它;在这个示例中,我们从被测试的系统(SUT)的 Startup 类中的 Configure 方法进行设置。这种方法可用于配置TestServer请求管道,与如何配置SUT服务器相同。
在测试的行动部分,发起一个对 TestServer 实例的“/”路径的请求,并且响应读回字符串。这个字符串将与预期的字符串"Hello World!"进行对比。如果匹配,测试通过,否则测试失败。
现在我们可以添加一些附加的集成测试,来确认通过web应用程序的素数检测功能性工作:
public class PrimeWebCheckPrimeShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebCheckPrimeShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
private async Task<string> GetCheckPrimeResponseString(
string querystring = "")
{
var request = "/checkprime";
if(!string.IsNullOrEmpty(querystring))
{
request += "?" + querystring;
}
var response = await _client.GetAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
[Fact]
public async Task ReturnInstructionsGivenEmptyQueryString()
{
// Act
var responseString = await GetCheckPrimeResponseString();
// Assert
Assert.Equal("Pass in a number to check in the form /checkprime?5",
responseString);
}
[Fact]
public async Task ReturnPrimeGiven5()
{
// Act
var responseString = await GetCheckPrimeResponseString("5");
// Assert
Assert.Equal("5 is prime!",
responseString);
}
[Fact]
public async Task ReturnNotPrimeGiven6()
{
// Act
var responseString = await GetCheckPrimeResponseString("6");
// Assert
Assert.Equal("6 is NOT prime!",
responseString);
}
}
需要注意的是,我们并不是想使用这些测试用例来测试质数检查程序的正确性,而是确认Web应用程序在我们期待的事情。我们已经有对 PrimeService 充满信心的单元测试覆盖率,您可以在这里看到:

注意
您可以从单元测试的文章中了解更多关于单元测试的内容。
现在,我们有一组通过的测试,是一个好的机会来考虑我们是否对设计应用程序的方案感到满意了。如果我们发现任何 代码异味,这将是一个重构应用程序来改善设计的好时机。
使用中间件重构
重构是改变一个应用程序的代码,以提高其设计而不改变其行为的过程。当有一套通过的测试,重构将理想的进行,因为这些有助于确保系统的行为在重构之前和之后保持不变。看看素数检测逻辑在我们的web应用程序中的实现方式,我们发现:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run(async (context) =>
{
if (context.Request.Path.Value.Contains("checkprime"))
{
int numberToCheck;
try
{
numberToCheck = int.Parse(context.Request.QueryString.Value.Replace("?",""));
var primeService = new PrimeService();
if (primeService.IsPrime(numberToCheck))
{
await context.Response.WriteAsync(numberToCheck + " is prime!");
}
else
{
await context.Response.WriteAsync(numberToCheck + " is NOT prime!");
}
}
catch
{
await context.Response.WriteAsync("Pass in a number to check in the form /checkprime?5");
}
}
else
{
await context.Response.WriteAsync("Hello World!");
}
});
}
这段代码能正确运行,但远远不是我们想在ASP.NET应用中实现这种功能的方式,即使和这段代码一样简单。想象一下,如果我们在每次添加另一个URL终结点时,我们需要在它的代码中添加那么多代码,Configure 方法会是什么样子呢!
一个选择是,可以考虑在应用程序中添加 MVC ,并创建一个控制器来处理素数检测。然而,假设我们目前不需要任何其它MVC的功能,这是一个有点矫枉过正。
然而,我们可以利用ASP.NET Core 中间件 的优势,可以帮助我们在它自己的类中封装素数检测的逻辑,并且在 Configure 方法中实现更好的 关注点分离 。
我们想让中间件使用的路径被指定为一个参数,所以中间件类在他的构造方法中预留了一个 RequestDelegate 和一个 PrimeCheckerOptions 实例。如果请求的路径与中间件期望的配置不匹配,我们只需要调用链表中的下一个中间件,并不做进一步处理。其余的在 Configure 中的实现代码,现在在 Invoke 方法中了。
注意
由于我们的中间件取决于PrimeService服务,我们也通过构造函数请求该服务的实例。该框架通过依赖注入来提供这项服务,查看 dependency-injection,假设已经进行了配置(例如在ConfigureServices中)。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using PrimeWeb.Services;
using System;
using System.Threading.Tasks;
namespace PrimeWeb.Middleware
{
public class PrimeCheckerMiddleware
{
private readonly RequestDelegate _next;
private readonly PrimeCheckerOptions _options;
private readonly PrimeService _primeService;
public PrimeCheckerMiddleware(RequestDelegate next,
PrimeCheckerOptions options,
PrimeService primeService)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
if (primeService == null)
{
throw new ArgumentNullException(nameof(primeService));
}
_next = next;
_options = options;
_primeService = primeService;
}
public async Task Invoke(HttpContext context)
{
var request = context.Request;
if (!request.Path.HasValue ||
request.Path != _options.Path)
{
await _next.Invoke(context);
}
else
{
int numberToCheck;
if (int.TryParse(request.QueryString.Value.Replace("?", ""), out numberToCheck))
{
if (_primeService.IsPrime(numberToCheck))
{
await context.Response.WriteAsync($"{numberToCheck} is prime!");
}
else
{
await context.Response.WriteAsync($"{numberToCheck} is NOT prime!");
}
}
else
{
await context.Response.WriteAsync($"Pass in a number to check in the form {_options.Path}?5");
}
}
}
}
}
注意
由于这个中间件作为请求委托链的一个endpoint,当它的路径匹配时,在这种情况下这个中间件处理请求时并没有调用_next.Invoke
有了合适的中间件和一写有用的扩展方法,使配置更加容易。重构过的 Configure 方法看起来像这样:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UsePrimeChecker();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
在这重构之后,我们有信心Web应用程序仍然像之前一样工作,因为我们的集成测试都是通过的。
提示
当您完成重构并且所有测试都通过后,提交您的变更到源代码管理中,是一个好的主意。如果您正尝试测试驱动开发,考虑提交代码到你的 Red-Green-Refacotr 循环中。
总结
集成测试提供了比单元测试更高层次的验证。它测试应用程序的基础设施和应用程序的不同部分如何一起工作。 ASP.NET Core 有很大可测试性,并附带了 TestServer 这使得为Web服务器endpoint连布置集成测试变得非常简单。
附加的资源
ASP.NET Core 中文文档 第五章 测试(5.2)集成测试的更多相关文章
- ASP.NET Core 中文文档 第三章 原理(6)全球化与本地化
原文:Globalization and localization 作者:Rick Anderson.Damien Bowden.Bart Calixto.Nadeem Afana 翻译:谢炀(Kil ...
- ASP.NET Core 中文文档 第三章 原理(1)应用程序启动
原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...
- ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...
- ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由
原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...
- ASP.NET Core 中文文档 第三章 原理(2)中间件
原文:Middleware 作者:Steve Smith.Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:许登洋(Seay) 章节: 什么是中间件 用 IApplicationBu ...
- ASP.NET Core 中文文档 第三章 原理(3)静态文件处理
原文:Working with Static Files 作者:Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay).孟帅洋(书缘) 静态文 ...
- ASP.NET Core 中文文档 第四章 MVC(3.6.1 )Tag Helpers 介绍
原文:Introduction to Tag Helpers 作者:Rick Anderson 翻译:刘浩杨 校对:高嵩(Jack) 什么是 Tag Helpers? Tag Helpers 提供了什 ...
- ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入
原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...
- ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)
原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...
随机推荐
- javascript的api设计原则
前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...
- IE的F12开发人员工具不显示问题
按下F12之后,开发人员工具在桌面上看不到,但是任务栏里有显示.将鼠标放在任务栏的开发人员工具上,出现一片透明的区域,选中之后却出不来.将鼠标移动到开发人员工具的缩略图上,右键-最大化,工具就全屏出现 ...
- [C#] C# 知识回顾 - 序列化
C# 知识回顾 - 序列化 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902005.html 目录 序列化的含义 通过序列化保存对象数据 众 ...
- 一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息.由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量. 在.NET项目中如果用户提出了相关文 ...
- 使用Expression实现数据的任意字段过滤(1)
在项目常常要和数据表格打交道. 现在BS的通常做法都是前端用一个js的Grid控件, 然后通过ajax的方式从后台加载数据, 然后将数据和Grid绑定. 数据往往不是一页可以显示完的, 所以要加分页: ...
- 【C#公共帮助类】 ToolsHelper帮助类
这个帮助类,目前我们只用到了两个,我就先更新这两个,后面有用到的,我会继续更新这个Helper帮助类 在Tools.cs中 有很多方法 跟Utils里是重复的,而且Utils里的方法更加新一点,大家可 ...
- 微信小程序体验(1):携程酒店机票火车票
在 12 月 28 日微信公开课上,张小龙对微信小程序的形态进行了阐释,小程序有四个特定:无需安装.触手可及.用完即走.无需卸载. 由于携程这种订酒店.火车票和机票等工具性质非常强的服务,非常符合张小 ...
- Spark-shell和Spark-Submit的使用
Spark-shell有两种使用方式: 1:直接Spark-shell 会启动一个SparkSubmit进程来模拟Spark运行环境,是一个单机版的. 2:Spark-shell --master S ...
- Java版本:识别Json字符串并分隔成Map集合
前言: 最近又看了点Java的知识,于是想着把CYQ.Data V5迁移到Java版本. 过程发现坑很多,理论上看大部分很相似,实践上代码写起来发现大部分都要重新思考方案. 遇到的C#转Java的一些 ...
- 吸顶大法 -- UWP中的工具栏吸顶的实现方式之一
如果一个页面中有很长的列表/内容,很多应用都会在用户向下滚动时隐藏页面的头,给用户留出更多的阅读空间,同时提供一个方便的吸顶工具栏,比如淘宝中的店铺页面. 下面是一个比较简单的实现,如果有同学有更好的 ...