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[1] 用于在集成测试中模拟和启动应用程序的主机环境通过创建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() {//Arrangevar httpClient = _factory.CreateClient();//actvar response = await httpClient.GetAsync(\"/api/Staff\");//Assert//校验状态码 Assert.Equal(HttpStatusCode.OK, response.StatusCode);//校验用户var users = await response.Content.ReadFromJsonAsync<List<Staff>>(); Assert.Not(users); } [Fact]public async Task GetConfig_WhenCalled_ReturnOk() {//Arrangevar httpClient = _factory.CreateClient();//actvar response = await httpClient.GetAsync(\"/GetConfig\");//Assert//校验状态码 Assert.Equal(HttpStatusCode.OK, response.StatusCode);//校验用户var config = await response.Content.ReadFromJsonAsync<string>(); Assert.Not(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() {//Arrangevar httpClient = factory.CreateClient();//actvar response = await httpClient.GetAsync(\"/api/Staff\");//Assert//校验状态码 Assert.Equal(HttpStatusCode.OK, response.StatusCode);//校验用户var users = await response.Content.ReadFromJsonAsync<List<Staff>>(); Assert.Not(users); } [Fact]public async Task GetConfig_WhenCalled_ReturnOk() {//Arrangevar httpClient = factory.CreateClient();//actvar response = await httpClient.GetAsync(\"/GetConfig\");//Assert//校验状态码 Assert.Equal(HttpStatusCode.OK, response.StatusCode);//校验用户var config = await response.Content.ReadFromJsonAsync<string>(); Assert.Not(config); }// 后面测试省略}
最后 集成测试是确保应用组件在包含数据库、文件系统和网络等基础结构的级别上正常运行的重要方式ASP.NET Core
通过结合单元测试框架、测试Web
主机和内存中测试服务器来支持集成测试在集成测试中,我们评估应用组件在更广泛的级别上的功能,验证多个组件一起工作以生成预期结果,包括数据库、文件系统、网络设备等单元测试主要用于测试独立的软件组件,而集成测试需要使用实际组件,涉及更多的代码和数据处理,以及更长的运行时间建议将集成测试限制在重要的基础结构方案上,优先选择单元测试或集成测试来测试行为在集成测试中,被测试的项目通常称为SUT
(System Under Test
),用于指代要测试的应用避免为每种数据库和文件系统交互编写独立的集成测试,而是通过一组集中式的测试来全面测试这些组件,并使用单元测试来测试与这些组件交互的方法逻辑通过自定义的WebApplicationFactory
,可以根据测试需求定制化配置和功能,模拟真实的应用程序环境进行全面和准确的测试自定义的WebApplicationFactory
提供了灵活性和定制性,满足复杂的测试需求,并确保测试环境的准确性虽然自定义的WebApplicationFactory
可能需要更多的理解和维护,但能更好地适应不同的测试场景集成测试是确保应用程序正常运行的关键步骤,通过综合不同组件的功能来验证应用的整体表现,提高应用程序的质量和稳定性ASP.NET Core 中的集成测试[2] 本文完整源代码[3] 参考资料 [1]TestServer: https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.testhost.testserver?view=aspnetcore-8.0[2]ASP.NET Core 中的集成测试: https://learn.microsoft.com/zh-cn/aspnet/core/test/integration-tests?view=aspnetcore-8.0[3]本文完整源代码: https://github.com/Dong-Ruipeng/dotNetParadise-xUnit
(图片来源网络,侵删)
0 评论