Skip to content

Commit 3cfc34d

Browse files
committed
refactor(cli): command-handler architecture and in-process tests
1 parent b6fc3e2 commit 3cfc34d

36 files changed

Lines changed: 2524 additions & 1503 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<RunSettings>
3+
<DataCollectionRunSettings>
4+
<DataCollectors>
5+
<DataCollector friendlyName="XPlat Code Coverage">
6+
<Configuration>
7+
<Format>cobertura</Format>
8+
</Configuration>
9+
</DataCollector>
10+
</DataCollectors>
11+
</DataCollectionRunSettings>
12+
</RunSettings>
13+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## Context
2+
`Modulus.Cli` 目前存在两类测试路径:
3+
- `install/uninstall/list` 已经具备 handler 形态(`Commands/Handlers/*`),测试可以直接调用 handler 并注入 `ServiceProvider`,因此覆盖率可统计。
4+
- `new/build/pack` 以进程执行为主(测试通过 `dotnet "<modulus.dll>" ...` 跑 CLI),导致覆盖率采集只覆盖测试进程,`Modulus.Cli` 关键文件几乎为 0%。
5+
6+
本变更的核心是统一架构与依赖注入方式,使所有命令均可 in-process 执行并被覆盖率工具准确统计。
7+
8+
## Goals / Non-Goals
9+
- Goals
10+
- 统一 `Modulus.Cli`**Command -> Handler** 架构。
11+
- 将外部副作用(Console/IO/Process)隔离到可替换抽象,支持测试注入与可重复执行。
12+
- 让 CLI 集成测试可 **in-process** 覆盖 `new/build/pack/install/uninstall/list` 的主路径。
13+
-`Modulus.Cli\` 行覆盖率提升到 **>= 95%**(以关键路径为准)。
14+
- Non-Goals
15+
- 不改变 CLI 的用户可见行为(参数、默认输出文案、目录结构)除非为修复 bug。
16+
- 不引入重量级命令行 UI 框架(例如迁移到 Spectre.Console)作为本变更前置条件。
17+
- 不在本变更内重写 `dotnet build` 本身的行为;只提供可测试的“进程执行适配层”。
18+
19+
## Decisions
20+
- Decision: 命令逻辑全部下沉到 Handler
21+
- 每个命令对应一个 handler(例如 `NewHandler` / `BuildHandler` / `PackHandler` / `InstallHandler` / `UninstallHandler` / `ListHandler`)。
22+
- Command 层只做:`System.CommandLine` 选项定义、解析、调用 handler。
23+
24+
- Decision: 引入最小抽象层隔离副作用
25+
- 抽象接口(实际命名):
26+
- `ICliConsole`:输出/输入、是否交互(替换 `Console.*`)。
27+
- `IProcessRunner`:进程执行(替换 `Process.Start`;测试可用 fake runner)。
28+
- 默认实现:
29+
- `SystemCliConsole``ProcessRunner`
30+
- Handler 仅依赖接口;默认实现通过 DI 组装。
31+
- 路径隔离:
32+
- 测试通过环境变量 `MODULUS_CLI_DATABASE_PATH` / `MODULUS_CLI_MODULES_DIR` 覆盖 CLI 使用的数据库路径与模块目录(由 `CliPathOverrides` 读取)。
33+
34+
- Decision: in-process 测试优先
35+
- `Modulus.Cli.IntegrationTests` 默认通过 handler/entrypoint in-process 执行命令。
36+
- 仅在必须验证“CLI 打包产物可执行”时,保留少量子进程端到端测试,但不计入覆盖率 gate 的关键路径统计。
37+
38+
## Risks / Trade-offs
39+
- 风险:重构触发行为漂移(输出/错误码/默认路径)
40+
- 缓解:以现有集成测试用例为回归基线;对输出保留关键断言(不做过度字符串耦合)。
41+
- 风险:抽象层过多导致复杂度上升
42+
- 缓解:严格限制接口数量与职责;一类副作用只允许一个抽象入口。
43+
44+
## Migration Plan
45+
- 先补齐 handler 架构与 DI 基座,再逐个迁移命令(`new``build``pack`→其余)。
46+
- 同步迁移测试为 in-process,并以覆盖率报告作为 gate。
47+
48+
## Open Questions
49+
- 是否需要将 `LocalStorage.GetUserRoot()` 增加可覆盖机制(环境变量/配置)来进一步提升可测试性?
50+
- 本变更已通过 CLI 层环境变量覆盖(`MODULUS_CLI_DATABASE_PATH` / `MODULUS_CLI_MODULES_DIR`)满足测试隔离需求,暂不在 Core 层引入额外全局开关。
51+
52+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Change: Refactor CLI Command Architecture for Testability & Coverage
2+
3+
## Why
4+
当前 `Modulus.Cli` 的部分命令(尤其 `new/build/pack`)以“进程外执行 + `Console`/`Process`/`File` 直连”为主,导致集成测试必须起子进程运行 `modulus.dll`,覆盖率采集无法统计子进程代码,`Modulus.Cli` 覆盖率长期低于预期(当前基线约 13%)。
5+
6+
本变更通过统一的 **Command -> Handler** 架构与可替换依赖抽象,使关键命令可以 **in-process** 执行,从而提升测试稳定性、可诊断性与覆盖率,并为后续 CLI 迭代提供可持续的工程基座。
7+
8+
## What Changes
9+
-`Modulus.Cli` 所有命令收敛为“薄 Command + 厚 Handler”的结构:Command 负责解析参数与绑定;Handler 负责业务逻辑。
10+
- 引入最小集合的可替换依赖抽象(`ICliConsole` / `IProcessRunner`),隔离外部副作用,支持测试注入与 deterministic 测试。
11+
- 调整 `Modulus.Cli.IntegrationTests`:优先使用 **in-process** 执行路径(调用 handler/entrypoint),避免子进程导致覆盖率丢失。
12+
- 增加覆盖率 gate:`Modulus.Cli\` 关键路径行覆盖率目标 **>= 95%**(不要求覆盖外部工具链自身,如 `dotnet build` 的真实编译行为)。
13+
- 为测试与自动化提供路径隔离:通过环境变量 `MODULUS_CLI_DATABASE_PATH` / `MODULUS_CLI_MODULES_DIR` 覆盖 CLI 使用的数据库与模块目录。
14+
15+
## Impact
16+
- **Affected specs**: `cli-testing` (MODIFIED)
17+
- **Affected code** (expected):
18+
- `src/Modulus.Cli/Program.cs`
19+
- `src/Modulus.Cli/Commands/*`
20+
- `src/Modulus.Cli/Commands/Handlers/*`
21+
- `tests/Modulus.Cli.IntegrationTests/*`
22+
- `build/*`(覆盖率采集与汇总脚本/配置)
23+
24+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: CLI Command Tests
4+
5+
CLI 集成测试 SHALL 覆盖所有 CLI 命令的基本功能,并优先使用 in-process 执行路径以确保覆盖率可统计与执行稳定。
6+
7+
#### Scenario: Commands can be executed in-process for coverage
8+
- **WHEN** 运行 `Modulus.Cli.IntegrationTests`
9+
- **THEN** `new/build/pack/install/uninstall/list` 的主路径通过 in-process 执行(handler/entrypoint 调用)
10+
- **AND** 不依赖子进程执行 `modulus.dll` 来覆盖核心逻辑
11+
12+
#### Scenario: Process-based end-to-end tests are allowed but minimal
13+
- **WHEN** 需要验证 CLI 产物可执行(例如 `modulus.dll` 可被 `dotnet` 启动)
14+
- **THEN** 允许保留少量进程外端到端测试
15+
- **AND** 这些测试不作为覆盖率 gate 的主要来源
16+
17+
### Requirement: CLI Test Coverage Gate
18+
19+
关键组件 `Modulus.Cli\` 行覆盖率 MUST 达到 95% 以上。
20+
21+
#### Scenario: Coverage gate fails when below threshold
22+
- **WHEN** 运行覆盖率汇总脚本(Cobertura)
23+
- **THEN** 如果 `Modulus.Cli\` 行覆盖率 < 95%
24+
- **AND** 构建/CI 失败并输出最低覆盖文件列表
25+
26+
### Requirement: CLI Command Handler Architecture
27+
28+
CLI 命令实现 SHALL 采用 “薄 Command + 厚 Handler” 的结构以支持可测试性与依赖注入。
29+
30+
#### Scenario: Command delegates to handler
31+
- **WHEN** 用户执行任意 CLI 命令(例如 `modulus pack`
32+
- **THEN** Command 层仅负责参数解析与调用
33+
- **AND** 核心业务逻辑位于对应 handler 类中
34+
35+
#### Scenario: Handlers depend on injectable abstractions
36+
- **WHEN** handler 需要使用 Console/IO/Process 等副作用能力
37+
- **THEN** handler 通过可注入抽象接口访问这些能力
38+
- **AND** 测试可替换为 fake 实现以实现 deterministic 行为
39+
40+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## 1. Spec
2+
- [x] 1.1 创建 `refactor-cli-command-architecture` change(proposal/tasks/design)
3+
- [x] 1.2 增加 `cli-testing` delta spec(MODIFIED requirements)
4+
- [x] 1.3 `openspec validate refactor-cli-command-architecture --strict` 通过
5+
6+
## 2. Implementation
7+
- [x] 2.1 增加 CLI 架构基座(handlers + DI 组装 + 抽象接口)
8+
- [x] 2.2 迁移 `new/build/pack` 到 handler 架构并保持行为不变
9+
- [x] 2.3 迁移 `install/uninstall/list` 命令入口,使其与 handler 统一(保留现有 handler 能力)
10+
- [x] 2.4 将 `Modulus.Cli.IntegrationTests` 调整为 in-process 执行主路径(覆盖率可统计)
11+
- [x] 2.5 补齐单元/集成测试分层与关键用例
12+
- [x] 2.6 覆盖率 gate:`Modulus.Cli\` 行覆盖率 >= 95%
13+
- [x] 2.7 全量 `dotnet test Modulus.sln -c Release` 通过
14+
15+

openspec/specs/cli-testing/spec.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ CLI 集成测试 SHALL 在隔离环境中运行,不影响真实用户数据。
2121
- **THEN** CLI 服务使用指定的数据库路径
2222
- **AND** CLI 服务使用指定的模块目录
2323

24+
#### Scenario: CLI supports path overrides via environment variables
25+
26+
- **WHEN** 设置环境变量 `MODULUS_CLI_DATABASE_PATH``MODULUS_CLI_MODULES_DIR`
27+
- **THEN** CLI 使用该数据库路径与模块目录执行 `install/uninstall/list`
28+
- **AND** 测试可通过设置这些变量实现 in-process 隔离而不写入真实用户目录
29+
2430
### Requirement: Database Schema Consistency
2531

2632
测试数据库 schema SHALL 与 Host 应用数据库 schema 保持一致。
@@ -34,7 +40,19 @@ CLI 集成测试 SHALL 在隔离环境中运行,不影响真实用户数据。
3440

3541
### Requirement: CLI Command Tests
3642

37-
CLI 集成测试 SHALL 覆盖所有 CLI 命令的基本功能。
43+
CLI 集成测试 SHALL 覆盖所有 CLI 命令的基本功能,并优先使用 in-process 执行路径以确保覆盖率可统计与执行稳定。
44+
45+
#### Scenario: Commands can be executed in-process for coverage
46+
47+
- **WHEN** 运行 `Modulus.Cli.IntegrationTests`
48+
- **THEN** `new/build/pack/install/uninstall/list` 的主路径通过 in-process 执行(handler/entrypoint 调用)
49+
- **AND** 不依赖子进程执行 `modulus.dll` 来覆盖核心逻辑
50+
51+
#### Scenario: Process-based end-to-end tests are allowed but minimal
52+
53+
- **WHEN** 需要验证 CLI 产物可执行(例如 `modulus.dll` 可被 `dotnet` 启动)
54+
- **THEN** 允许保留少量进程外端到端测试
55+
- **AND** 这些测试不作为覆盖率 gate 的主要来源
3856

3957
#### Scenario: New command creates module
4058
- **WHEN** 执行 `modulus new -n TestModule --force`
@@ -68,6 +86,32 @@ CLI 集成测试 SHALL 覆盖所有 CLI 命令的基本功能。
6886
- **WHEN** 执行 `modulus list --verbose`
6987
- **THEN** 额外显示模块 ID、发布者等详细信息
7088

89+
### Requirement: CLI Test Coverage Gate
90+
91+
关键组件 `Modulus.Cli\` 行覆盖率 MUST 达到 95% 以上。
92+
93+
#### Scenario: Coverage gate fails when below threshold
94+
95+
- **WHEN** 运行覆盖率汇总脚本(Cobertura)
96+
- **THEN** 如果 `Modulus.Cli\` 行覆盖率 < 95%
97+
- **AND** 构建/CI 失败并输出最低覆盖文件列表
98+
99+
### Requirement: CLI Command Handler Architecture
100+
101+
CLI 命令实现 SHALL 采用 “薄 Command + 厚 Handler” 的结构以支持可测试性与依赖注入。
102+
103+
#### Scenario: Command delegates to handler
104+
105+
- **WHEN** 用户执行任意 CLI 命令(例如 `modulus pack`
106+
- **THEN** Command 层仅负责参数解析与调用
107+
- **AND** 核心业务逻辑位于对应 handler 类中
108+
109+
#### Scenario: Handlers depend on injectable abstractions
110+
111+
- **WHEN** handler 需要使用 Console/Process 等副作用能力
112+
- **THEN** handler 通过可注入抽象接口访问这些能力(例如 `ICliConsole`, `IProcessRunner`
113+
- **AND** 测试可替换为 fake 实现以实现 deterministic 行为
114+
71115
### Requirement: Module Load Verification
72116

73117
CLI 集成测试 SHALL 验证生成的模块能被 `ModuleLoader` 正确加载。

0 commit comments

Comments
 (0)