原文: 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. [APUE]进程控制(上)

    一.进程标识 进程ID 0是调度进程,常常被称为交换进程(swapper).该进程并不执行任何磁盘上的程序--它是内核的一部分,因此也被称为系统进程.进程ID 1是init进程,在自举(bootstr ...

  2. CORS详解[译]

    介绍 由于同源策略的缘故,以往我们跨域请求,会使用诸如JSON-P(不安全)或者代理(设置代理和维护繁琐)的方式.而跨源资源共享(Cross-Origin Resource Sharing)是一个W3 ...

  3. 终于等到你:CYQ.Data V5系列 (ORM数据层)最新版本开源了

    前言: 不要问我框架为什么从收费授权转到免费开源,人生没有那么多为什么,这些年我开源的东西并不少,虽然这个是最核心的,看淡了就也没什么了. 群里的网友:太平说: 记得一年前你开源另一个项目的时候我就说 ...

  4. nw.js桌面软件开发系列 第0.1节 HTML5和桌面软件开发的碰撞

    第0.1节 HTML5和桌面软件开发的碰撞 当我们谈论桌面软件开发技术的时候,你会想到什么?如果不对技术本身进行更为深入的探讨,在我的世界里,有这么多技术概念可以被罗列出来(请原谅我本质上是一个Win ...

  5. .NET 对接JAVA 使用Modulus,Exponent RSA 加密

    最近有一个工作是需要把数据用RSA发送给Java 虽然一开始标准公钥 net和Java  RSA填充的一些算法不一样 但是后来这个坑也补的差不多了 具体可以参考 http://www.cnblogs. ...

  6. ASP.NET Core的路由[3]:Router的创建者——RouteBuilder

    在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...

  7. CSS垂直居中的11种实现方式

    今天是邓呆呆球衣退役的日子,在这个颇具纪念意义的日子里我写下自己的第一篇博客,还望前辈们多多提携,多多指教! 接下来,就进入正文,来说说关于垂直居中的事.(以下这11种垂直居中的实现方式均为笔者在日常 ...

  8. IIC驱动移植在linux3.14.78上的实现和在linux2.6.29上实现对比(deep dive)

    首先说明下为什么写这篇文章,网上有许多博客也是介绍I2C驱动在linux上移植的实现,但是笔者认为他们相当一部分没有分清所写的驱动时的驱动模型,是基于device tree, 还是基于传统的Platf ...

  9. spring源码分析之@ImportSelector、@Import、ImportResource工作原理分析

    1. @importSelector定义: /** * Interface to be implemented by types that determine which @{@link Config ...

  10. 简记用ArcGIS处理某项目需求中数据的步骤

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1. 背景 项目需求涉及如下几个步骤: a.矢量化 b.获取范围内要素 ...