应该增加一套分层测试体系,而不是只新增一个“大而全”的测试项目。并且不要把现有的MaxWell.Driver.Test当成单元测试工程,它目前是可部署的测试驱动程序集,不是dotnet test项目。
核心目标应是:每一条业务需求、每个安全联锁、每种协议报文、每个故障恢复场景,都有可重复执行的验证方式。
测试结构
tests/ ├─ MaxWell.Core.Tests/ 状态机、命令、结果模型 ├─ MaxWell.Workflow.Tests/ 工站流程、互锁、取消、恢复 ├─ MaxWell.Communication.Tests/ TCP、串口抽象、超时、重连 ├─ MaxWell.Automation.Tests/ JSON/TCP 协议解析与幂等 ├─ MaxWell.SecsGem.Tests/ SECS/GEM 指令与状态映射 ├─ MaxWell.Persistence.Tests/ SQLite、CSV、断电恢复、Outbox ├─ MaxWell.Configuration.Tests/ Stations.json、PLC.csv、配方校验 ├─ MaxWell.IntegrationTests/ 模拟多工站端到端流程 └─ MaxWell.HardwareTests/ 实验台真实硬件测试,不进普通 CI测试层级应如下:
真实硬件在环测试 少量、受控、实验室执行 端到端模拟测试 完整生产流程 组件集成测试 TCP / SQLite / 文件 / 协议 纯单元测试 状态机、校验、转换、工艺规则首先需要的架构调整
当前项目中存在DeviceManager.Instance、UniverseData、LogHelper、DateTime.Now、AppContext.BaseDirectory、MessageBox等全局依赖。它们会让测试互相污染,也无法构造稳定场景。
测试前应优先抽象以下接口:
IClock// 替代 DateTime.NowIFileSystem// 替代 File / Directory 静态调用IStationDeviceFactory// 创建设备或模拟设备IPlcDriver// PLC 读写边界IMainBoardClient// 主控板边界IAlarmSink// 报警输出IOperationJournal// 审计与恢复记录IUserContext// 当前登录用户与权限例如PLCController当前直接依赖具体AuxPLC,测试异常读写、寄存器变化和断线会很困难。应让控制器面向IPlcDriver,再用真实AuxPLC或FakePlcDriver注入。
StationWorkflowStateMachine、StationCommandMailbox等内部类可以在程序集增加:
[assembly:InternalsVisibleTo("MaxWell.Workflow.Tests")]这样可以测试内部核心逻辑,而不必为了测试把实现类暴露为 public。
每个核心模块的测试重点
| 模块 | 应测试内容 |
|---|---|
| 工站状态机 | 所有合法和非法状态迁移、状态版本递增、事件仅触发一次、停止/复位/恢复边界 |
| 命令邮箱 | 同站串行、跨站并行、高优先级复位取消普通命令、关闭时排队命令的结果 |
| PLC 控制器 | 快照有效性、断线后旧快照失效、批量读取、地址映射、写入重试、轮询异常限频 |
| TCP 通信 | 半包、粘包、远端关闭、超时、重连、部分发送、并发读写隔离 |
| 主控板协议 | 帧校验、校验和、非法长度、超时、迟到响应、命令与响应关联 |
| 自动化协议 | JSON 格式错误、字段缺失、未知命令、重复 SN、响应回放、会话断开 |
| SECS/GEM | SxFy 解析、控制状态、远程命令许可、S1F3 状态变量顺序、异常回包 |
| 数据文件 | CSV 转义、异步队列背压、文件发布失败、临时文件恢复、重复写入 |
| SQLite | 新库建表、迁移、事务回滚、并发写、数据库锁、损坏数据库的安全降级 |
| 配置 | JSON/INI/CSV Schema、重复工站、无效端口、重复 PLC 地址、配方越界 |
| 权限 | 操作员不能复位/改配方,工程师不能管理用户,协议调用不能绕过 UI 权限 |
| 恢复 | 程序中断、未完成产品、重新启动、人工清料、恢复确认后的状态变化 |
当前 TestDataDbService.cs 中InitializeTable()被注释,应新增“空目录首次运行可建库”的集成测试,防止新机部署后首条数据失败。
先实现的 12 个高价值测试
- 工站不能从
Offline直接进入WorkflowRunning。 Reset到达时,尚未执行的普通SOT命令必须被取消。- PLC 断线后,流程不能使用旧快照继续测试。
- PLC 一次读取失败后,快照必须标记为
IsValid = false。 - TCP
Send只发送部分字节时,驱动必须继续发送至完整报文。 - TCP 对端关闭后,下一条安全命令可重连,非幂等命令不得盲目重试。
- Automation 同一
Station + SN + Command重复提交时,不得重复驱动硬件。 - SECS/GEM 在
OnlineLocal状态下必须拒绝远程控制命令。 - CSV 发布失败时,缓存文件必须保留且可重试发布。
- SQLite 新建数据库时必须完成 schema 初始化和索引创建。
- 应用异常退出后,存在未完成操作时工站必须进入
RecoveryRequired。 - 未授权用户即使通过 Automation/EAP 入口提交命令,也必须被拒绝并审计。
测试项目配置
由于当前公共构建配置默认使用net8.0-windows和 WPF,第一阶段测试项目可以同样定位到 Windows:
<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net8.0-windows</TargetFramework><IsTestProject>true</IsTestProject><UseWPF>false</UseWPF><Nullable>enable</Nullable></PropertyGroup><ItemGroup><ProjectReferenceInclude="..\..\MaxWell.Driver.M\MaxWell.Driver.M.csproj"/><PackageReferenceInclude="Microsoft.NET.Test.Sdk"/><PackageReferenceInclude="xunit"/><PackageReferenceInclude="xunit.runner.visualstudio"PrivateAssets="all"/><PackageReferenceInclude="coverlet.collector"PrivateAssets="all"/></ItemGroup></Project>包版本应统一放到Directory.Packages.props。目前根目录的Directory.Build.props和Directory.Packages.props都维护了部分包版本,开测前应先统一,避免测试与生产引用不同版本。
示例:状态机测试风格
publicsealedclassStationWorkflowStateMachineTests{[Theory][InlineData(StationButtonState.Offline,StationButtonState.Initializing,true)][InlineData(StationButtonState.Offline,StationButtonState.WorkflowRunning,false)][InlineData(StationButtonState.WorkflowRunning,StationButtonState.Resetting,true)]publicvoidTransition_should_follow_lifecycle_rules(StationButtonStatecurrent,StationButtonStatenext,boolexpected){varresult=StationWorkflowStateMachine.CanTransition(current,next);Assert.Equal(expected,result);}}测试名称建议统一使用:
Given_前置条件_When_执行动作_Then_预期结果例如:
Given_plc_snapshot_is_invalid_When_start_test_Then_command_is_rejected Given_duplicate_automation_sn_When_sot_received_Then_hardware_is_invoked_once Given_database_write_fails_When_test_completed_Then_station_enters_data_storage_fault模拟器比 Mock 更重要
工业软件仅靠 Mock 不足以验证流程。需要提供可执行的模拟设备:
FakePlcDriver ├─ 预置寄存器值 ├─ 模拟地址变化 ├─ 模拟超时、断线、错误码 └─ 记录写命令顺序 FakeMainBoardClient ├─ 模拟测试启动 ├─ 模拟采样数据 ├─ 模拟迟到响应 └─ 模拟板卡报警 FakeBarcodeScanner ├─ 成功扫码 ├─ 空码 ├─ 重复码 └─ 超时 FakeLoadUnitDriver ├─ 正常状态 ├─ 温度异常 ├─ 通信失败 └─ 运行中断有了这些模拟器,就可以运行完整流程:
开批 → 上料 → 扫码 → 预测试 → 正式测试 → 数据落库 → CSV 发布 → 下料 → 结批这类测试放在MaxWell.IntegrationTests,不连接真实硬件。
UI 和硬件测试策略
WPF 不应成为最先投入自动化的层。优先测试 ViewModel、命令和状态绑定;页面本身只做少量冒烟测试。
真实硬件测试必须独立:
MaxWell.HardwareTests只允许在实验台运行,并通过环境变量显式启用:
RUN_HARDWARE_TESTS=true普通开发机、PR CI、发布 CI 一律不运行硬件测试,避免误动作。
CI 门禁
当前完整MaxWell.sln受 .NET Framework 工具项目影响,不能稳定构建。建议新增MaxWell.CI.sln,先只包含 SDK 风格项目和测试项目;旧工具迁移完成后再回归主解决方案。
每次 Pull Request 至少执行:
dotnet restore dotnet build MaxWell.CI.sln -c Release dotnet test MaxWell.CI.sln -c Release dotnet test --collect:"XPlat Code Coverage"生成:
trx测试报告- Cobertura 覆盖率报告
- 构建日志
- 失败时的协议报文、SQLite 数据库和流程事件日志
建议指标:
- 状态机、互锁、权限、协议解析:分支覆盖率 100%
- 新增流程功能:必须带自动化测试
- 核心 Runtime:逐步达到 80% 行覆盖率、70% 分支覆盖率
- 每个已修复生产问题:必须新增回归测试
- UI 覆盖率不作为主指标,业务与安全行为覆盖率优先
实施顺序
- 新建
tests/、xUnit 基础设施、CI 专用解决方案。 - 先测状态机、命令邮箱、配置解析和协议解析等纯逻辑。
- 抽象 PLC、文件、时钟、数据库和用户上下文,移除关键流程对静态对象的直接依赖。
- 加入模拟 PLC、主控板、扫码器,完成工站端到端模拟流程。
- 增加 SQLite、CSV、恢复和断电场景。
- 最后增加 WPF 冒烟测试与实验台硬件在环测试。
“测试每个功能”不应理解为测试每一行私有实现,而应建立需求到测试的追踪表:每项功能、每条安全规则、每种报警、每个外部协议命令都至少有一个可重复的自动化验收测试。