引言

集成测试可在包含应用支持基础结构(如数据库、文件系统和网络)的级别上确保应用组件功能正常。 ASP.NET Core 通过将单元测试框架与测试 Web 主机和内存中测试服务器结合使用来支持集成测试。

简介

集成测试与单元测试相比,能够在更广泛的级别上评估应用的组件,确认多个组件一起工作以生成预期结果,包括数据库、文件系统、网络设备等组件。单元测试主要用于测试独立软件组件,如类方法,通常使用 fakemock 对象。集成测试使用实际组件,需要更多代码和数据处理,运行时间更长。建议将集成测试限制在重要的基础结构方案上,若可用单元测试或集成测试测试行为,优先选择单元测试。集成测试中被测试的项目通常称为"SUT",用于指代要测试的应用。避免为每种数据库和文件系统交互排列编写集成测试,而是通过一组集中式读取、写入、更新和删除集成测试充分测试这些组件,使用单元测试测试与这些组件交互的方法逻辑,使用 fakemock 对象可加快测试速度。

集成测试实战

我们在之前的章节中创建了Sample.ApiSample.Repository的项目,现在我们对这个项目进行整体的集成测试,带大家来感受一下。

ASP.NET Core 中的集成测试需要以下内容:

  • 测试项目用于包含和执行测试。 测试项目具有对 SUT 的引用。
  • 测试项目为 SUT 创建测试Web主机,并使用测试服务器客户端处理 SUT 的请求和响应。
  • 测试运行程序用于执行测试并报告测试结果。

集成测试后跟一系列事件,包括常规“排列”“操作”“断言”测试步骤:

  • 已配置 SUTWeb 主机。
  • 创建测试服务器客户端以向应用提交请求。
  • 执行“排列”测试步骤:测试应用会准备请求。
  • 执行“操作”`测试步骤:客户端提交请求并接收响应。
  • 执行“断言”测试步骤:实际响应基于预期响应验证为通过或失败。
  • 该过程会一直继续,直到执行了所有测试。
  • 报告测试结果。

上面解释到了集成测试中被测试的项目通常称为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 类,以简化 SUTTestServer 中的启动过程。

概念有点多,后续里面的概念会慢慢讲到。

我们知道Asp.Net CoreWeb项目项目是借助Kestrel启动,用集成测试的TestServer正是代替了Kestrel托管服务的启动,那我们要测试的项目就不需要单独被启动了。

什么是TestServer?

TestServer 用于在集成测试中模拟和启动应用程序的主机环境。通过创建 TestServer 实例,可以在测试中模拟出一个运行中的应用程序实例,以便进行端到端的集成测试。

Microsoft.AspNetCore.Mvc.Testing中已经默认集成了对TestServer的支持所以,不需要额外进行配置。

SUT 环境?

如果未设置 SUT 的环境,则环境会默认为开发环境即 Development

向测试项目公开启动类Program

使用 WebApplicationFactory<TEntryPoint> 创建 TestServer以进行集成测试。 TEntryPointSUT 的入口点类,通常是 Program.cs

有两种向测试项目公开 Program 的方法

  • Program.cs添加部分类
  var builder = WebApplication.CreateBuilder(args);
// ... Configure services, routes, etc.
app.Run();
+ public partial class Program { }
  • 配置MSBuild

    SUTcsproj文件下添加
	<ItemGroup>
<Using Include="Sample.Api" />
<InternalsVisibleTo Include="dotNetParadise.IntegrationTest" />
</ItemGroup>
  1. <Using Include="Sample.Api" />:这个子元素指定了要在项目中使用的命名空间或程序集。在这里,Sample.Api 被包含在项目中,以便项目可以访问和使用该命名空间或程序集中的内容。

  2. <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>

接下来在我们的SUTSample.Apiprogram中打个断点来验证一下

看到了我们测试时默认的 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主机和内存中测试服务器来支持集成测试。

在集成测试中,我们评估应用组件在更广泛的级别上的功能,验证多个组件一起工作以生成预期结果,包括数据库、文件系统、网络设备等。单元测试主要用于测试独立的软件组件,而集成测试需要使用实际组件,涉及更多的代码和数据处理,以及更长的运行时间。建议将集成测试限制在重要的基础结构方案上,优先选择单元测试或集成测试来测试行为。

在集成测试中,被测试的项目通常称为SUTSystem Under Test),用于指代要测试的应用。避免为每种数据库和文件系统交互编写独立的集成测试,而是通过一组集中式的测试来全面测试这些组件,并使用单元测试来测试与这些组件交互的方法逻辑。

通过自定义的WebApplicationFactory,可以根据测试需求定制化配置和功能,模拟真实的应用程序环境进行全面和准确的测试。自定义的WebApplicationFactory提供了灵活性和定制性,满足复杂的测试需求,并确保测试环境的准确性。虽然自定义的WebApplicationFactory可能需要更多的理解和维护,但能更好地适应不同的测试场景。

集成测试是确保应用程序正常运行的关键步骤,通过综合不同组件的功能来验证应用的整体表现,提高应用程序的质量和稳定性。

实战指南:使用 xUnit 和 ASP.NET Core 进行集成测试【完整教程】的更多相关文章

  1. 使用 xUnit 编写 ASP.NET Core 单元测试

    还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊..NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP ...

  2. 使用 xUnit 编写 ASP.NET Core WebAPI单元测试

    本文使用xUnit对ASP.NET Core WebAPI做单元测试,使用HttpClient的同步和异步请求,下面详细介绍xUnit的使用过程: 一.创建示例项目 模板为我们自动创建了一个Value ...

  3. 《ASP.NET Core应用开发入门教程》与《ASP.NET Core 应用开发项目实战》正式出版

    “全书之写印,实系初稿.有时公私琐务猬集,每写一句,三搁其笔:有时兴会淋漓,走笔疾书,絮絮不休:有时意趣萧索,执笔木坐,草草而止.每写一段,自助覆阅,辄摇其首,觉有大不妥者,即贴补重书,故剪刀浆糊乃不 ...

  4. 【目录】ASP.NET Core 2.1 入门教程

    ASP.NET Core 2.1 快速学习.入门系列教程,这个入门系列教程为了帮助大家快速上手ASP.NET Core. 本教程包含且不限于: 使用VS Code开发ASP.NET Core应用 AS ...

  5. 使用xunit对asp.net core webapi进行集成测试

    新项目我们采用前后端分离,后端采用asp.net core webapi, 如何对后端代码进行自动化测试呢,有以下几种方案: 1. 单元测试,目前这个方案对我们来说难度很大,抛开时间的问题,单元测试对 ...

  6. 极简版ASP.NET Core学习路径及教程

    绝承认这是一个七天速成教程,即使有这个效果,我也不愿意接受这个名字.嗯. 这个路径分为两块: 实践入门 理论延伸 有了ASP.NET以及C#的知识以及项目经验,我们几乎可以不再需要了解任何新的知识就开 ...

  7. ASP.NET Core中获取完整的URL(转载)

    在之前的ASP.NET中,可以通过 Request.Url.AbsoluteUri 获取,但在ASP.NET Core没有这个实现,请问如何获取呢?方法一:先引用“using Microsoft.As ...

  8. ASP.NET Core获取请求完整的Url

    在ASP.NET项目中获取请求完整的Url: 获取System.Web命名空间下的类名为HttpRequestBase的Url方法: /// <summary>在派生类中替代时,获取有关当 ...

  9. ASP.NET Core 2.1 Web API + Identity Server 4 + Angular 6 + Angular Material 实战小项目视频

    视频简介 ASP.NET Core Web API + Angular 6的教学视频 我是后端开发人员, 前端的Angular部分讲的比较差一些, 可以直接看代码!!!! 这是一个小项目的实战视频, ...

  10. ASP.NET Core会议管理平台实战_汇总贴

    ASP.NET Core会议管理平台实战 课程地址:https://ke.qq.com/course/389673?from=800004097#term_id=100464670 ASP.NET C ...

随机推荐

  1. java中webSocket发送图片文件数据非常慢

    一.问题由来 目前在开发的这个小程序中有一个功能需要和Unity客户端进行互动操作,互动的大致流程为在微信小程序中点击一个操作,发送一个HTTP请求, Java后台收到这个请求后,会给Unity客户端 ...

  2. 基于python的wav转txt的源码

    最近在做一个算法的时候,用到了这个转换,这里做一个备忘,希望能给你提供价值. import wave import matplotlib.pyplot as plt import numpy as n ...

  3. 3DCAT将携Cloud3D/XR解决方案亮相视博会

    2021年05月10日-12日,亚洲视觉智能与沉浸式产业博览会将在广州·中国进出口商品交易会展展馆隆重开幕!届时,3DCAT实时渲染云将携Cloud3D.CloudXR解决方案惊艳亮相,4.2号展馆H ...

  4. Sealos 云开发:Laf 出嫁了,与 Sealos 正式结合!

    千呼万唤始出来,Laf 云开发最近已正式与 Sealos 融合,入住 Sealos!大家可以登录 Sealos 公有云 体验和使用,现在正式介绍一下 Sealos 云开发. Sealos 云开发是什么 ...

  5. Python简单程序设计(Average篇)

    如题: 解题方式如下:

  6. Java内存马2-Spring内存马

    Spring内存马 目录 Spring内存马 1.Spring&Spring MVC简介 2.环境搭建 3.Controller内存马 4.踩坑日记 5.Interceptor内存马 1.Sp ...

  7. ZYNQ7000系列学习之TF卡读写(2)

    ZYNQ读写实验(2) 1.实验原理 在TF卡读写实验1中,已经将每一个步骤都做完了,但是最后得到的结果是错误的.那个时候由于TF没有格式化,显示的是错误信息.在格式化后,再次实验,得到了预期的结果. ...

  8. KingbaseES V8R6集群运维系列 -- 修改ssh通信为 sys_securecmdd 通信

    一.适用于: 本文档使用于KingbaseES V008R006版本. 二.关于SYS_SECURECMDD: sys_securecmdd是KingbaseES集群自带的工具,集群监控.管理集群时通 ...

  9. 2 URLEncode和Base64

    1. URLEncode和Base64 在我们访问一个url的时候总能看到这样的一种url https://www.sogou.com/web?query=%E5%90%83%E9%A5%AD%E7% ...

  10. 3 JavaScript字符串操作

    3 字符串操作 常用的字符串操作相关的方法: s.split() 字符串切割 s.substr(start, len) 字符串切割, 从start开始切, 切len个字符 s.substring(st ...