Integration testing

集成测试用来确保app的不同模块之间可以正确的一起工作。ASP.NET Core提供单元测试框架和内建的测试网络服务来支持集成测试,并且测试网络服务不需要网络开销就可以处理请求。

View or download sample code

Introduction to integration testing

不像单元测试,集成测试通常包含app的基础设施问题,比如,数据库,文件系统,网络资源,网络请求和响应。单元测试使用假的或模拟的对象来代替这些问题,但是集成测试的目的是确认系统在这些基础设施之上是否能正常的运行。

集成测试,运行更大量的代码段,并且因为他们依赖于基础设施元素,往往比单元测试慢几个数量级。因此,限制编写的集成测试数量是个好主意,尤其是当你可以使用单元测试来测试相同的行为。

Note

如果一些行为可以使用单元测试或集成测试来测试,请使用单元测试,因为它几乎总是更快。 您可能有许多或几百个单元测试与许多不同的输入,但只是一些覆盖最重要的场景的集成测试。

不需要写太多的集成测试来确认你能够写入和读取数据到数据库。不需要列举每一个可能的组合,只需要确认app可以一起正常工作就可以。

Integration testing ASP.NET Core

要设置好集成测试并运行,需要创建一个测试项目,添加需要测试的项目的ASP.NET Core网站应用,并安装一个测试运行器。这个过程在Unit testing有描述。

Note

将单元测试和集成测试分成不同的项目,将帮助你避免意外的将基础架构问题引入单元测试,并且可以很容易的选择启动哪一组测试。

The Test Host

ASP.NET Core包含一个测试主机,可以添加到集成测试项目,并用于托管ASP.NET Core应用程序,提供测试请求,而不需要真正的Web主机。

所提供的示例包括已配置为使用xUnit和测试主机的集成测试项目。 它使用Microsoft.AspNetCore.TestHost NuGet包。

一旦Microsoft.AspNetCore.TestHost包包含在项目中,您就可以在测试中创建和配置TestServer。 以下测试显示如何验证对网站根目录发出的请求返回“Hello World!”。 并应成功运行由Visual Studio创建的ASP.NET Core的默认空Web模板。

  1. public class PrimeWebDefaultRequestShould
  2. {
  3. private readonly TestServer _server;
  4. private readonly HttpClient _client;
  5. public PrimeWebDefaultRequestShould()
  6. {
  7. // Arrange
  8. _server = new TestServer(new WebHostBuilder()
  9. .UseStartup<Startup>());
  10. _client = _server.CreateClient();
  11. }
  12. [Fact]
  13. public async Task ReturnHelloWorld()
  14. {
  15. // Act
  16. var response = await _client.GetAsync("/");
  17. response.EnsureSuccessStatusCode();
  18. var responseString = await response.Content.ReadAsStringAsync();
  19. // Assert
  20. Assert.Equal("Hello World!",
  21. responseString);
  22. }
  23. }

此测试使用Arrange-Act-Assert模式。 Arrange步骤在构造函数中完成,它创建一个TestServer的实例。 一个配置好的WebHostBuilder将用于创建TestHost; 在此示例中,来自被测系统(SUT)的Startup类的Configure方法被传递给WebHostBuilder。 此方法将用于配置TestServer的请求管道,与SUT服务器的配置方式相同。

在测试的Act部分,向TestServer实例发出“/”路径的请求,并将响应读回到字符串中。 此字符串与预期的“Hello World!”字符串进行比较。 如果它们匹配,则测试通过; 否则失败。

现在,您可以添加一些额外的集成测试,以确认素数检查功能能在app中正常运行。

  1. public class PrimeWebCheckPrimeShould
  2. {
  3. private readonly TestServer _server;
  4. private readonly HttpClient _client;
  5. public PrimeWebCheckPrimeShould()
  6. {
  7. // Arrange
  8. _server = new TestServer(new WebHostBuilder()
  9. .UseStartup<Startup>());
  10. _client = _server.CreateClient();
  11. }
  12. private async Task<string> GetCheckPrimeResponseString(
  13. string querystring = "")
  14. {
  15. var request = "/checkprime";
  16. if(!string.IsNullOrEmpty(querystring))
  17. {
  18. request += "?" + querystring;
  19. }
  20. var response = await _client.GetAsync(request);
  21. response.EnsureSuccessStatusCode();
  22. return await response.Content.ReadAsStringAsync();
  23. }
  24. [Fact]
  25. public async Task ReturnInstructionsGivenEmptyQueryString()
  26. {
  27. // Act
  28. var responseString = await GetCheckPrimeResponseString();
  29. // Assert
  30. Assert.Equal("Pass in a number to check in the form /checkprime?5",
  31. responseString);
  32. }
  33. [Fact]
  34. public async Task ReturnPrimeGiven5()
  35. {
  36. // Act
  37. var responseString = await GetCheckPrimeResponseString("5");
  38. // Assert
  39. Assert.Equal("5 is prime!",
  40. responseString);
  41. }
  42. [Fact]
  43. public async Task ReturnNotPrimeGiven6()
  44. {
  45. // Act
  46. var responseString = await GetCheckPrimeResponseString("6");
  47. // Assert
  48. Assert.Equal("6 is NOT prime!",
  49. responseString);
  50. }
  51. }

注意!!!上面的集成测试不是用来检查素数检查器的正确性的,而是检查app是否像你期望的那样运行,因为素数检查器的正确性是单元测试覆盖的范围。

Refactoring to use middleware

重构是改变应用程序代码以改进其设计而不改变其行为的过程。 理想情况下,有一套已经通过的测试,可以帮助确保系统的行为在更改之前和之后保持不变。 查看在Web应用程序的Configure方法中实现主要检查逻辑的方式,您将看到:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  2. {
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. app.Run(async (context) =>
  8. {
  9. if (context.Request.Path.Value.Contains("checkprime"))
  10. {
  11. int numberToCheck;
  12. try
  13. {
  14. numberToCheck = int.Parse(context.Request.QueryString.Value.Replace("?", ""));
  15. var primeService = new PrimeService();
  16. if (primeService.IsPrime(numberToCheck))
  17. {
  18. await context.Response.WriteAsync($"{numberToCheck} is prime!");
  19. }
  20. else
  21. {
  22. await context.Response.WriteAsync($"{numberToCheck} is NOT prime!");
  23. }
  24. }
  25. catch
  26. {
  27. await context.Response.WriteAsync("Pass in a number to check in the form /checkprime?5");
  28. }
  29. }
  30. else
  31. {
  32. await context.Response.WriteAsync("Hello World!");
  33. }
  34. });
  35. }

这个代码可以正常运行,但它远不是你想要实现的那样,即使这么简单。 想象一下,如果你需要在每次添加另一个URL路径时都添加这么多的代码,Configure方法会是什么样子?

一个选项是考虑在app中添加MVC,创建一个控制器来处理素数的检查。然而,假设你现在不需要MVC的其他功能,因为它太大了。

可以利用ASP.NET Core middleware,它可以让我们再自己的类中封装素数检查逻辑,并让Configure方法更好的分离关注点。

您希望允许中间件使用路径作为参数,因此中间件类在其构造函数中需要一个RequestDelegate和一个PrimeCheckerOptions实例。 如果请求的路径与此中间件配置的不匹配,您只需调用链中的下一个中间件,不做任何进一步操作。 配置中的其余实现代码现在位于Invoke方法中。

  1. using Microsoft.AspNetCore.Builder;
  2. using Microsoft.AspNetCore.Http;
  3. using PrimeWeb.Services;
  4. using System;
  5. using System.Threading.Tasks;
  6. namespace PrimeWeb.Middleware
  7. {
  8. public class PrimeCheckerMiddleware
  9. {
  10. private readonly RequestDelegate _next;
  11. private readonly PrimeCheckerOptions _options;
  12. private readonly PrimeService _primeService;
  13. public PrimeCheckerMiddleware(RequestDelegate next,
  14. PrimeCheckerOptions options,
  15. PrimeService primeService)
  16. {
  17. if (next == null)
  18. {
  19. throw new ArgumentNullException(nameof(next));
  20. }
  21. if (options == null)
  22. {
  23. throw new ArgumentNullException(nameof(options));
  24. }
  25. if (primeService == null)
  26. {
  27. throw new ArgumentNullException(nameof(primeService));
  28. }
  29. _next = next;
  30. _options = options;
  31. _primeService = primeService;
  32. }
  33. public async Task Invoke(HttpContext context)
  34. {
  35. var request = context.Request;
  36. if (!request.Path.HasValue ||
  37. request.Path != _options.Path)
  38. {
  39. await _next.Invoke(context);
  40. }
  41. else
  42. {
  43. int numberToCheck;
  44. if (int.TryParse(request.QueryString.Value.Replace("?", ""), out numberToCheck))
  45. {
  46. if (_primeService.IsPrime(numberToCheck))
  47. {
  48. await context.Response.WriteAsync($"{numberToCheck} is prime!");
  49. }
  50. else
  51. {
  52. await context.Response.WriteAsync($"{numberToCheck} is NOT prime!");
  53. }
  54. }
  55. else
  56. {
  57. await context.Response.WriteAsync($"Pass in a number to check in the form {_options.Path}?5");
  58. }
  59. }
  60. }
  61. }
  62. }

由于此中间件在其路径匹配时充当请求委派链中的端点,因此当此中间件处理请求时,不会调用_next.Invoke。

创建好了这个中间件和一些有用的扩展方法,配置他们更容易,重构的配置方法如下所示:

  1. public void Configure(IApplicationBuilder app,
  2. IHostingEnvironment env)
  3. {
  4. if (env.IsDevelopment())
  5. {
  6. app.UseDeveloperExceptionPage();
  7. }
  8. app.UsePrimeChecker();
  9. app.Run(async (context) =>
  10. {
  11. await context.Response.WriteAsync("Hello World!");
  12. });
  13. }

在重构之后,可以确信Web应用程序仍然像以前一样工作,因为您的集成测试全部通过。

Note

在完成重构和测试通过后,最好将更改提交给源代码控制服务。 如果你正在练习测试驱动开发,考虑添加Commit到你的Red-Green-Refactor循环,详见这里

Resources

Unit Testing

Middleware

Testing Controllers

Integration testing的更多相关文章

  1. 系统内部集成测试(System Integration Testing) SIT 用户验收测试(User Acceptance Testing)

    系统内部集成测试(System Integration Testing) SIT 用户验收测试(User Acceptance Testing) UAT SIT在前,UAT在后,UAT测完才可以上线

  2. Unit Testing, Integration Testing and Functional Testing

    转载自:https://codeutopia.net/blog/2015/04/11/what-are-unit-testing-integration-testing-and-functional- ...

  3. 谈谈集成测试(integration testing)

    对于软件开发来说,软件测试是一个几乎贯穿所有阶段的活动,所以测试的重要性毋庸置疑.不同开发组织如何在不同的产品研发阶段进行测试,也在很大程度上反映了其研发能力和质量控制能力.软件测试有很多类型,包括单 ...

  4. 学习笔记之Unit testing/Integration testing/dotnet test and xUnit

    source code https://github.com/haotang923/dotnet/tree/master/src Unit testing C# code in .NET Core u ...

  5. Rails 5 Test Prescriptions 第8章 Integration Testing with Capybara and Cucumber

    Capybara:  A complete reference is available atrubydoc.info. 集成测试就是把局部的程序组合起来测试. 端到端测试是一个特殊的集成测试,覆盖了 ...

  6. Rails 5 Test Prescriptions 第9章 Testing-JavaScript: Integration Testing,❌挂一个问题webpacker::helper

    使用Capybara进行JS的集成测试 谈论驱动 让测试通过 Webpack in Development Mode Js设计 是用户在网页上有好的体验的重要因素. 尽管如此,许多网页不测试JS. 部 ...

  7. 玩转单元测试之Testing Spring MVC Controllers

    玩转单元测试之 Testing Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html The Spri ...

  8. 单元测试unit test,集成测试integration test和功能测试functional test的区别

    以下内容转自 https://codeutopia.net/blog/2015/04/11/what-are-unit-testing-integration-testing-and-function ...

  9. 40. Testing Prev Part IV. Spring Boot features

    40. Testing Spring Boot provides a number of utilities and annotations to help when testing your app ...

随机推荐

  1. ActiveXObject

    只有IE浏览器才支持这个构造函数,可以用这个来判断,当前是否为IE浏览器 var isIE=!!window.ActiveXObject; 在IE的不同版本下,要创建XHR对象,也需要通过这个构造函数 ...

  2. GOPATH和GOROOT

    安装指定版本golang apt-get purge golang* //删除之前安装的文件 add-apt-repository ppa:evarlast/golang-1.8 apt-get up ...

  3. nrf51822微信开发2:[转]airkiss/airsync介绍

    "微信蓝牙"专题共分为8部分 1.airkiss/airsync介绍 2.eclipes的j2ee软件使用教程 3.微信公众号使用Dome(airkiss/airsync) 4.新 ...

  4. Linux学习-系统基本设定

    网络设定 (手动设定与 DHCP 自动取得) 网络其实是又可爱又麻烦的玩意儿,如果你是网络管理员,那么你必须要了解局域网络内的 IP, gateway, netmask 等参数,如果还想要连上 Int ...

  5. Linux任务计划、周期性任务执行

    Linux任务计划.周期性任务执行 周期性任务执行: cron 守护进程(crond):服务,不间断地运行于后台 # service crond {start|stop|status|restart} ...

  6. Phonegap环境配置和安装插件

    一:安装好jdk(配置好环境变量) 二:安装好Android SDK(配置好环境变量path F:\Android\android-sdk-windows\platform-tools;F:\Andr ...

  7. MiniProfiler监控调试MVC5以及EntityFramework6性能

    想要通过在MVC中view中直观的查看页面加载以及后台EF执行情况,可以通过MiniProfiler小工具来实现. 但是从网上搜索的相关信息要么是MVC4下的老版本的MiniProfiler,要么就是 ...

  8. Python虚拟机中的一般表达式(一)

    在Python虚拟机框架这一章中,我们通过PyEval_EvalFrameEx看到了Python虚拟机的整体框架.而这章开始,我们将了解Python虚拟机是如何完成对Python的一般表达式的执行,这 ...

  9. 基于百度OCR的图片文字识别

    先上图,有图有真相 首先在百度开通ORC服务,目前是免费的,普通识别每天50000次免费,非常棒! 百度文档:http://ai.baidu.com/docs#/OCR-API/top 下载百度SDK ...

  10. springboot-vue-自定义注解限制接口调用

    新建注解: /** * 想要权限拦截的接口就加上这个注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Doc ...