原文: 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. JavaScript自定义浏览器滚动条兼容IE、 火狐和chrome

    今天为大家分享一下我自己制作的浏览器滚动条,我们知道用css来自定义滚动条也是挺好的方式,css虽然能够改变chrome浏览器的滚动条样式可以自定义,css也能够改变IE浏览器滚动条的颜色.但是css ...

  2. 【探索】机器指令翻译成 JavaScript

    前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript ...

  3. Matlab 绘制三维立体图(以地质异常体为例)

    前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...

  4. [C#] 了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数

    了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数 目录 简介 特点 方法的参数 方法的返回值 与批处理交互的一个示例 简介 我们知道,新建一个控制台应用程序的时候,IDE 会同时创建 ...

  5. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  6. MVC通过路由实现URL重写

    public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Ro ...

  7. 如何将VCSA添加到微软域控环境,并且实现微软域账号登陆vCenter

    v:* { } o:* { } w:* { } .shape { } p.msonormal,li.msonormal,div.msonormal { margin: 0cm; margin-bott ...

  8. 你真的会玩SQL吗?之逻辑查询处理阶段

    你真的会玩SQL吗?系列目录 你真的会玩SQL吗?之逻辑查询处理阶段 你真的会玩SQL吗?和平大使 内连接.外连接 你真的会玩SQL吗?三范式.数据完整性 你真的会玩SQL吗?查询指定节点及其所有父节 ...

  9. 基于window7+caffe实现图像艺术风格转换style-transfer

    这个是在去年微博里面非常流行的,在git_hub上的代码是https://github.com/fzliu/style-transfer 比如这是梵高的画 这是你自己的照片 然后你想生成这样 怎么实现 ...

  10. 浏览器的兼容模式下的button中文字垂直方向不居中显示

    <button style="cursor:pointer;vertical-align: middle;" >删除</button> 这时候垂直不居中. ...