服务器测试与本地数据库

本文件是 talent-card-server 的测试约定。目标是让**新会话**不需要重新摸索环境:一条命令跑测试,
一条命令重置库,并且不重蹈已知的坑。

环境一览(2026-09-17 实测)

库 版本 Flyway 谁在连
开发库 192.168.110.13:3306/talent_card(局域网共享) MySQL 5.7.44 关(flyway.enabled=false) 后端、后台管理页、两个小程序
测试库 127.0.0.1:8134/talent_card_test(本机实例) MySQL 8.0.36 开 本文件的测试脚本

三条由此推出的硬约束:

  • 迁移必须同时兼容 5.7 与 8.0:测试只在 8.0 上跑,跑绿不代表开发库能执行(V151 的存储过程写法
    已在 5.7 上实跑验证过,但每次新增迁移都值得照做一遍)。
  • 开发库的结构不由 Flyway 维护:缺表缺列只能靠启动时的初始化器 best-effort 补,补不上就手工补(见下)。
  • 本机 8134 实例上另有 talent_card、talent_card_dryrun、talent_card_verify1 三个库:它们是
    2026-09-16 把本机库并入服务器库**之前**的本机副本,**不再是开发库**,不要拿它们判断开发库状态。
    早先会话留在 .scratch/ 下的一次性探针(*Probe*.java、Inspect*.java、Apply*.java 等)大多把 URL
    写死成 127.0.0.1:8134/talent_card,指的就是这份旧副本;复用时要显式传开发库 URL,别照默认值跑。

一条命令跑测试

cd talent-card-server
.\scripts\run-server-tests.ps1                              # 全量
.\scripts\run-server-tests.ps1 -Test PaymentSettlementTests # 单个类
.\scripts\run-server-tests.ps1 -Test A,B                    # 多个类
.\scripts\run-server-tests.ps1 -Clean -Test A               # 构建产物可疑时先 clean
.\scripts\run-server-tests.ps1 -Reset                       # 重置库后再跑

脚本会设置 TEST_DATASOURCE_*(MySQL 127.0.0.1:8134、talent_card_test、root/123456,
也可用环境变量覆盖),把日志写到 target/server-tests.log,并在失败时列出已知基线失败。

用 pwsh 跑(本机装了 PowerShell 7:C:\Program Files\PowerShell\7\pwsh.exe)。脚本也已在
Windows PowerShell 5.1 下实测通过——但如果 5.1 里出现"构建在编译中途莫名中断",先换 pwsh 再怀疑代码:
5.1 会把 Maven 打到 stderr 的警告(例如 JVM 的 sun.misc.Unsafe)当成终止错误。

另注:mvnw.cmd 是 batch/PowerShell 混合的 wrapper,它自己**总是**用 powershell(即 5.1)做引导,
与你在哪个 shell 里敲命令无关——这是 Apache Maven Wrapper 3.3.4 的既有实现,不要改成 pwsh。
(zcode 的 bash 工具跑在 Git Bash 里,PowerShell 只在显式点名或这个 wrapper 内部才出现。)

必须走这个脚本(或等价地只跑一个 Maven 进程)。 同一个 checkout 上并发跑两个构建会写坏
target/,症状是看起来不可能的报错——例如 No qualifying bean of type 'HuifuPaymentProperties'。
遇到这类"不可能"的 bean/classpath 错误,先 -Clean,再怀疑代码。

重置测试库

.\scripts\reset-test-db.ps1                    # 默认 talent_card_test
.\scripts\reset-test-db.ps1 -Database talent_card

脚本用项目已有的 MySQL JDBC 驱动(本仓库没有 mysql 客户端、没有 Docker),删掉该库里的全部表;
下一次测试由 Flyway 从 db/migration 重建。

不要 drop database,也不要只删 flyway_schema_history 里的某一行。
- drop database 之后重建会触发 BankBranchDirectoryInitializer 导入 10 万行网点数据(几分钟,
且曾观察到卡死)。测试 profile 已关闭该导入,所以上面的重置脚本不会再有这个代价。
- 手工删 history 行会让已执行的 DDL 与 Flyway 记录不一致,后续迁移报错更难查。

不要重建开发库

开发库是局域网共享的那一个(192.168.110.13:3306/talent_card,见上表),不是测试库。它里面有
一批**迁移从未创建**的表——当年是直接在库里手建的,例如 dependent_card_apply、merchant_benefit_rule。
所以「把测试库结构克隆到开发库」「reset-test-db.ps1 -Database talent_card」这类操作会把开发库里这些
表连同数据一起删掉:2026-09-13 就这样丢过一批,最后靠一份同源快照才回灌回来(reset-test-db.ps1
对名为 talent_card 的库一律拒绝,正是为此)。

注意开发库的 spring.flyway.enabled=false:它**不会**被迁移自动修正,缺表缺列只能手工补。

需要让开发库结构跟上迁移时:

  • 只补缺失的 DDL(create table if not exists、add column),**不要**删表重建。手工补 DDL 正是
    2026-09-17「点开收款能力就 500 / Unknown column 'capability_sync_until'」的解药。
  • 有一条命令可用了:.\scripts\apply-sql-file.ps1 -File <某个 .sql> -Yes 用项目已有的 JDBC 驱动
    逐条执行 SQL(默认连开发库,可 -Database/-DatabaseHost/-Port 覆盖;不带 -Yes 只打印不执行)。
    它认得 DELIMITER,所以 db/migration/V*.sql 也能原样重放。默认指向**开发库**,别拿它去改测试库
    (测试库由 Flyway 从 db/migration 重建,手工 DDL 会让迁移与 history 表对不上)。
    开发者首次拿到新列:2026-09-17 的 V152(支付流水两列)与 V153(手输核销码计数表)就是这样落地的,
    对应脚本 scripts/dev-schema-v152-payment-attempt.sql(写法可重复执行的那份在迁移里,两者等价)。
  • 动结构之前先备份。本机**没有** mysql 客户端、也没有 Docker(README 里指向 D:\MySQL\... 和
    docker exec talent-card-mysql-1 的两条老做法都已失效),要备份就在 DB 主机上做,或照
    scripts/reset-test-db.ps1 的办法用项目已有的 JDBC 驱动写个单文件程序。
  • 初始化器是 best-effort 的:它的 alter table 等 DDL 元数据锁的上限只有 5 秒,等不到就记一条
    error 放行(宁可少一列,也不能让启动被 DDL 挂住——2026-09-15 为此瘫过一次)。所以「重启过就一定有
    新列」不成立:启动日志里搜 Merchant onboarding schema: could not add,那里会给出可直接执行的手工补救语句。

迁移是只追加的

已经执行过的 V*__*.sql 不要改(改一个字符就会 checksum 不匹配,测试库直接起不来)。需要调整
就新增一个迁移文件,或用 reset-test-db.ps1 重建后再跑。历史上 scripts/repair-local-mysql-v*.sql
就是为此付出的代价。

已知基线失败(不是回归)

整套测试里有 5 项在基线上就失败,与本轮改动无关:前 3 项用 HEAD worktree + 同一测试库复现过,
后 2 行是连内存 H2 的用例(原因见表内)。

测试 现象
MerchantWorkServiceTests.filtersOrdersByCategoryStatusAndDateWithoutExposingFullMobile stays.total() 期望 1 实得 0
TalentAuthServiceTests.movesWechatBindingWhenSameWechatLogsInWithAnotherMobile insert into talent_session BadSqlGrammar
MerchantSettlementStatisticsMapperTests.includesHotelFreeOfflineSubsidyWithoutIncreasingTransactionAmount 持久化断言失败
ManualConsumptionServiceTests 2 项(childcareCurrentBenefitUsesTheScannedLevelsRightsDescription、cultureCurrentBenefitUsesOnlyTheScannedLevelsStoreConsumptionDescription) 连的是内存 H2,H2 里没建 merchant 表 → Table "merchant" not found。与测试库无关,与支付域改动也无关
MerchantVerificationFreeStayTests.scanReturnsConfiguredHotelSubsidyAmount 同上:H2 里 select merchant_type_code, merchant_rights_config from merchant 报表不存在

新会话不要为了证明它们"是既有的"而重建 worktree:直接看这张表。修好其中任何一项时,请删掉对应行。

最后两行是 2026-09-24 补的(跑全量时共 6 项失败:3 项基线 + 这 3 项):它们不在运行脚本打印的
known baseline failures 清单里,但全量与单独跑那 2 个类都同样失败。原因是这几个用例自己连内存 H2
(jdbc:h2:mem:...)而不是共享测试库,而 H2 里没有建 merchant 表——判断一次改动是否引入回归时,
先把它们排除;要修就得给这几条 H2 路径补上建表(属权益/手工消费那一域,跨域改动请交给对应负责人)。

环境相关失败:已修(2026-09-17)

PaymentFundConfirmationTests.aPendingConfirmationOfAVerifiedOrderIsRetriedWithoutAnotherVerification
原先会随测试库累积而**必然**失败:补偿任务扫全库、order by o.id asc limit 50,用例新建的那单 id 最大、
排在窗口之外。

2026-09-17 已按本文件的建议修好——该用例先把**自己之外**的相关行移出扫描条件,于是它从「环境相关失败」
变成稳定通过;同一天还把同一处扫描改为「每单只看最新一笔成功支付」,并把它扩展到 reverting(撤销核销
的回退在途)状态。以后新写这类「后台任务只处理了我这条」的用例,照它的做法先腾窗口,不要再依赖测试库的
累积状况。

测试库跑不起来时,先看 WSL(2026-09-17 实测)

测试库那个 8.0.36 实例跑在 WSL 的 docker 里(发行版 Ubuntu-26.04,容器 talent-card-mysql-1,
127.0.0.1:8134->3306)。WSL 虚拟机会在空闲时自己关掉,容器跟着停——于是测试全部报

Unable to obtain connection from database: Communications link failure
Caused by: java.net.ConnectException: Connection refused: getsockopt

或者更绕的一句(连接失败被 Hibernate 翻译过之后的表象):

Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' ...)

判断与恢复:

wsl.exe -d Ubuntu-26.04 -u root -- docker ps          # 容器在不在
wsl.exe -d Ubuntu-26.04 -u root -- docker start talent-card-mysql-1 talent-card-redis-1

光起容器不够:跑测试期间要有东西把 VM 挂住,否则命令一返回、VM 又空闲关掉,测试照样撞在停机缝里
(现象是容器每次 docker ps 都显示 Up 2 seconds)。用一个长命进程持有它,跑完再结束:

wsl.exe -d Ubuntu-26.04 -u root -- bash -lc "sleep 1800"   # 后台挂着,测试跑完再停

本机 Windows 侧没有 mysqld、没有 Docker——**别在 Windows 里找 8.0 实例**,它只在 WSL 里。

测试库的状态是共享的

Spring 测试共用一个 schema,测试之间不做清理(没有 @Transactional 回滚)。因此:

  • 断言要按自己创建的 ID / 单号过滤,不要断言"全表只有一行"。
  • 断言"后台任务只处理了我这条"时,先把自己之外的相关行挪出时间窗口(例如把历史
    payment_transaction.created_at 置为 now()),否则前面的用例会漏进来。
  • 需要真实 MySQL 语义(当前所有 Spring 测试都是)时,H2 只作为没有 MySQL 时的兜底。