原文: 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)集成测试的更多相关文章

  1. ASP.NET Core 中文文档 第三章 原理(6)全球化与本地化

    原文:Globalization and localization 作者:Rick Anderson.Damien Bowden.Bart Calixto.Nadeem Afana 翻译:谢炀(Kil ...

  2. ASP.NET Core 中文文档 第三章 原理(1)应用程序启动

    原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...

  3. ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态

    原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...

  4. ASP.NET Core 中文文档 第四章 MVC(4.2)控制器操作的路由

    原文:Routing to Controller Actions 作者:Ryan Nowak.Rick Anderson 翻译:娄宇(Lyrics) 校对:何镇汐.姚阿勇(Dr.Yao) ASP.NE ...

  5. ASP.NET Core 中文文档 第三章 原理(2)中间件

    原文:Middleware 作者:Steve Smith.Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:许登洋(Seay) 章节: 什么是中间件 用 IApplicationBu ...

  6. ASP.NET Core 中文文档 第三章 原理(3)静态文件处理

    原文:Working with Static Files 作者:Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay).孟帅洋(书缘) 静态文 ...

  7. ASP.NET Core 中文文档 第四章 MVC(3.6.1 )Tag Helpers 介绍

    原文:Introduction to Tag Helpers 作者:Rick Anderson 翻译:刘浩杨 校对:高嵩(Jack) 什么是 Tag Helpers? Tag Helpers 提供了什 ...

  8. ASP.NET Core 中文文档 第四章 MVC(3.8)视图中的依赖注入

    原文:Dependency injection into views 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:孟帅洋(书缘) ASP.NET Core 支持在视图中使用 依赖 ...

  9. ASP.NET Core 中文文档 第四章 MVC(4.6)Areas(区域)

    原文:Areas 作者:Dhananjay Kumar 和 Rick Anderson 翻译:耿晓亮(Blue) 校对:许登洋(Seay) Areas 是 ASP.NET MVC 用来将相关功能组织成 ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(6):方法参数

    本文老周就给大伙伴们介绍一下方法参数代码的生成. 在开始之前,先补充一下上一篇烂文的内容.在上一篇文章中,老周检讨了 MemberAttributes 枚举的用法,老周此前误以为该枚举不能进行按位操作 ...

  2. Taurus.MVC 2.2 开源发布:WebAPI 功能增强(请求跨域及Json转换)

    背景: 1:有用户反馈了关于跨域请求的问题. 2:有用户反馈了参数获取的问题. 3:JsonHelper的增强. 在综合上面的条件下,有了2.2版本的更新,也因此写了此文. 开源地址: https:/ ...

  3. JavaScript性能优化

    如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...

  4. C语言 · 矩形面积交

    问题描述 平面上有两个矩形,它们的边平行于直角坐标系的X轴或Y轴.对于每个矩形,我们给出它的一对相对顶点的坐标,请你编程算出两个矩形的交的面积. 输入格式 输入仅包含两行,每行描述一个矩形. 在每行中 ...

  5. 【初学python】使用python连接mysql数据查询结果并显示

    因为测试工作经常需要与后台数据库进行数据比较和统计,所以采用python编写连接数据库脚本方便测试,提高工作效率,脚本如下(python连接mysql需要引入第三方库MySQLdb,百度下载安装) # ...

  6. 一起学微软Power BI系列-使用技巧(1)连接Oracle与Mysql数据库

    说起Oracle数据库,以前没用过Oracle不知道,但是这1年用Oracle后,发现真的是想狂吐槽,特别是那个.NET驱动和链接字符串,特别奇葩.总归是和其他数据库不一样,标新立异,不知道为何.另外 ...

  7. mac下安装及配置tomcat

    mac下的软件不像windows下的程序那样写注册表,对于tomcat的安装来说,在mac下是名符其实的绿色软件,具体操作如下: 1.到 apache官方主页 下载完整 tar.gz文件包.(没有专门 ...

  8. PHP之Memcache缓存详解

         Mem:memory缩写(内存):内存缓存 1.  断电或者重启服务器内存数据即消失,即临时数据: Memcache默认端口:11211 存入方式:key=>>value    ...

  9. 实现php连接memcached

    准备工作: 实现lnmp环境 给php添加模块,so库 下载扩展包:memcache-2.2.5.tgz wget http://pecl.php.net/get/memcache-2.2.5.tgz

  10. 端盘子的服务生到月薪一万五的IT精英,你能相信吗

    一直以来,我都觉得自己不是一个有故事的人. 以前的我,是个乖宝宝,对父母言听计从,特别内向,甚至一度感觉到自卑.不上学之后,我干过送货员,去工地除泥搬砖,当过油漆工,去过工厂,还去饭店当过端盘子的服务 ...