实战指南:使用 xUnit 和 ASP.NET Core 进行集成测试【完整教程】
引言
集成测试可在包含应用支持基础结构(如数据库、文件系统和网络)的级别上确保应用组件功能正常。 ASP.NET Core
通过将单元测试框架与测试 Web
主机和内存中测试服务器结合使用来支持集成测试。
简介
集成测试与单元测试相比,能够在更广泛的级别上评估应用的组件,确认多个组件一起工作以生成预期结果,包括数据库、文件系统、网络设备等组件。单元测试主要用于测试独立软件组件,如类方法,通常使用 fake
或 mock
对象。集成测试使用实际组件,需要更多代码和数据处理,运行时间更长。建议将集成测试限制在重要的基础结构方案上,若可用单元测试或集成测试测试行为,优先选择单元测试。集成测试中被测试的项目通常称为"SUT"
,用于指代要测试的应用。避免为每种数据库和文件系统交互排列编写集成测试,而是通过一组集中式读取、写入、更新和删除集成测试充分测试这些组件,使用单元测试测试与这些组件交互的方法逻辑,使用 fake
或 mock
对象可加快测试速度。
集成测试实战
我们在之前的章节中创建了Sample.Api
和Sample.Repository
的项目,现在我们对这个项目进行整体的集成测试,带大家来感受一下。
ASP.NET Core
中的集成测试需要以下内容:
- 测试项目用于包含和执行测试。 测试项目具有对
SUT
的引用。 - 测试项目为
SUT
创建测试Web主机
,并使用测试服务器客户端处理SUT
的请求和响应。 - 测试运行程序用于执行测试并报告测试结果。
集成测试后跟一系列事件,包括常规“排列”
、“操作”
和“断言”
测试步骤:
- 已配置
SUT
的Web
主机。 - 创建测试服务器客户端以向应用提交请求。
- 执行
“排列”
测试步骤:测试应用会准备请求。 - 执行
“操作
”`测试步骤:客户端提交请求并接收响应。 - 执行
“断言”
测试步骤:实际响应基于预期响应验证为通过或失败。 - 该过程会一直继续,直到执行了所有测试。
- 报告测试结果。
上面解释到了集成测试中被测试的项目通常称为SUT
。我们要测试的项目Sample.Api
既是我们的SUT
。
好了我们开始创建xUnit的单元测试项目
,并添加Sample.Api
的项目引用.
集成测试第一步
在我们的单元测试项目中安装Nuget
依赖
PM> NuGet\Install-Package Microsoft.AspNetCore.Mvc.Testing -Version 8.0.4
基础结构组件(如测试
Web 主机
和内存中测试服务器 (TestServer
))由Microsoft.AspNetCore.Mvc.Testing
包提供或管理。 使用此包可简化测试创建和执行。
Microsoft.AspNetCore.Mvc.Testing
包处理以下任务:
将依赖项文件 (.deps
) 从 SUT
复制到测试项目的 bin
目录中。
将内容根目录设置为 SUT
的项目根目录,以便可在执行测试时找到静态文件和页面/视图。
提供 WebApplicationFactory
类,以简化 SUT
在 TestServer
中的启动过程。
概念有点多,后续里面的概念会慢慢讲到。
我们知道Asp.Net Core
的Web
项目项目是借助Kestrel
启动,用集成测试的TestServer
正是代替了Kestrel
托管服务的启动,那我们要测试的项目就不需要单独被启动了。
什么是TestServer
?
TestServer 用于在集成测试中模拟和启动应用程序的主机环境。通过创建 TestServer
实例,可以在测试中模拟出一个运行中的应用程序实例,以便进行端到端的集成测试。
在
Microsoft.AspNetCore.Mvc.Testing
中已经默认集成了对TestServer
的支持所以,不需要额外进行配置。
SUT
环境?
如果未设置 SUT
的环境,则环境会默认为开发环境即 Development
。
向测试项目公开启动类Program
使用 WebApplicationFactory<TEntryPoint>
创建 TestServer
以进行集成测试。 TEntryPoint
是 SUT
的入口点类,通常是 Program.cs
。
有两种向测试项目公开 Program
的方法
- 在
Program.cs
添加部分类
var builder = WebApplication.CreateBuilder(args);
// ... Configure services, routes, etc.
app.Run();
+ public partial class Program { }
- 配置
MSBuild
在SUT
的csproj
文件下添加
<ItemGroup>
<Using Include="Sample.Api" />
<InternalsVisibleTo Include="dotNetParadise.IntegrationTest" />
</ItemGroup>
<Using Include="Sample.Api" />
:这个子元素指定了要在项目中使用的命名空间或程序集。在这里,Sample.Api
被包含在项目中,以便项目可以访问和使用该命名空间或程序集中的内容。<InternalsVisibleTo Include="dotNetParadise.IntegrationTest" />
:这个子元素用于将内部可见性(InternalsVisibleTo
)属性应用于项目,允许指定的程序集(在这里是dotNetParadise.IntegrationTest
)访问项目中的内部成员。这在单元测试或集成测试中非常有用,因为测试项目通常需要访问被测试项目的内部成员以进行更全面的测试。
相对来说更推荐使用第一种部分类的形式来对测试项目公开。
WebApplicationFactory
可以使用默认的WebApplicationFactory
和自定义的WebApplicationFactory
来进行集成测试
测试类实现一个类固定例程接口 (IClassFixture
),以指示类包含测试,并在类中的所有测试间提供共享对象实例。
来感受一下
使用默认 WebApplicationFactory 的基本测试
看一下如何使用
public class DefaultWebApplicationFactoryTest : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public DefaultWebApplicationFactoryTest(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetAll_Query_ReturnOkAndListStaff()
{
//Arrange
var httpClient = _factory.CreateClient();
//act
var response = await httpClient.GetAsync("/api/Staff");
//Assert
//校验状态码
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
//校验用户
var users = await response.Content.ReadFromJsonAsync<List<Staff>>();
Assert.NotNull(users);
}
[Fact]
public async Task GetConfig_WhenCalled_ReturnOk() {
//Arrange
var httpClient = _factory.CreateClient();
//act
var response = await httpClient.GetAsync("/GetConfig");
//Assert
//校验状态码
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
//校验用户
var config = await response.Content.ReadFromJsonAsync<string>();
Assert.NotNull(config);
}
}
看到我们的测试类继承了IClassFixture
来共享实例对象,并且泛型类型是默认的WebApplicationFactory<Program>
接下来在我们的SUT
即Sample.Api
的program
中打个断点来验证一下
看到了我们测试时默认的 WebApplicationFactory
使用默认配置启动应用程序主机,包括加载 appsettings.json
等配置文件。
在我们的appsettings.Development.json
中加了一个配置
{
"Config": "这里是appsettings.Development.json"
}
GetConfig_WhenCalled_ReturnOk
测试方法看下结果
正确的读到appsettings.Development.json
的内容了,从而可以得出我们上面的结论,如果未设置 SUT
的环境,则环境会默认为开发环境即 Development
。
从上面我们看到我们向SUT
发请求是调用的CreateClient()
:
CreateClient()
方法用于创建一个 HttpClient
实例,用于模拟客户端与 SUT
进行交互。通过这个 HttpClient
,测试代码可以发送 HTTP
请求到应用程序,并验证应用程序的响应。
总的来说默认的 WebApplicationFactory
提供了一种快速启动应用程序主机进行集成测试的方式,适用于简单的测试场景。
自定义 WebApplicationFactory
通过从 WebApplicationFactory<TEntryPoint>
来创建一个或多个自定义工厂,可以独立于测试类创建 Web
主机配置
我们来创建一个SampleApiWebAppFactory
的类,然后继承WebApplicationFactory<Program>
public class SampleApiWebAppFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) =>
{
});
builder.UseEnvironment("Production");
base.ConfigureWebHost(builder);
}
public HttpClient Client()
{
return CreateDefaultClient();
}
}
里面有Asp.Net Core
启动项配置,我们都可以在自定义的SampleApiWebAppFactory
进行重写, 自定义的 WebApplicationFactory
提供了一种灵活的方式来定制化应用程序主机的配置,并扩展功能以满足特定的测试需求。通过继承并重写 ConfigureWebHost
方法等,开发人员可以对应用程序主机进行自定义配置,包括添加新的服务、中间件或修改默认配置,从而在测试环境中模拟特定的场景或功能。
优势和功能扩展:
- 定制化配置: 自定义的
WebApplicationFactory
允许开发人员根据测试需求添加自定义配置,比如测试环境特定的服务、中间件或其他设置,以确保测试环境与实际生产环境保持一致或满足特定测试需求。 - 功能扩展: 通过重写
ConfigureWebHost
方法,开发人员可以扩展应用程序主机的功能,例如注册额外的服务、修改中间件管道、添加测试专用的配置等,从而更好地适应测试场景。
复杂性和维护:
- 定制化代码量增加: 自定义的
WebApplicationFactory
可能会包含更多的定制化代码,需要更多的理解和维护,但这样可以更好地控制应用程序主机的配置和功能。 - 更高的灵活性: 虽然需要更多的理解和维护,但自定义的
WebApplicationFactory
提供了更大的灵活性和定制性,可以满足更复杂的测试需求,并确保测试环境的准确性和一致性。
总的来说,通过自定义的 WebApplicationFactory
,开发人员可以根据具体的测试场景和需求定制化配置和功能,以确保在集成测试中能够模拟真实的应用程序环境,并进行更全面和准确的测试。这种方式允许开发人员更好地控制应用程序主机的设置,以适应不同的测试需求和场景。
SUT
的数据库上下文在 Program.cs
中注册。 测试应用的 builder.ConfigureServices
回调在执行应用的 Program.cs
代码之后执行。 若要将与应用数据库不同的数据库用于测试,必须在 builder.ConfigureServices
中替换应用的数据库上下文。
builder.ConfigureServices((context, services) =>
{
var descriptor = new ServiceDescriptor(
typeof(DbContextOptions<SampleDbContext>),
serviceProvider => DbContextFactory<SampleDbContext>(serviceProvider, (sp, o) =>
{
o.UseInMemoryDatabase("TestDB");
}),
ServiceLifetime.Scoped);
services.Replace(descriptor);
});
上面用到的DbContextFactory
方法
private static DbContextOptions<TContext> DbContextFactory<TContext>(IServiceProvider applicationServiceProvider,
Action<IServiceProvider, DbContextOptionsBuilder> optionsAction)
where TContext : DbContext
{
var builder = new DbContextOptionsBuilder<TContext>(
new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>()));
builder.UseApplicationServiceProvider(applicationServiceProvider);
optionsAction?.Invoke(applicationServiceProvider, builder);
return builder.Options;
}
来写个集成测试
public class SampleApiTest(SampleApiWebAppFactory factory) : IClassFixture<SampleApiWebAppFactory>
{
[Fact]
public async Task GetAll_Query_ReturnOkAndListStaff()
{
//Arrange
var httpClient = factory.CreateClient();
//act
var response = await httpClient.GetAsync("/api/Staff");
//Assert
//校验状态码
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
//校验用户
var users = await response.Content.ReadFromJsonAsync<List<Staff>>();
Assert.NotNull(users);
}
[Fact]
public async Task GetConfig_WhenCalled_ReturnOk()
{
//Arrange
var httpClient = factory.CreateClient();
//act
var response = await httpClient.GetAsync("/GetConfig");
//Assert
//校验状态码
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
//校验用户
var config = await response.Content.ReadFromJsonAsync<string>();
Assert.NotNull(config);
}
// 后面测试省略。。。。
}
最后
集成测试是确保应用组件在包含数据库、文件系统和网络等基础结构的级别上正常运行的重要方式。ASP.NET Core
通过结合单元测试框架、测试Web
主机和内存中测试服务器来支持集成测试。
在集成测试中,我们评估应用组件在更广泛的级别上的功能,验证多个组件一起工作以生成预期结果,包括数据库、文件系统、网络设备等。单元测试主要用于测试独立的软件组件,而集成测试需要使用实际组件,涉及更多的代码和数据处理,以及更长的运行时间。建议将集成测试限制在重要的基础结构方案上,优先选择单元测试或集成测试来测试行为。
在集成测试中,被测试的项目通常称为SUT
(System Under Test
),用于指代要测试的应用。避免为每种数据库和文件系统交互编写独立的集成测试,而是通过一组集中式的测试来全面测试这些组件,并使用单元测试来测试与这些组件交互的方法逻辑。
通过自定义的WebApplicationFactory
,可以根据测试需求定制化配置和功能,模拟真实的应用程序环境进行全面和准确的测试。自定义的WebApplicationFactory
提供了灵活性和定制性,满足复杂的测试需求,并确保测试环境的准确性。虽然自定义的WebApplicationFactory
可能需要更多的理解和维护,但能更好地适应不同的测试场景。
集成测试是确保应用程序正常运行的关键步骤,通过综合不同组件的功能来验证应用的整体表现,提高应用程序的质量和稳定性。
实战指南:使用 xUnit 和 ASP.NET Core 进行集成测试【完整教程】的更多相关文章
- 使用 xUnit 编写 ASP.NET Core 单元测试
还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊..NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP ...
- 使用 xUnit 编写 ASP.NET Core WebAPI单元测试
本文使用xUnit对ASP.NET Core WebAPI做单元测试,使用HttpClient的同步和异步请求,下面详细介绍xUnit的使用过程: 一.创建示例项目 模板为我们自动创建了一个Value ...
- 《ASP.NET Core应用开发入门教程》与《ASP.NET Core 应用开发项目实战》正式出版
“全书之写印,实系初稿.有时公私琐务猬集,每写一句,三搁其笔:有时兴会淋漓,走笔疾书,絮絮不休:有时意趣萧索,执笔木坐,草草而止.每写一段,自助覆阅,辄摇其首,觉有大不妥者,即贴补重书,故剪刀浆糊乃不 ...
- 【目录】ASP.NET Core 2.1 入门教程
ASP.NET Core 2.1 快速学习.入门系列教程,这个入门系列教程为了帮助大家快速上手ASP.NET Core. 本教程包含且不限于: 使用VS Code开发ASP.NET Core应用 AS ...
- 使用xunit对asp.net core webapi进行集成测试
新项目我们采用前后端分离,后端采用asp.net core webapi, 如何对后端代码进行自动化测试呢,有以下几种方案: 1. 单元测试,目前这个方案对我们来说难度很大,抛开时间的问题,单元测试对 ...
- 极简版ASP.NET Core学习路径及教程
绝承认这是一个七天速成教程,即使有这个效果,我也不愿意接受这个名字.嗯. 这个路径分为两块: 实践入门 理论延伸 有了ASP.NET以及C#的知识以及项目经验,我们几乎可以不再需要了解任何新的知识就开 ...
- ASP.NET Core中获取完整的URL(转载)
在之前的ASP.NET中,可以通过 Request.Url.AbsoluteUri 获取,但在ASP.NET Core没有这个实现,请问如何获取呢?方法一:先引用“using Microsoft.As ...
- ASP.NET Core获取请求完整的Url
在ASP.NET项目中获取请求完整的Url: 获取System.Web命名空间下的类名为HttpRequestBase的Url方法: /// <summary>在派生类中替代时,获取有关当 ...
- ASP.NET Core 2.1 Web API + Identity Server 4 + Angular 6 + Angular Material 实战小项目视频
视频简介 ASP.NET Core Web API + Angular 6的教学视频 我是后端开发人员, 前端的Angular部分讲的比较差一些, 可以直接看代码!!!! 这是一个小项目的实战视频, ...
- ASP.NET Core会议管理平台实战_汇总贴
ASP.NET Core会议管理平台实战 课程地址:https://ke.qq.com/course/389673?from=800004097#term_id=100464670 ASP.NET C ...
随机推荐
- confluence 破解系列
confluence 破解系列 目录 confluence 破解系列 前言 confluence 一. 安装数据库mysql 二 docker启动confluence 前言 confluence Co ...
- Lock wait timeout exceeded; try restarting transaction-Mysql报错
一.问题由来 现在在做一个小程序的后台,使用Java写的,数据库使用的Mysql,之前一直调试的时候都好好的,今天在调试的时候突然就报一个错: ### Error updating database. ...
- 摆脱鼠标系列 - vscode 新建终端 默认最大化显示
摆脱鼠标系列 - vscode 新建终端 默认最大化显示 实现 搜索 opens max 改成 always
- pdfz Vue-3-Cheat-Sheet-zh 官方要是能下载下来,我也就不放这里了。 VUE3 composition API cheat sheet
https://files.cnblogs.com/files/pengchenggang/Vue-3-Cheat-Sheet-zh.zip 官方下载地址:https://www.vuemastery ...
- Tomcat错误之java.lang.OutOfMemoryError:PermGen space解决方案
公司的站点是跑在Tomcat环境下的,运行一段时间后,有时会报这样的错误:java.lang.OutOfMemoryError: PermGen space 在网上查询了一下,大部分都说是jvm虚拟机 ...
- 【Django】HTML如何显示富文本内容
一.背景 我采用的前端样式是 LayUI,通过它的富文本编辑器保存内容到数据库后,遇到了一个回显到页面的问题 二.方案 在不考虑使用 Vue 的情况下,有一种简单的方式 <div id=&quo ...
- LOTO示波器的变长存储深度和分段存储
LOTO示波器的变长存储深度和分段存储 经常有客户咨询和不理解LOTO示波器的存储深度为什么是变长的,也表示对LOTO示波器的分段存储功能不理解,本文对LOTO示波器的存储机制做一次完整的梳理,帮助我 ...
- vue-router动态注册
来源 写路由时每新建一个路由都需要import一下或其他方式(如箭头函数import)很是麻烦,有麻烦就有需求,于是以下这篇文章就来了 吹水 要想动态注册路由,那么就需要制定规则,即每个路由有一定的规 ...
- top 命令解释
PID:进程ID USER:运行改进程的用户 PR:进程的优先级 NI:Nice值,进程的优先级修正值,负值表示高优先级,正值表示低优先级 VIRT:虚拟内存,进程使用的虚拟内存总量 RES:物理内存 ...
- 【IOT安全】ASA5520基本知识和配置
本文主要介绍ASA5520防火墙的基本知识和配置 环境搭建 Linux eveng 5.17.8-eve-ng-uksm-wg+ #1 SMP PREEMPT Mon May 16 10:08:59 ...