Aspire CLI 端到端测试指南:基于 Hex1b 终端自动化的 E2E 测试体系与 CI 并行矩阵
2026/9/18 5:55:29 网站建设 项目流程

Aspire CLI 端到端测试指南:基于 Hex1b 终端自动化的 E2E 测试体系与 CI 并行矩阵

【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire

本文档围绕 Aspire 仓库中的 CLI 端到端测试项目展开,系统讲解如何利用 Hex1b 终端自动化库模拟真实用户与aspire命令行的交互、如何按测试类拆分 CI 任务实现并行执行、如何编写与运行这类 E2E 测试,以及如何在不修改任何 CI 配置的情况下自动纳入新的测试类。读完本文,你将掌握该测试项目的完整架构、测试类模板、交互辅助方法与本地/CI 运行全流程。

项目定位与测试目标

tests/Aspire.Cli.EndToEnd.Tests是 Aspire 仓库中专门针对 Aspire CLI 的端到端(End-to-End,简称 E2E)测试项目。与普通单元测试不同,这类测试不 mock 终端或命令层,而是通过 Hex1b 终端自动化库真正地打开一个伪终端(PTY),在 shell 中逐字键入aspire ...命令、观察终端输出、响应交互式提示,从而模拟用户操作 CLI 的完整链路。

从仓库中 80 多个测试类可以看出其覆盖面:aspire new/init模板脚手架(CSharpInitTests.cs、TypeScriptStarterTemplateTests.cs)、aspire run/stop生命周期(StartStopTests.cs)、部署到 Kubernetes/Docker/Podman/Radius(KubernetesDeployWithPostgresTests.cs、DockerDeploymentTests.cs)、资源命令(ResourceCommandTests.cs)、doctor/logs/ps等诊断命令,以及配置迁移、遥测、横幅展示等行为。这些测试的目标是验证 CLI 在真实终端环境下的完整行为,包括交互提示、退出码、文件系统副作用与进程生命周期。

测试架构

测试基础设施

整个测试项目建立在三个关键组件之上:

  • CliEndToEndTestBase:所有 E2E 测试类的基类,提供 Hex1b 终端初始化、工作目录管理与通用辅助方法。虽然该类在仓库中位于共享代码(Hex1bTestHelpers.cs 中对应Hex1bTestHelpers静态辅助类,提供终端创建与提示符检测),其核心职责是一致的:为每个测试准备一个独立的、可复现的终端会话。
  • HeadlessPresentationAdapter:无显示环境下的终端渲染适配器,使测试可以在没有图形界面的 CI 容器中运行,并用 asciinema 格式录制终端会话。
  • TestEnumerationRunsheetBuilder:统一的 MSBuild targets(见 TestEnumerationRunsheetBuilder.targets),在SplitTestsOnCI=true的项目中提取测试类并生成每个类对应的 runsheet(测试运行清单)。

终端会话的底层创建方式

共享辅助类Hex1bTestHelpers.CreateTestTerminal展示了终端会话的具体配置(Hex1bTestHelpers.cs):

  • headless模式创建终端,默认尺寸 160 列 × 48 行;
  • 开启asciinema 录制.cast文件),CI 中录制文件写入$GITHUB_WORKSPACE/testresults/recordings/并作为工件上传,本地则写入TestResults/recordings/目录;
  • 通过WithPtyProcess("/bin/bash", ["--norc"])启动真实的 bash 伪终端进程,不加载 rc 文件以保证环境干净。

录制文件按 xUnit 报告的测试方法名命名([CallerMemberName]回退机制见 ResolveTestMethodName),确保.cast文件能与 TRX 测试结果按名称关联,供 CI 上的录制评论工作流使用。

提示符同步机制

终端自动化最大的难点是"何时可以执行下一条命令"。共享辅助类通过SequenceCounter与 bash 提示符模式匹配解决这一问题(Hex1bTestHelpers.cs):

  • WaitForSuccessPrompt:等待形如[N OK] $的成功提示符(N 为递增的序列号),命令成功后继续;
  • WaitForErrorPrompt:等待[N ERR:{exitCode}] $的错误提示符,用于验证命令以指定非零退出码失败;
  • WaitForAnyPrompt:同时接受成功或错误提示,用于不关心退出码的场景。

默认等待超时为 500 秒,足以覆盖aspire new创建项目(dotnet new还原与构建)在慢速 CI 环境下的耗时。

CI 流水线与并行矩阵

README 明确指出:每个测试类在 CI 中作为独立的 job 运行,从而在 GitHub Actions runner 上实现并行执行。这与"一个测试类 = 一个 CI job"的设计直接对应测试项目 csproj 中的SplitTestsOnCI=true(Aspire.Cli.EndToEnd.Tests.csproj)。

整个流水线分三个阶段:

  1. 发现阶段(Discovery Phase)TestEnumerationRunsheetBuilder构建测试项目,并调用GenerateTestPartitionsForCItarget 发现所有测试类。该 builder 默认跳过tests/Sharedtests/testproject等非测试项目目录,且仅当IncludeCliE2ETests=true时才包含本项目(见 TestEnumerationRunsheetBuilder.targets);
  2. 矩阵生成(Matrix Generation):为每个唯一测试类创建一个矩阵条目,并按目标平台展开;
  3. 并行执行(Parallel Execution):GitHub Actions 为每个测试类创建独立 job,在不同 agent 上并行运行。

架构示意如下:

┌─────────────────────────────────────────────────────────────────┐ │ generate_cli_e2e_matrix │ │ Discovers test classes and generates runsheet │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ build_packages │ │ Builds NuGet packages needed for tests │ └─────────────────────────────────────────────────────────────────┘ │ ┌───────────┼───────────┐ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ Job 1 │ │ Job 2 │ │ Job N │ │ NewCmd │ │ RunCmd │ │ ... │ │ Tests │ │ Tests │ │ │ └───────────┘ └───────────┘ └───────────┘

从测试项目 csproj 可以进一步确认 CI 相关的平台策略(Aspire.Cli.EndToEnd.Tests.csproj):

  • 仅在 GitHub Actions Linux 上运行RunOnGithubActionsLinux=true),Windows 与 macOS 均关闭;
  • 不在 Helix 与 AzDO CI agent 上运行(RunOnAzdoHelixWindows/LinuxRunOnAzdoCIWindows/Linux均为 false);
  • TestClassNamePrefixForCI设置为Aspire.Cli.EndToEnd.Tests,用于 CI 矩阵中按命名空间过滤测试类;
  • 测试会话超时 30 分钟、挂起超时 15 分钟(TestSessionTimeout=30mTestHangTimeout=15m);
  • 项目需要已构建的 NuGet 包(RequiresNugets=true)、CLI 归档(RequiresCliArchive=true)与 GitHub Token(RequiresGitHubToken=true)。

编写 E2E 测试

测试类结构

每个测试类必须遵循以下约定(README 原文要求):

  1. 继承自CliEndToEndTestBase
  2. 实现IAsyncLifetime以支持正确的 setup/teardown;
  3. InitializeAsync中调用await base.InitializeAsync()
  4. DisposeAsync中调用await base.DisposeAsync()

标准模板如下:

public sealed class MyCommandTests : CliEndToEndTestBase, IAsyncLifetime { public async Task InitializeAsync() { await base.InitializeAsync(); } async Task IAsyncLifetime.DisposeAsync() { await base.DisposeAsync(); } [Fact] public async Task MyCommand_DoesExpectedThing() { // Use the helper methods to interact with the CLI await RunAspireAsync("my-command --option value"); await WaitAsync(2000); // Assert on file system changes, process output, etc. } }

可用辅助方法

方法作用
RunAspireAsync(string arguments)在终端中运行aspire <arguments>
TypeAndEnterAsync(string text)键入文本并按下回车
WaitAsync(int milliseconds)等待指定时长
CreateSequence()创建Hex1bTerminalInputSequenceBuilder,用于复杂交互
WorkDirectory当前测试独有的临时目录

复杂交互

对于交互式提示与复杂的终端操作,使用CreateSequence()构造输入序列:

var sequence = CreateSequence() .SlowType("aspire new") .Enter() .Wait(2000) .Key(Hex1bKey.DownArrow) // Navigate menu .Enter() // Select option .SlowType("myproject") // Enter project name .Enter() .Build(); await sequence.ApplyAsync(Terminal);

更贴近源码的交互方式:Automator 与序列构建器

在实际测试中,交互被进一步封装。以 SmokeTests.cs 的CreateAndRunAspireStarterProject为例,一个完整的"创建并运行 Starter 项目"流程为:

  1. 获取仓库根目录(CliE2ETestHelpers.GetRepoRoot());
  2. 检测 CLI 安装策略(CliInstallStrategy.Detect,支持本地/归档/Docker 多种安装方式);
  3. 创建临时工作区(TemporaryWorkspace.Create);
  4. 创建 Docker 测试终端(CreateDockerTestTerminal,可挂载 Docker socket);
  5. 通过Hex1bTerminalAutomator依次执行PrepareDockerEnvironmentAsyncInstallAspireCliAsyncAspireNewAsync("AspireStarterApp", counter)→ 运行aspire run
  6. 等待 "Press CTRL+C to stop the AppHost and exit." 出现,确认应用启动成功;
  7. 发送 Ctrl+C 停止 AppHost,等待成功提示符。

共享辅助类还提供了针对aspire new完整交互流程的封装AspireNew(Hex1bTestHelpers.cs),它按步骤等待模板列表、选择模板(Starter / JsReact / ExpressReact / PythonReact / EmptyAppHost / TypeScriptEmptyAppHost / JavaEmptyAppHost)、输入项目名、接受默认输出路径、处理*.dev.localhostURL 询问、Redis 缓存询问与测试项目询问,最后拒绝 agent init 确认提示。AspireInit则封装了aspire init --language csharp及 NuGet.config 提示的处理。这意味着aspire new的提示发生变化时,只需修改这一处共享封装,而不必逐个改动测试。

CellPatternSearcher是核心的终端屏幕匹配工具,通过Find("text")/FindPattern("...")/RightText("...")组合匹配终端快照中的单元格模式,WaitUntil则轮询快照直到匹配成功或超时。

运行测试

npm 测试依赖的版本管理

README 特别强调项目级package.json(package.json)对 npm 工具的版本固定作用:

  • ViteTestHelpers.GetCreateCommand从该清单读取create-vite版本(当前固定为9.2.0),而不是使用vite@latest
  • 该清单以嵌入式资源方式编译进测试程序集(见 csproj 中的EmbeddedResource Include="package.json",Aspire.Cli.EndToEnd.Tests.csproj),因此本地运行与 CI 测试归档使用同一版本,本地无需为清单执行npm install
  • Dependabot 每周检查该目录,并在提出新版本前等待七天(冷却期),避免采纳刚发布、其依赖可能被 Deno 最低依赖年龄策略拒绝的 Vite 模板;
  • 清单中的工具版本必须保持精确(exact),且应使用共享辅助方法,而不是在单个测试中内嵌版本号。

构建与运行命令

# Build the test project ./build.sh -restore -build -projects tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj # Run all tests dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj # Run a specific test class dotnet test tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj -- --filter-class "Aspire.Cli.EndToEnd.Tests.AspireNewCommandTests"

注意:--filter-class过滤器用于精确指定某个测试类;若要按特性或其它条件筛选,可使用 xUnit v3 的标准--filter语法。

运行环境要求

README 列出的硬性要求如下,这与 csproj 中的平台开关完全一致:

  • 仅限 Linux:Hex1b 需要 Linux 终端环境,测试在 Windows 和 macOS 上跳过(csproj 中RunOnGithubActionsWindows/RunOnGithubActionsMacOS均为 false,且 Helix 与 AzDO 均不运行);
  • Aspire CLI 已安装aspire命令必须位于 PATH 中;
  • 已构建 NuGet 包:测试运行前需要先构建 Aspire 相关包(csproj 中RequiresNugets=trueTestUsingWorkloads=true)。

此外,从源码可以补充两点实现细节:

  • 测试会话超时(TestSessionTimeout=30m)与挂起超时(TestHangTimeout=15m)防止 CI 任务无限挂起;
  • 由于 Hex1b 包未签名,csproj 显式设置了SignAssembly=false以禁用强名称签名(Aspire.Cli.EndToEnd.Tests.csproj)。

添加新的测试类

添加新测试类非常轻量,只需三步:

  1. 创建新文件,命名遵循Aspire*Tests.cs*CommandTests.cs模式(仓库中现有如BannerTests.csDescribeCommandTests.csDoctorCommandTests.csKubernetesPublishTests.cs等);
  2. 遵循上文"测试类结构"一节的标准模板(继承基类、实现IAsyncLifetime、调用 base 的初始化与释放);
  3. CI 会自动发现并以独立 job 运行新测试——无需修改任何 CI 配置

其自动化的原理在于:TestEnumerationRunsheetBuilder会自动发现所有设置了SplitTestsOnCI=true的项目中的测试类(TestEnumerationRunsheetBuilder.targets)。该 builder 属于 class-mode 项目(按类枚举),会针对程序集构建后逐一提取测试类生成 runsheet,再交由build-test-matrix.ps1展开为矩阵。完整机制可参考仓库内的 TestingOnCI.md 文档。

TestEnumerationRunsheetBuilder.targets的实现可以进一步确认:对于Aspire.Cli.EndToEnd.Tests这类 class-mode 项目,构建器会走"Build + class discovery"路径;而对于使用[Trait("Partition","<n>")]分区特性的项目,则优先通过源码扫描(无需编译整个闭包)直接提取分区值,并始终追加uncollected:*条目兜底,保证任何未被扫描到的类都不会被遗漏。

测试样例参考

以下仓库内现成的测试类可作为编写新测试的最佳参照:

  • SmokeTests.cs:创建并运行 Starter 项目、SSH 重定向输出等冒烟场景;
  • BannerTests.cs:验证首次运行横幅与--banner显式标志,通过删除~/.aspire/cli/cli.firstUseSentinel哨兵文件模拟首次运行,并断言RootCommandStrings.BannerWelcomeText与 "Telemetry" 文案出现;
  • DotnetToolSmokeTests.cs:验证以dotnet tool方式安装的 CLI;
  • KubernetesDeploy*Tests.cs:部署类测试,通常包含CaptureWorkspaceOnFailure特性——测试失败时自动捕获工作区状态以辅助排查。

小结

Aspire CLI E2E 测试项目通过 Hex1b 终端自动化实现了对 CLI 真实交互行为的全面验证,其"一个测试类 = 一个 CI job"的矩阵设计保证了大规模并行执行效率,而TestEnumerationRunsheetBuilder的自动发现机制让新增测试几乎零配置。对于想要为该测试套件贡献新用例的开发者,只需遵循本文的类结构模板、复用共享辅助方法(终端创建、提示符同步、AspireNew/AspireInit交互封装),即可快速编写出稳定、可维护、可并行执行的端到端测试。

【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询