后端电商前端微服务【免费下载链接】eShopA reference .NET application implementing an eCommerce site项目地址https://gitcode.com/GitHub_Trending/es/eShop点击查看免费下载本指南以 tests/README.md 为骨架系统梳理 eShop 参考实现.NET 电商站点中用于验证各业务组件行为的测试体系。仓库tests/目录下同时包含单元测试与功能测试两类工程前者以 MSTest NSubstitute 对服务与领域逻辑做快速隔离验证后者借助.NET Aspire 托管测试容器如 PostgreSQL在真实依赖环境下端到端验证 API 行为。读完本文你将掌握 eShop 各测试项目的结构、核心测试手法、Aspire 测试基础设施的搭建方式以及运行这些测试所需的前置条件Docker。一、测试体系总览单元测试与功能测试的分工原文档开宗明义地指出tests/目录存放一组用于验证 eShop 应用各组件行为的单元测试unit tests与功能测试functional tests。结合仓库实际布局这两个层次的分工非常清晰层次测试项目验证对象依赖单元测试Basket.UnitTestsBasket.API 的 gRPC 服务逻辑NSubstitute Mock 无外部依赖单元测试Ordering.UnitTestsOrdering 领域模型与命令处理器NSubstitute Mock 领域模型单元测试ClientApp.UnitTestsMAUI 客户端服务层内置 Mock 服务功能测试Catalog.FunctionalTestsCatalog.API 的 REST API 全流程Aspire PostgreSQL 测试容器功能测试Ordering.FunctionalTestsOrdering.API 的下单、取消、发货等流程Aspire PostgreSQL Identity API原文档还特别强调了一条重要前置条件功能测试依赖 Aspire host 启动测试容器因此要求 Docker 处于运行状态。这一点在所有功能测试项目中都有直接体现详见下文第四节。从 tests/Directory.Build.props 可以看到所有测试项目共享统一的测试框架配置Project !-- MSTest settings -- PropertyGroup MSTestAnalysisModeRecommended/MSTestAnalysisMode /PropertyGroup !-- xUnit settings -- PropertyGroup UseMicrosoftTestingPlatformRunnertrue/UseMicrosoftTestingPlatformRunner /PropertyGroup /Project该文件通过目录级属性Directory.Build.props为整个tests/子树启用了 MSTest 的推荐分析模式MSTestAnalysisModeRecommended并统一开启 Microsoft Testing Platform runnerUseMicrosoftTestingPlatformRunnertrue这意味着所有测试项目均以Exe输出类型运行于统一的测试执行平台之上。二、单元测试项目详解隔离验证服务与领域逻辑2.1 Basket.UnitTestsgRPC 服务层的 Mock 测试Basket.UnitTests 以 MSTest.Sdk 为项目骨架通过 NSubstitute 对仓储层进行替换验证 BasketService 的 gRPC 方法行为详见 Basket.UnitTests.csproj 与 BasketServiceTests.cs。核心测试思路可从 BasketServiceTests.cs 中提炼[TestClass] public class BasketServiceTests { public TestContext TestContext { get; set; } [TestMethod] public async Task GetBasketReturnsEmptyForNoUser() { var mockRepository Substitute.ForIBasketRepository(); var service new BasketService(mockRepository, NullLoggerBasketService.Instance); var serverCallContext TestServerCallContext.Create(cancellationToken: TestContext.CancellationToken); serverCallContext.SetUserState(__HttpContext, new DefaultHttpContext()); var response await service.GetBasket(new GetBasketRequest(), serverCallContext); Assert.IsInstanceOfTypeCustomerBasketResponse(response); Assert.IsEmpty(response.Items); } }三个测试用例恰好覆盖了三种关键路径无用户上下文GetBasketReturnsEmptyForNoUser验证未携带用户信息时返回空购物篮有效用户 IDGetBasketReturnsItemsForValidUserId通过ClaimsPrincipal注入sub声明验证能取回对应条目的购物篮无效用户 IDGetBasketReturnsInvalidUserId验证即便仓储有数据无有效身份时仍返回空结果。测试的关键难点在于 gRPC 的ServerCallContext在单元测试中难以直接构造仓库通过 TestServerCallContext.cs 提供帮助器来模拟。同时测试用NullLoggerBasketService.Instance代替真实日志器将关注点完全收敛到业务逻辑本身。2.2 Ordering.UnitTests命令处理器与领域聚合测试Ordering.UnitTests 是覆盖面最广的单元测试项目引用 Ordering.API、Ordering.Domain、Ordering.Infrastructure 三个项目见 Ordering.UnitTests.csproj测试分布在Application/与Domain/两个命名空间下Application 层测试 CQRS 命令处理器例如 NewOrderCommandHandlerTest.cs 通过 NSubstitute Mock 掉IOrderRepository、IIdentityService、IMediator、IOrderingIntegrationEventService验证CreateOrderCommandHandler.Handle在订单未持久化时返回false并验证空 Buyer 构造时抛出ArgumentNullExceptionIdentifiedCommandHandlerTest.cs 与 SetStockRejectedOrderStatusCommandTest.cs 则覆盖幂等命令与库存驳回状态流转OrdersWebApiTest.cs 验证 Web API 映射层Domain 层BuyerAggregateTest.cs 与 OrderAggregateTest.cs 直接对 DDD 聚合根进行行为验证ValueObjectTests.cs 则验证值对象相等性语义。以订单命令处理器测试为例其 Mock 装配方式是典型的 eShop 单元测试模式_orderRepositoryMock Substitute.ForIOrderRepository(); _identityServiceMock Substitute.ForIIdentityService(); _orderingIntegrationEventService Substitute.ForIOrderingIntegrationEventService(); _mediator Substitute.ForIMediator(); // 模拟仓储层保存成功 _orderRepositoryMock.UnitOfWork.SaveChangesAsync(default) .Returns(Task.FromResult(1));从源码结构可以看出Ordering 的单元测试有意保持与基础设施EF Core、消息总线完全解耦让领域规则与命令编排可以在毫秒级完成验证。2.3 ClientApp.UnitTestsMAUI 客户端服务层测试ClientApp.UnitTests 是唯一针对移动客户端.NET MAUI的测试项目其 csproj 设置了UseMauitrue并引用 src/ClientApp/ClientApp.csproj见 ClientApp.UnitTests.csproj。测试对象是客户端封装的 HTTP 服务层与 ViewModel。以 CatalogServiceTests.cs 为例它直接使用CatalogMockService客户端内置的假数据服务验证目录、品牌、类型三个数据源均能返回非空集合[TestMethod] public async Task GetFakeCatalogTest() { var catalogMockService new CatalogMockService(); var catalog await catalogMockService.GetCatalogAsync(); Assert.AreNotEqual(0, catalog.Count()); }项目还内置了 MockDialogService.cs、MockNavigationService.cs、MockSettingsService.cs 等一组 Mock 服务用于支撑 MainViewModelTests.cs、CatalogViewModelTests.cs、CatalogItemViewModelTests.cs、OrderViewModelTests.cs 等 ViewModel 层测试覆盖购物篮服务、目录服务、订单服务三条客户端业务线对应 Services 目录下的BasketServiceTests.cs、CatalogServiceTests.cs、OrdersServiceTests.cs。三、功能测试的 Aspire 基础设施测试容器的关键原理原文档着重指出功能测试leverage the Aspire host to spin up test containers。这一机制在 CatalogApiFixture.cs 中体现得最为直接。该 Fixture 继承WebApplicationFactoryProgram并实现IAsyncLifetime在构造函数中使用DistributedApplication.CreateBuilder启动一个精简的 Aspire hostpublic sealed class CatalogApiFixture : WebApplicationFactoryProgram, IAsyncLifetime { private readonly IHost _app; public CatalogApiFixture() { var options new DistributedApplicationOptions { AssemblyName typeof(CatalogApiFixture).Assembly.FullName, DisableDashboard true // 测试环境关闭 Aspire 仪表盘 }; var appBuilder DistributedApplication.CreateBuilder(options); Postgres appBuilder.AddPostgres(CatalogDB) .WithImage(ankane/pgvector) .WithImageTag(latest); _app appBuilder.Build(); } ... }这段代码揭示了三个关键设计测试即 Aspire 编排测试进程本身就是一个简化版 Aspire 应用通过AddPostgres(CatalogDB)声明 PostgreSQL 资源Aspire 会负责在 Docker 中拉起容器并管理其生命周期选用 pgvector 镜像Catalog 功能测试使用的镜像为ankane/pgvector这与 Catalog.API 中商品目录的语义化检索semantic search能力相匹配——测试容器需要具备向量存储扩展关闭仪表盘DisableDashboard true表明测试环境刻意避免 Aspire Dashboard 的开销只保留资源编排能力。InitializeAsync中执行await _app.StartAsync()并在之后通过Postgres.Resource.GetConnectionStringAsync()获取容器就绪后的真实连接串CreateHost则将该连接串以ConnectionStrings:CatalogDB的形式注入被测应用的配置protected override IHost CreateHost(IHostBuilder builder) { builder.ConfigureHostConfiguration(config { config.AddInMemoryCollection(new Dictionarystring, string { { $ConnectionStrings:{Postgres.Resource.Name}, _postgresConnectionString }, }); }); return base.CreateHost(builder); }这正是在真实数据库上跑 API的实现路径被测的Program来自 src/Catalog.API以容器数据库的连接串启动功能测试访问的是与生产行为一致的完整技术栈。对应的 Catalog.FunctionalTests.csproj 使用的 SDK 为Aspire.AppHost.Sdk/13.2.0目标框架为net10.0并引用了Aspire.Hosting.PostgreSQL容器编排、Microsoft.AspNetCore.Mvc.Testing与Microsoft.AspNetCore.TestHost进程内托管被测应用、Asp.Versioning.Http.ClientAPI 版本化客户端以及xunit.v3.mtp-v2xUnit v3 Microsoft Testing Platform 运行器。注意其中对 Catalog.API 的项目引用带有IsAspireProjectResourcefalse明确告知 Aspire 不要将被测项目当作可编排资源另行启动而是作为进程内程序集引用。四、功能测试实战Catalog 与 Ordering 的端到端验证4.1 Catalog.FunctionalTests覆盖 v1/v2 双版本 APICatalogApiTests.cs 通过IClassFixtureCatalogApiFixture复用同一个 Fixture同一测试容器实例并使用ApiVersionHandlerQueryStringApiVersionWriter构造支持 API 版本化的 HttpClientprivate HttpClient CreateHttpClient(ApiVersion apiVersion) { var handler new ApiVersionHandler(new QueryStringApiVersionWriter(), apiVersion); return _webApplicationFactory.CreateDefaultClient(handler); }几乎所有用例都以[Theory][InlineData(1.0)]/[InlineData(2.0)]的形式同时验证 v1 与 v2 两个 API 版本且两种版本往往使用不同的路由形态例如 v1 的/api/catalog/items/by/{name}在 v2 中演化为/api/catalog/items?name。代表性用例及其验证点包括测试用例验证内容GetCatalogItemsRespectsPageSize分页语义pageIndex0pageSize5返回 5 条且断言库中商品总数101 条种子数据 2 条由 AddCatalogItem 用例新增 103UpdateCatalogItemWorksWithoutPriceUpdate/WithPriceUpdate更新库存/价格后能正确回读价格变更用例断言Price更新为1.99mGetCatalogItemsbyIdsids1ids2ids3多 ID 查询返回 3 条GetCatalogItemWithExactName/WithPartialName精确名称Wanderer Black Hiking Boots 返回 1 条与模糊名称Alpine 返回 4 条检索GetCatalogItemWithsemanticrelevance语义相关性检索端点withsemanticrelevance可用GetCatalogItemWithTypeIdBrandId/GetAllCatalogTypeItemWithBrandId类型 品牌组合过滤type3/brand3 返回 4 条brand3 全类型返回 11 条GetCatalogItemPicWithId商品图片端点返回image/webp内容类型GetAllCatalogTypes/GetAllCatalogBrands元数据8 个类型、13 个品牌AddCatalogItem/DeleteCatalogItem完整生命周期新增后可查询到v1/v2 使用不同 ID删除后返回NoContent、再查询返回NotFound其中AddCatalogItem用例构造的完整CatalogItem负载含AvailableStock、RestockThreshold、MaxStockThreshold、OnReorder等库存字段与 CatalogItem.cs 中的模型定义一一对应可以直接作为调用 Catalog API 的参考报文。值得注意的是GetCatalogItemsRespectsPageSize断言总数为 103这一数据依赖测试的线性执行顺序先AddCatalogItem后分页查询从测试设计上看属于功能测试中的隐含执行顺序约束。4.2 Ordering.FunctionalTests多服务编排与自动身份注入Ordering.FunctionalTests 是依赖最复杂的功能测试其 Fixture 不仅拉起订单数据库容器还编排了第二个 PostgreSQL 容器IdentityDB以及 Identity.API 项目本身Postgres appBuilder.AddPostgres(OrderingDB); IdentityDB appBuilder.AddPostgres(IdentityDB); IdentityApi appBuilder.AddProjectProjects.Identity_API(identity-api) .WithReference(IdentityDB);OrderingApiFixture.cs 在CreateHost中把Postgres连接串注入ConnectionStrings:OrderingDB把IdentityApi.GetEndpoint(http).Url注入Identity:Url配置并注册了一个AutoAuthorizeStartupFilter来注入测试专用的认证中间件。AutoAuthorizeMiddleware.cs 是 Ordering 功能测试的关键设计由于下单接口需要用户身份测试通过中间件为每个请求伪造一个固定身份的ClaimsIdentitypublic const string IDENTITY_ID 9e3163b9-1ae6-4652-9dc6-7898ab7b7a00; public async Task Invoke(HttpContext httpContext) { var identity new ClaimsIdentity(cookies); identity.AddClaim(new Claim(sub, IDENTITY_ID)); identity.AddClaim(new Claim(unique_name, IDENTITY_ID)); identity.AddClaim(new Claim(ClaimTypes.Name, IDENTITY_ID)); httpContext.User.AddIdentity(identity); await _next.Invoke(httpContext); }这样 OrderingApiTests.cs 中的用例无需真实登录流程即可覆盖受保护端点同时测试真实走通身份解析链路sub声明由 ServerCallContextIdentityExtensions 一类的扩展解析。Ordering 功能测试覆盖的典型场景包括正常下单AddNewOrder用CreateOrderRequestx-requestid请求头提交断言返回OK草稿订单PostDraftOrder、CreateOrderDraftSucceeds验证订单草稿计算逻辑后者甚至断言Total等于Σ(Quantity × UnitPrice)并对订单项产品 ID 与请求负载做一致性比对失败路径AddNewEmptyOrder空订单返回BadRequest、CancelWithEmptyGuidFails/ShipWithEmptyGuidFailsx-requestid为空 Guid 返回BadRequest、CancelNonExistentOrderFails/ShipNonExistentOrderFails不存在的订单返回InternalServerError查询路径GetAllStoredOrdersWorks、GetAllOrdersCardType、GetStoredOrdersWithOrderId访问不存在的订单 ID 返回NotFound。这里x-requestid请求头正是 eShop 幂等性设计的核心载体——下游 Idempotency/RequestManager.cs 会基于该请求 ID 对命令去重。功能测试对空 Guid 与非空 Guid 的区分验证恰好覆盖了幂等命令验证器的行为边界对应 IdentifiedCommandHandlerTest.cs 的单元层面验证两层测试形成互补。五、运行测试命令、前置条件与适用限制5.1 单元测试零依赖即开即跑单元测试Basket.UnitTests、Ordering.UnitTests、ClientApp.UnitTests不依赖任何外部服务直接通过dotnet test执行即可。例如只跑购物篮服务测试dotnet test tests/Basket.UnitTests/Basket.UnitTests.csproj运行整个解决方案的测试dotnet test eShop.slnx由于各测试项目均为net10.0目标框架且启用了 Microsoft Testing Platform runner运行环境需要与仓库 global.json 声明匹配的 .NET 10 SDKMAUI 客户端测试ClientApp.UnitTests还要求具备 .NET MAUI 工作负载。5.2 功能测试必须先启动 Docker功能测试Catalog.FunctionalTests、Ordering.FunctionalTests的运行链路为测试进程启动 Aspire host → Aspire 向 Docker 请求创建ankane/pgvectorCatalog或标准 PostgreSQLOrdering容器 → 等待容器就绪并取得连接串 → 进程内启动被测 API。因此Docker 必须正在运行——这是 tests/README.md 明确声明的前置条件否则_app.StartAsync()阶段将因无法创建容器而失败首次运行会拉取镜像取决于网络环境可能需要一定时间Catalog 功能测试使用WithImageTag(latest)拉取最新版ankane/pgvector镜像测试容器默认不共享 DashboardDisableDashboard true测试结束通过DisposeAsync依次停止并释放 Aspire host 与被测应用。运行单个功能测试项目dotnet test tests/Catalog.FunctionalTests/Catalog.FunctionalTests.csproj5.3 测试覆盖的边界从源码结构看从仓库源码结构可以推断 eShop 测试体系目前的设计边界API 服务层覆盖充分Catalog、Ordering 的 REST 端点均有功能测试Basket 的 gRPC 服务有单元测试订单领域逻辑覆盖充分聚合根、值对象、命令处理器、幂等命令均有单元测试客户端以 Mock 数据为主ClientApp.UnitTests 通过内置 Mock 服务验证服务与 ViewModel 逻辑并未真实启动后端 API集成事件链路依赖功能测试间接覆盖如 Ordering 下单后发布OrderStartedIntegrationEvent的环节见 OrderStartedIntegrationEventHandler.cs在单元测试中通过 Mock 的IOrderingIntegrationEventService隔离其真实链路正确性更多依赖功能测试的整体通过。六、给读者的实操要点总结想快速验证业务规则跑 Ordering.UnitTests 与 Basket.UnitTests它们完全隔离、秒级完成且测试命名清晰GetBasketReturnsEmptyForNoUser等本身就是行为文档想验证 API 契约与数据层跑 Catalog.FunctionalTests它直接以ankane/pgvector容器验证包含语义检索在内的完整目录能力并同时覆盖 v1/v2 两代 API想验证订单核心链路跑 Ordering.FunctionalTests它编排了 OrderingDB、IdentityDB、Identity API 三个资源配合AutoAuthorizeMiddleware在免登录前提下验证下单、草稿、取消、发货全流程务必先启动 Docker这是 tests/README.md 唯一明确强调的硬性前置条件功能测试的一切容器编排都建立在其之上。综上eShop 的测试体系是一套单元测试保逻辑、功能测试保链路的双层方案单元测试层以 MSTest NSubstitute 将服务与领域逻辑打磨到可快速回归功能测试层则以 .NET Aspire 为底座把真实数据库容器编排进测试进程为电商核心 API 提供了接近生产环境的端到端验证能力。赞分享后端电商前端微服务【免费下载链接】eShopA reference .NET application implementing an eCommerce site项目地址https://gitcode.com/GitHub_Trending/es/eShop点击查看免费下载相关推荐OGX 测试体系深度解析Record-Replay 集成测试与单元测试实战指南OGX 测试体系深度解析Record Replay 集成测试与单元测试实战指南 OGXOpen GenAI Stack作为面向多 Provider 的 GAI应用API网关后端模型推理服务Nix 测试体系完全指南从单元测试、功能测试到模糊测试与安装器测试的实战解析Nix 测试体系完全指南从单元测试、功能测试到模糊测试与安装器测试的实战解析 Nix 是纯函数式包管理器其正确性高度依赖一套分层严密的测试体系。本文以 Ni包管理器开发工具CLI构建工具MoneyPrinterTurbo5分钟从创意到短视频的AI自动化神器MoneyPrinterTurbo5分钟从创意到短视频的AI自动化神器 还在为制作短视频而烦恼吗从文案构思到素材剪辑再到配音字幕传统视频创作流程不仅耗时AI 应用媒体生成音视频视频上一篇网络安全扫描工具Nuclei配置与使用完全指南下一篇HeyGem.ai本地部署完整指南3条命令跑通全离线数字人视频合成环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考