本文须要您了解ASP.NET Core Web API 和 xUnit的相关知识.html
这里有xUnit的介绍: http://www.javashuo.com/article/p-rqwpmfqu-gm.htmlweb
ASP.NET Core集成测试官方文档: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.1数据库
测试金字塔, 但它只是一个指导性的概念.服务器
若是所单元测试是对一个组件进行隔离测试的话, 那么集成测试则是测试多个组件共同协做产生出期待的结果.网络
单元测试一般很快. 而集成测试则慢的多, 由于它须要不少配置, 而且可能依赖于外部的组件, 例如数据库, 网络, 文件等.函数
一般在一个项目里单元测试要比集成测试多不少.单元测试
单元测试一般依赖于mock的组件, 而集成测试则使用可运行的组件.测试
注意: 若是一个行为能够经过单元测试或集成测试来测试的话, 那么应该使用单元测试.优化
若是我想测试一个API Controller的Action, 我可能须要把这个项目运行起来, 等它跑起来, 发送请求并检验结果. 但这样作的话须要不少的配置工做, 而且很麻烦.ui
幸亏ASP.NET Core 提供了一个Microsoft.AspNetCore.TestHost 库, 使用它就无需单独去运行被测试系统了.
ASP.NET Core应用里, 咱们在Program.cs里建立WebHostBuilder, 并配置Kestrel Web服务器, 使用Startup类进行应用配置, 注册服务和中间件等. 最终在WebHostBuilder上使用Build()来建立WebHost的实例, 它能够用来在特定的URL和端口上运行并监听请求.
而这个TestHost库也使用了WebHostBuilder, 但它会本身把构建和运行web宿主的工做处理好, 也就是建立出了一个TestServer. TestServer不会在网络上进行监听, TestServer建立了一个名为Host的属性, 它的类型是IWebHost, 它能够用来处理内存里的请求对象. TestServer还会暴露一个HttpClient, 你能够用它来发送请求到被测试系统. 整个交互的过程都是在内存里完成的.
下图是被测试系统在生产环境和集成测试使用TestServer情形下的对比图:
图中:
当应用/被测试系统在生产环境运行的时候, 它使用Kestrel服务器, 监听HTTP请求, 并把它转化为HttpContext, 而后再传进ASP.NET Core的管道里.
TestServer不监听网络请求, 它使用HttpClient在内存里发送请求.
仔细看一下集成测试时使用TestServer的流图:
图中能够看到: 测试代码建立TestServer, TestServer建立HttpClient. 测试代码使用HttpClient发送请求接收响应. TestServer会转化请求并交给ASP.NET Core MVC/API 应用来处理.
首先须要为你的应用创建集成测试项目:
而后须要为项目添加Microsoft.AspNetCore.TestHost 这个库:
被测试的是这个Controller的GetRoot()所对应的行为, 而不仅是这个方法:
测试返回NoContent:
这里面按照以前讲的顺序, 建立IWebHostBuilder, 并用它建立TestServer, 而后TestServer建立HttpClient. 随后就使用httpClient发送请求, 返回结果, Assert便可.
须要注意的是, 在建立IWebHostBuilder的时候, 我使用了被测试系统的Startup类来进行配置, 并设定的环境是Development.
因为我这个项目能够看做是真实项目, 因此第一次运行该测试的时候, 测试是Fail的. 由于Startup里面有不少配置并不知足测试要求.
在我把IpRateLimiting, HttpsRedirection, Authentication, AuthorizeFilter等中间件/组件去掉以后, 测试才经过:
因此这就引出了一个问题, Startup里面的配置在开发时 和 测试时 以及 生产运行时 多是不太同样的.
个人Startup里面已经有不少代码了, 若是再进行环境判断, 那就会更乱了.
因此我决定为集成测试新创建一个Startup配置类:
ASP.NET Core项目也支持多环境的多个Startup配置类, 这部份内容请参考官方文档: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.1#environment-based-startup-class-and-methods.
而后修改代码, 使用这个测试专用的Startup便可:
测试会经过.
下面继续测试GetRoot方法的另外一个路径, 这个路径会用到RootController的依赖项IUrlHelper.
在集成测试里, 一般状况下是不使用Mocking技术的. 因此在这里我也不会mock IUrlHelper:
这里没有mock任何东西. 此外这个被测试的行为须要设置AcceptHeader.
测试会Pass的, TestServer帮我搞定了一切:
写了两个测试方法, 又引出了一个新的问题: 这两个方法有一些共同的设置代码, 这些设置可能会比较耗资源. 咱们能够把这些设置放在构造函数里面, 可是若是使用Theory并含有多个InlineData的话, 就会屡次运行构造函数里的设置代码, 可能会很是好资源(时间).
因此咱们应该考虑使用test fixture 这里有介绍: http://www.cnblogs.com/cgzl/p/8438019.html#share
并且咱们可使用WebApplicationFactory来构建TestServer, 使用WebApplicationFactory的好处是能够灵活的进行自定义配置.
要使用WebApplicationFactory, 须要添加库: Microsoft.AspNetCore.Mvc.Testing
使用该库以后, 代码应该以下:
可是却有一个问题, 这里我选择的时StartupIntegrationTest. 而电脑环境变量设置的是Development, 而调试测试以后发现走的是StartupDevelopment.
也许这是个Bug? 或者就是这样的意图. 那我暂时仍是使用原始的方法建立TestServer吧, 下面是我使用的代码:
创建一个TestServerFixture, 须要使用IDisposable来作清理工做:
而测试类注入该Fixture便可:
而后重跑测试, 会pass的:
我要测试这个Controller下CreateProduct方法对应的行为. 该Controller须要不少依赖项, 其中两个还须要使用数据库.
一般状况下集成测试里使用的数据库和生产环境中使用的数据库不一样, 在测试环境我更倾向于使用内存类数据库.
EF Core里面至少有两个内存类的数据库提供商:
在StartupIntegrationTest里, 我就使用InMemory吧;
下面是测试方法的代码:
这代码其实很简单, 就是对应着被测试的Controller方法作一些须要的设定便可, 例如Headers, Content-Type等等.
须要注意的是Content-Type是在Content的Header里设置, 而不是Request的Headers里设置, 不然会报乱用Header的错.
该测试会pass:
最后针对该行为再作一个Model验证失败的测试:
没什么不一样, 就是model的Name属性超长了.
这个测试一样会经过:
集成测试就简单介绍这些.......