# jtestdemo **Repository Path**: chuanghou/jtestdemo ## Basic Information - **Project Name**: jtestdemo - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-17 - **Last Updated**: 2026-05-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jtestdemo `jtestdemo` 是一个 Java 多模块本地联调示例工程,测试框架能力由 `jtestlib` 提供。 当前仓库通过 git submodule 引入 `jtestlib`,用于演示如何配置 suite、快速启动所需模块、按场景回归验证。 ## 仓库关系 - `jtestdemo`: 业务示例工程(你现在看的这个) - `jtestlib`: 通用测试框架库(独立仓库) - 关系:`jtestdemo/jtestlib` 是 `jtestlib` 的 submodule ## 拉取与初始化 首次克隆 `jtestdemo`(包含子模块): ```bash git clone --recurse-submodules git@gitee.com:chuanghou/jtestdemo.git ``` 如果已经 clone 过,再初始化子模块: ```bash git submodule update --init --recursive ``` ## 常用命令(在 jtestdemo 内) ```bash python test/jtest.py -k # kill 所有运行模块端口段 python test/jtest.py -d # 编译 + install 辅助模块 + copy 运行模块依赖 python test/jtest.py -kd # kill 后做依赖准备 python test/jtest.py java_integration # 直接跑单套件(Java 侧一站式集成) python test/jtest.py -c java_integration # 先编译再跑单套件 python test/jtest.py -a # 跑全部套件(会先执行 -kd) python test/jtest.py -e staging java_integration # 指定 jtest 环境名 staging(见下文「运行环境」) ``` ## 日志处理方案(Python + Java) 为避免 Python 日志与 Java 子进程日志在控制台并发输出时互相打散,`jtestlib` 采用了“生产者-消费者”模式做统一日志分发。 ### 设计目标 - 保证单条日志原子输出,不被其他线程切割。 - Java `stdout` / `stderr` 可区分来源,便于排查异常。 - 在本地测试场景优先保证可读性与稳定性,而非极致性能。 ### 当前实现 - Java 子进程仍由 `subprocess.Popen` 拉起,`stdout` / `stderr` 通过两个读取线程持续读取。 - 读取线程不再直接 `print`,而是将日志事件投递到全局队列。 - 日志分发线程单线程消费队列,统一执行: - 写入 suite 日志文件(按行 `flush`) - 默认实时输出到控制台 - 日志行格式示例: ```text 18:09:23.417 [J][gateway:0][STDERR] java.lang.RuntimeException: ... ``` 其中: - `18:09:23.417`:毫秒时间戳 - `[J]`:Java 日志来源标识 - `[gateway:0]`:服务名与节点号 - `[STDOUT/STDERR]`:标准输出或错误输出流 ### 为什么能改善“日志重叠” 关键不是“统一前缀”,而是“统一出口”。 多个生产者线程并发读取没问题,但最终只允许一个消费者线程输出,能够避免多线程同时写控制台导致的行内交错。 ### 退出与边界说明 - 框架已在 suite 执行结束路径尝试 `flush` 日志队列,并注册 `atexit` 做进程退出前收尾。 - 正常执行场景下,日志完整性较高。 - 若进程被外部强制终止(如 `taskkill /F`、直接关闭终端),仍可能丢失最后极少量日志(属于操作系统级强杀边界)。 ### 使用建议 - 日常回归:控制台会实时打印,同时可结合 `test//*.log` 回看完整日志。 ## 全局测试配置层(application-jtest.yml) 为减少各个 suite 的重复配置,框架支持在 `test/` 根目录放置全局测试配置文件: - `test/application-jtest.yml` - 或 `test/application-jtest.yaml` 该文件是可选的,不存在时不会影响启动。 ### 配置优先级 运行时配置优先级如下(后者覆盖前者;中间可插入「环境」层,见下一节「jtest 运行环境」): - `application.yml` - `application-jtest.yml`(profile `jtest`,可选:仅当 `test/` 下存在该文件时才会激活此 profile) - `application-.yml`(profile 为 `-e` 指定的环境名,默认 `jtest`;与 `jtest` 同名时不重复激活) - `application---.yml`(suite + 节点层) 实现方式是启动参数 `spring.profiles.active` 自动按需组装(自左向右,后者覆盖前者): - 有 `application-jtest.yml`(或 `.yaml`):`jtest,,--`(若 `` 为 `jtest`,则只出现一次 `jtest`) - 无全局 jtest 文件:`,--` ### 与现有节点配置机制的关系 - 现有节点级 yml 机制保持不变,仍要求 suite 内提供: - `application---.yml` - `application-jtest.yml` 只是新增一层“全局公共覆盖”,用于放通用测试配置。 ### classpath 资源读取 运行时 classpath 现在包含: - `test//`(suite 目录) - `test/`(测试根目录) 因此你可以把全局资源文件(不仅是 yml)放在 `test/` 下,并在 Java 里按 classpath 资源方式直接读取。 ### 最小示例(全局 + suite 节点覆盖) `test/application-jtest.yml`(全局公共配置): ```yaml demo: timeout-ms: 3000 enable-mock: true ``` `test/java_integration/application-gateway-java_integration-0.yml`(suite + 节点覆盖): ```yaml demo: timeout-ms: 8000 ``` 启动 `java_integration` 套件的 `gateway:0` 时,最终效果: - `demo.enable-mock = true`(来自全局层) - `demo.timeout-ms = 8000`(被节点层覆盖) 这样可以把跨 suite 通用配置统一放在 `test/application-jtest.yml`,只把差异项留在各 suite 节点 yml 里。 ## jtest 运行环境(`-e`)与 `infra_ops_by_env` 本地联调时,除了「全局测试层 + 各 suite 节点」之外,还可以按**环境**再拆一层:同一套 suite,在不同环境(库、SSH、Spring 覆盖配置)下复用。 ### CLI 参数 `-e` / `--jtest-env` - 含义:当前 **jtest 环境名**(字符串,默认 **`jtest`**;不指定 `-e` 时即为 `jtest`)。 - 入口会把该值写入子进程环境变量 **`JTEST_ENV`**;套件脚本里 `run_suite` 创建的 `SuiteContext` 会带上 `jtest_env`,也可在套件内通过 `get_active_suite_context().jtest_env` 读取。 - 若直接执行 `python test//test.py` 而未经过 `test/jtest.py`,可事先设置环境变量 `JTEST_ENV`,未设置时仍按 `jtest` 处理。 示例: ```bash python test/jtest.py -e staging java_integration python test/jtest.py -c java_integration -e dev python test/jtest.py -a -e staging python test/jtest.py -db default -e staging # 实体生成连库也使用 staging 对应 infra ``` ### Spring Profile 与 `application-.yml` - 框架会在 `spring.profiles.active` 中、在 profile **`jtest`(若存在全局 `application-jtest.yml`)与「`--`」之间**,再插入一段 **环境 profile**,名字等于 `-e` 的值(默认 `jtest`)。 - 将环境相关覆盖放在 **`test/application-.yml`**(例如 `-e staging` → `test/application-staging.yml`)即可;`test/` 已在运行时 classpath 中,Spring Boot 会按惯例加载。 与「全局测试配置层」一节的 profile 组装规则一致;默认 `jtest` 与全局层的 `jtest` profile 重名时不会重复追加。 ### `test/jtest.py` 中的 `infra_ops_by_env` - `ProjectConfig` 使用字段 **`infra_ops_by_env`**:`Mapping[str, InfraOpsConfig]`,**key 与 `-e` 的环境名一致**。 - 默认应至少提供 **`jtest`** 这一条;执行时若当前环境在 map 中没有精确 key,会**回退到 `jtest`** 对应配置。 - MySQL / SSH / SFTP(`mysql_query_all`、`ssh_exec` 等)以及 `jtest_orm.BaseMapper`、`-db` 实体生成在连库时,都会按**当前 `jtest_env`** 解析对应的 `InfraOpsConfig`。 示例(节选): ```python PROJECT = ProjectConfig( # ... infra_ops_by_env={ "jtest": InfraOpsConfig(mysqls={...}, remotes={...}), "staging": InfraOpsConfig(mysqls={...}, remotes={...}), }, ) ``` 需要按环境拆配置时,在 `infra_ops_by_env` 增加与 `-e` 同名的 key 即可。 ### 库侧 API - `jtestlib.resolve_infra_ops(project, env)`:按环境名解析 `InfraOpsConfig`(先精确匹配,再回退 `jtest`)。 ## Entity 生成与 Builder 用法 通过数据库表结构一键生成实体文件。命令入口: ```bash python test/jtest.py -db [mysql_name] ``` ### `-db` 参数规则 - `-db` 后面的 `mysql_name` 是可选参数。 - 如果你的项目只有一个 MySQL 配置(通常名为 `default`),可以直接执行: ```bash python test/jtest.py -db ``` - 上面等价于: ```bash python test/jtest.py -db default ``` - 如果你配置了多个 MySQL(例如 `default`、`order`、`risk`),则建议显式指定: ```bash python test/jtest.py -db order python test/jtest.py -db risk ``` ### 生成文件命名规则 - 当 `mysql_name=default`(包括仅写 `-db` 的情况): - 输出文件:`test/entity.py` - 当 `mysql_name` 为其他名字(例如 `order`): - 输出文件:`test/order_entity.py` 这意味着多数据库场景下,每个库会生成不同文件名,避免互相覆盖。 ### 生成内容说明 - 会按当前数据库中的表结构生成 dataclass 实体。 - 每个实体内会生成: - `builder()` 方法 - Builder 链式设置方法 - 一段可直接复制的 Builder 快速模板注释(你只需要改参数值) - 一段可直接复制的 `BaseMapper` / `QueryWrapper` / `UpdateWrapper` 快速模板注释(按需改字段和值) 生成后的实体支持两种初始化方式: ```python from test.entity import JtestMpUser # 1) dataclass 直接构造 u1 = JtestMpUser(username="alice", age=20, email="a@example.com") # 2) Java 风格 builder 链式构造 u2 = ( JtestMpUser.builder() .id(1001) .username("bob") .age(22) .email("b@example.com") .deleted(0) .build() ) ``` 说明: - 生成代码会带上数据库表注释和字段注释,便于阅读。 - 字段类型采用 `Optional[...] = None`,兼顾可读性与类型语义一致性。 ## 在其他项目中使用 jtestlib(推荐) 下面给你一套可直接复用的流程。 ### 1) 在你的项目里接入 submodule ```bash git submodule add https://gitee.com/chuanghou/jtestlib.git jtestlib git submodule update --init --recursive ``` ### 2) 新建测试目录结构 建议最小结构: ```text your-project/ jtestlib/ test/ jtest.py demo_suite/ test.py application--demo_suite-0.yml ``` 也可以一键初始化(推荐): ```bash python jtestlib/init_project.py --project-root . --suite demo_suite ``` 如果需要覆盖已存在文件: ```bash python jtestlib/init_project.py --project-root . --suite demo_suite --force ``` ### 3) 编写 `test/jtest.py` 在 `test/jtest.py` 里: - 配置 `ProjectConfig`(运行模块、辅助模块、main class、port base、`infra_ops_by_env` 等) - 保持 `bootstrap_paths()`,确保能 import `jtestlib` - 直接复用 `-k/-d/-kd/-c/-a/-e/` 命令语义(`-e` 见上文「jtest 运行环境」) 你可以直接参考本仓库现成文件:`test/jtest.py`,或直接复制模板:`test/jtest.py.template`。 ### 4) 复制一个 suite 模板 从本仓库任意套件复制一个最小模板,例如 `test/java_integration/test.py`,或直接使用模板 `test/suite_test.py.template`,把: - `module_nodes` - 接口路径 - 断言逻辑 改成你自己的业务即可。 ## 子模块升级与协作 ### 在 `jtestdemo` 中升级 `jtestlib` ```bash # 1) 拉到子模块最新提交(仅更新工作区里的子模块指针) git submodule update --remote jtestlib # 2) 进入子模块检查状态(常见为 detached HEAD) cd jtestlib git status # 3) 如果只是“消费上游版本”,保持 detached HEAD 即可,回主仓库提交指针 cd .. git add jtestlib git commit -m "upgrade jtestlib" git push ``` ### 如果你要修改 `jtestlib` 代码(重点:避免 detached HEAD 直接开发) `git submodule update` 后子模块常处于 detached HEAD。 该状态下可以 commit,但提交不挂在分支上,协作时容易出现“主仓库指向了别人拉不到的 SHA”。 推荐流程: ```bash # 在子模块里切到分支再开发 cd jtestlib git switch -c feat-xxx # 或 git switch main/develop(按实际分支) # 开发、提交、推送子模块 git add . git commit -m "feat: xxx" git push -u origin HEAD # 回主仓库提交“子模块指针更新” cd .. git add jtestlib git commit -m "chore: bump jtestlib pointer" git push ``` 协作顺序务必是:**先 push 子模块提交,再 push 主仓库指针提交**。 ### 在其他机器上同步 ```bash git pull git submodule update --init --recursive ``` ## 迁移提示 - 如果遇到 `ClassNotFound` 或服务启动失败,优先执行: - `python test/jtest.py -kd` - `mvn clean compile -DskipTests`(包名/类名迁移后尤其建议) - 框架已内置运行前自检,会提示缺失的 `target/classes` 和 `libs` 目录。 ## 团队协作检查清单(子模块) - 在子模块里改代码前,先确认不在 detached HEAD;需要开发时先 `git switch -c `。 - 子模块代码提交后,先把子模块分支 push 到远端,确保目标 SHA 可被拉取。 - 回到主仓库后再提交 `jtestlib` 指针更新(`git add jtestlib && git commit`)。 - 推送顺序固定为:先子模块仓库,再主仓库;不要反过来。 - 其他机器同步后,统一执行 `git submodule update --init --recursive` 校准子模块状态。