
接手一片新项目的芯片测试程序版本管理我第一件事永远是打开目录结构先看一眼。不是不信任同事而是这个动作最能暴露一套程序到底有没有“版本管理的底子”。我就见过这样的项目文件夹叫final_final_v12src目录里躺着一堆.bin和.wavbuild目录里混着 Python 源码和编译出来的 DLL整个 Git 仓库膨胀到 900 多 MBpush 一次要等一杯咖啡的时间。芯片测试程序版本管理这件事说穿了没有多玄乎但凡是真刀真枪做过的人都懂第一关往往不是 Git 命令而是目录结构。目录放得不对后面再努力都是白费diff 看不清、发布不确定、换人交接直接懵。这篇是“芯片测试程序版本怎么管”系列的第二篇专门把目录结构里最容易绊倒人的“四道坎”掰开揉碎讲一遍。适合 ATE 测试工程师、实验室测试程序开发以及所有为了测试程序定版发愁的朋友。1. 先想清楚为什么芯片测试程序的目录结构“管不住”1.1 芯片测试程序跟普通软件项目的根本差别很多人一上来就套用互联网项目的 Git 规范结果处处碰壁原因在于芯片测试程序有它非常特殊的一面。第一它和硬件强绑定。普通软件跑在通用操作系统上逻辑对了就行芯片测试程序要对的是测试机台、探针台、分选机、温箱、DUT被测芯片同一个测试项在不同机台型号上可能有完全不同的时序参数和上下电要求。这意味着程序里的“环境依赖”比普通软件重得多。第二它的产物形态很杂。一套完整的测试程序通常包含源代码、编译后的可执行文件或算法文件、测试向量pattern、datalog 记录、良率报告、波形文件、校准数据、测试计划配置。这些文件有的是文本、有的是二进制、有的单个就几百兆。它们混在一起用传统源码管理思路很难收场。第三它同时服务“开发调试”和“量产出货”两个完全不同的场景。开发调试阶段程序天天改、跑一遍出一堆 log 和波形没人想清理量产出货阶段程序一旦定版就不能随便动厂里还要求能精确回溯到“当时产线跑的是哪一个版本”。这些差别放在一起结论只有一个目录结构必须是先在纸上想清楚的事否则版本管理从源头就是乱的。1.2 目录规划的三条底层原则我做了这么多年测试程序管理最后发现不管项目多复杂底层其实就三条原则。你把这三条立住了后面四道坎就都有的解。第一条单一入口原则。整套测试程序只有一个“顶层目录”这个目录叫什么不重要但所有人 clone 下来、解压开第一眼看到的应该是同一套骨架。不要出现“这个项目我有三个文件夹每个都叫 project”这种情况。单一入口是所有后续操作的地基。第二条顶层分层原则。顶层下面不要按“张三的目录”“李四的目录”分那是把人放在程序前面应该按职责分。我的习惯是固定分成src、config、tool、doc这几类各管各的谁也别越界。这样新人进来五分钟就能定位要找的东西。第三条路径无关原则。程序里绝对不能写死D:\Users\zhangsan\test\...这种绝对路径一旦写了死换台机器就铺不平。目录结构必须保证任何人拿下来放在任意位置都能跑最多改一个环境变量或者一个入口配置。这三条原则看起来简单但执行起来需要配套的目录设计也就是下面要拆的四道坎。每一道坎都是这三条原则在某个方向上的具体碰撞。2. 第一道坎代码和产物一锅炖版本库越推越重2.1 “一锅炖”长什么样风险在哪里这是我在各种客户现场见得最多的一类问题。测试程序的目录结构长这样project/ ├── test_main.py ├── test_main_20240101.py ├── test_main_20240115_final.py ├── test_main_final_v2.py ├── build/ │ ├── test_main.dll │ └── test_main_old.dll ├── output/ │ ├── log_20240101.txt │ ├── datalog_20240101.txt │ ├── waveform_001.wav │ └── result_report.xlsx ├── data/ ├── .git/ └── .vscode/源码、备份源码、编译产物、日志、报表全部平铺在一起。表面上“找什么都方便”实际上隐患非常大。第一个隐患是版本库快速膨胀。二进制日志、波形文件、DLL 每次编译都会变Git 会把每个历史版本都完整记下来仓库体积很快从几十 MB 涨到几个 GB。到了这个阶段clone 极慢push 还容易超时同事拉一次代码能喝三杯水。第二个隐患是 diff 失去意义。你在test_main.py里改了两行但因为同目录混着test_main_20240101.pyreview 的人根本分不清哪个是“当前有效版本”哪个是“以前的历史版本”。Git 的优势在于版本回溯结果你用文件名在做版本管理等于把 Git 当网盘用。第三个隐患是协作冲突。两个人同时跑测试日志都写到output目录然后都 commit 上去每次 pull 都提示冲突。这种冲突毫无技术含量纯粹是目录结构设计失误造成的。2.2 怎么拆src与build/out的二分法我的做法很朴素就是给“人写的代码”和“机器生成的东西”划出绝对边界。源代码包括 C、Python、头文件、测试项逻辑全部放在src下只有这里的文件需要被 Git 跟踪。凡是程序运行后自动生成的不管是编译产物、日志、datalog、波形还是报告默认都进build或者out目录并且明确告诉 Git“这个目录我不跟踪”。一个合理的骨架长这样project/ ├── src/ │ ├── common/ │ │ ├── gpib_helper.py │ │ └── dmm_control.cpp │ ├── test_items/ │ │ ├── power_up_test.py │ │ └── func_test.py │ └── main.py ├── config/ │ ├── testplan.yaml │ └── limits/ ├── tool/ ├── doc/ ├── out/ │ ├── log/ │ ├── datalog/ │ └── report/ └── .gitignore这里有一个很容易忽略的点out目录也放进项目骨架里但配上.gitignore让它“存在但不受版本控制”。为什么因为程序启动时总得有个地方写日志如果目录不存在很多程序直接报错而如果让它受控又会把垃圾收进来。正确做法是让out空目录保留在仓库里但内容全部 ignore。这样新同事 clone 下来目录结构是完整的一跑就能用又不会产生一堆无意义的提交。对于少数必须入库的“大文件”比如固定不变的测试向量、校准文件我建议默认不进 Git 仓库或者用 Git LFS 来管理。普通 Git 对二进制大文件的压缩和增量处理很差硬塞进去仓库迟早要出问题。2.3 配套实操PyCharm 用 SSH 登录 GitHub 做版本管理目录结构理顺了接下来就要把远程仓库的访问方式也理顺。很多测试工程师用 PyCharm 做开发却还在每次 push 的时候输用户名密码麻烦且容易输错。我建议直接配成 SSH 方式一劳永逸。先说为什么用 SSH 而不是 HTTPS。用 HTTPS 推送代码要么每次都输账号密码要么在 Windows 凭据管理器里缓存一个 token但那个 token 有有效期过期了又得重来。SSH 是基于密钥对认证的配好之后 push、pull、clone 全自动不用再跟密码纠缠。配起来其实就四步跟着做就行。第一步在本地生成一对密钥。打开终端执行ssh-keygen -t ed25519 -C your_emailexample.com一路回车会在~/.ssh/下生成id_ed25519私钥和id_ed25519.pub公钥。私钥千万别泄露公钥是要给别人看的。第二步把公钥内容复制出来登录 GitHub依次进入 Settings → SSH and GPG keys → New SSH key粘贴保存。cat ~/.ssh/id_ed25519.pub第三步本地验证是否连通ssh -T gitgithub.com如果看到类似Hi xxx! Youve successfully authenticated的提示就说明通了。第四步回到 PyCharm。在 Settings → Version Control → Git 里把 SSH executable 选成 Native原生 SSH然后在 clone 仓库时使用 SSH 格式的地址gitgithub.com:yourname/yourrepo.git之后你在 PyCharm 里正常 push、pull只要密钥没删就永远不用再输密码。这一个细节能极大降低大家“懒得往 GitHub 推”的心理门槛很多团队版本管理推不动不是因为不会 Git而是因为每次推送太麻烦。3. 第二道坎本机路径写死换台机器就铺不平3.1 路径写死的典型症状与根因第一道坎处理完之后团队开始有版控意识了但紧接着第二个问题就冒出来了代码能 clone 到任何一台机器上可一跑就报错。最常见的报错是FileNotFoundError或者 DLL 加载失败打开代码一看路径全是写死的。比如这种代码我见过太多次# 错误示范绝对路径写死 testplan open(D:/Users/zhangsan/project/config/testplan.yaml) pattern_path C:/ATE_data/pattern_a.bin这段代码在张小三的电脑上跑得好好的换到李小四的电脑上路径变成了E:/workspace/project啪崩了。更隐蔽的情况是路径写一半相对一半绝对# 错误示范混合路径 base D:/project log_path base /out/log/ time.strftime(%Y%m%d)这类问题本质上就是触犯了路径无关原则。测试程序是给团队用的、给产线用的不是给某一个人的个人软件路径写死等于把“换人维护”的路堵死了。3.2 改法相对路径加环境变量双管齐下解决路径问题的标准做法是“程序内部永远用相对路径”并且相对路径的基准点要统一。以 Python 项目为例我习惯在模块入口处把项目根目录找出来然后所有路径都从根目录往下拼# 正确示范 from pathlib import Path # 定位到项目根目录main.py 所在目录的上一级 PROJECT_ROOT Path(__file__).resolve().parent.parent CONFIG_DIR PROJECT_ROOT / config OUTPUT_DIR PROJECT_ROOT / out testplan_path CONFIG_DIR / testplan.yaml这样不管项目放在哪里只要整个目录结构是完整的程序都能跑。你需要保证的就是大家 clone 下来用的是一模一样的目录树这也是为什么第一道坎那么重要。还有一种情况要用环境变量。比如测试程序要引用一个存放在公共盘的通用算法库路径可能飘忽不定。这时候我建议定义一个环境变量比如TEST_HOME程序启动时从环境变量里取路径取不到再走默认值。在 Windows 上设置setx TEST_HOME D:/shared_assets在 Linux 上export TEST_HOME/opt/shared_assetsPython 侧读取import os SHARED_DIR os.environ.get(TEST_HOME, DEFAULT_PATH)逻辑很简单项目自己的文件用相对路径公共外部资源用环境变量永远不写死具体盘符和人名。3.3 约定一套“拿到就能跑”的规则代码改完之后还要在团队里定规矩。我的习惯是立三条规矩写进 README。第一条任何人 clone 仓库后第一步不是改代码而是先按 README 里的要求设置环境变量、安装依赖然后跑一次项目自带的 smoke test冒烟测试能跑通才算环境就绪。第二条程序运行产生的所有临时文件、日志、输出必须写到out目录下禁止随手写到桌面、系统盘或者和源码混在一起。这个习惯一开始就要用 .gitignore 从机制上兜住。第三条path 里禁止出现用户名、机器名、盘符等具体个人信息。凡是出现这种硬编码代码 review 直接打回重改。宁可多花十分钟改代码也不要让后面十个人花一天去排障。说句实在话路径问题在单个工程师本地开发时根本暴露不出来一旦开始团队协同马上就变成第一杀手。目录结构能统一路径逻辑必须跟着统一否则版本管理做到最后大家还是在互相发“解压到我这台机器上就报错”的求助信息。4. 第三道坎配置跟着代码飘版本里没有“参数开关”4.1 参数写死在代码里的痛点前两道坎处理完代码已经能跨机器跑了但接下来这个坎比较隐蔽很多人都没意识到它跟目录结构有关那就是“配置”和“代码”没有分开。我做咨询时见过一个典型项目测试程序的 limits上下限、bin 判定规则、测试项执行顺序、上电时序参数全部硬编码在 Python 文件里。出现什么情况呢芯片 PVT 条件一变、良率需要盯一段时间测试工程师直接打开.py源文件改数字。改完提交代码 diff 里经常出现“这行只是改了测试参数根本没改动逻辑”的噪音。更麻烦的是代码 review 的人为了确认这行参数改得对不对得把整个上下文读一遍。这还不是最糟糕的。最糟糕的是现场调参数的时候工程师根本来不及走 Git 提交流程直接在产线机器上改了源码然后把一份源码拷贝到共享盘备份。等回办公室想把改动同步到正式仓库发现已经忘了改过哪几个文件了。这个问题的本质是芯片测试程序不只是“程序”它更是一套“参数库”。真正的价值往往不全是代码逻辑而是那套根据芯片行为反复调参得到的测试方案。如果不把参数从代码里剥离开每一次调参都变成了一次代码变更版本管理必然失真。4.2 配置独立化的目录组织我的做法是把所有“可能变的参数”全部外置到一个独立的config目录代码里只留读取逻辑。还是上面那个例子骨架可以变成project/ ├── src/ │ ├── test_items/ │ └── main.py ├── config/ │ ├── testplan.yaml │ └── limits/ │ ├── power_up_limits.csv │ └── func_test_limits.csvtestplan.yaml里定义测试项顺序和执行开关tests: - name: power_up enabled: true samples: 128 bin_pass: 1 bin_fail: 8 - name: func_test enabled: true pattern: pattern_a.binlimts目录放上下限的表格每一行是“测试项 条件 min max”。这样调参数的人根本不需要碰代码只要改 YAML 或者 CSV然后重新跑一遍就行。代码逻辑一行不动版本管理里看到的变更就是清清楚楚的“参数变了”。这带来的另外一个好处是不懂代码的测试工程师也能参与维护。他们不需要理解 Python 的 class 和函数只需要按格式填表。这套机制让“程序开发人员”和“测试调参人员”的职责分开了版本管理的提交者也从“某些人乱改代码”变成“参数变更可追溯”。4.3 配置也得进版本管理而且要单独规范这里有一个非常多人做错的地方一说配置和代码分离就有人把配置存在本地、存在 FTP、甚至存在微信群里。这是本末倒置。配置不只是在运行期供程序读它同样是项目的核心资产必须进 Git 仓库和代码一起走评审、走版本。但方式有讲究。我建议配置和源码放在同一个仓库、不同目录而不是拆成两个仓库。因为配置和代码的版本需要强一致一个测试项的代码逻辑改了对应的参数文件通常也要配套改。放在同一个仓库才能保证“我 checkout 这个 commit拿到的代码和配置永远是配套的”。在提交规范上凡是配置文件的变更commit message 必须写清楚“改了哪个参数、为什么改、对应哪个批次或哪种条件”。我甚至建议加一条规则提交里同时包含src和config的改动时必须在 message 里说明两者的关联比如“func_test 增加低电压模式同步更新 limits 表中低压档的上下限”。这样处理之后整个项目才真正进入“参数可追溯”的状态。以后再有人问“这个版本的良率为什么会掉当时测试条件是什么”你只要 checkout 当时的 tag把config目录打开所有参数一目了然。5. 第四道坎公共库和项目库剪不断理还乱5.1 复制粘贴公共库的后患第四道坎是很多团队踩了无数次坑才反应过来的公共库common lib到底怎么放。芯片测试程序通常不是从零开发的。公司里往往已经有了一套基础库比如仪器控制封装、datalog 解析、bin 统计、和机台通信的底层接口。这时候出现了一个典型的懒惰操作把公共库的源码整个复制一份到自己的项目目录里然后直接开改。复制粘贴带来的后果非常严重。第一项目仓库里多出一大堆不属于这个项目逻辑的代码体积变大、review 变难第二公共库在总部修了个 bug你这边还是旧代码永远得不到修复第三过一段时间你会发现同一个公共库在公司内存在三四个不同的“副本”谁也说不清哪个是最新的。这种“代码分裂”比版本管理混乱还要命因为他意味着“团队的技术基础设施”正在无组织地膨胀。5.2 公共材料的正解子模块或者正式依赖如果公共代码量不大并且是团队内部维护的少量源文件我建议用 Git submodule 来做。子模块的好处是你的项目仓库里只记录一个“指向公共库某个 commit 的引用”而不是把公共库的代码复制进来。项目 clone 下来后用一条命令拉取子模块git submodule update --init --recursive就能拿到和该 commit 配套的公共库代码。这样公共库单独演进、单独打版本项目侧只是“钉”在某个版本上两边互不污染。如果公共库已经发展到一定规模比如有几十个文件和完整 API我建议走更正规的依赖管理。Python 项目就把它做成一个可安装的包发到公司内部的私有 PyPI 服务器项目里用requirements.txt锁定版本C 项目就把公共部分编译成静态库或动态库项目只依赖头文件和库文件不引用源码。这种做法最干净代价是需要有人维护公共包的发布流程但对一个长期演进的团队来说非常值得。我见过一些团队用了 submodule 之后出现“子模块 push 了新的 commit但项目仓库没有升级引用”的踩坑经历。要我说这不是 submodule 的问题是流程没跟上。正确做法是规定公共库任何变更都要发 release 并打 tag项目要升级公共库时单独提交一个“bump common lib to vX.Y.Z”的 commit方便回溯。5.3 一套收敛后的完整目录树照着搭就行了到这里四道坎都讲完了。我把前面所有方案收敛到一起给出一套我实际用过的、比较成熟的目录骨架直接照抄即可project/ ├── src/ │ ├── common/ # 项目内的私有工具库不是公司公共库 │ │ ├── file_utils.py │ │ └── gpib_wrapper.py │ ├── test_items/ # 各测试项的实现 │ │ ├── power_up_test.py │ │ ├── func_test.py │ │ └── __init__.py │ └── main.py # 入口统一从这里启动 ├── config/ │ ├── testplan.yaml │ ├── limits/ │ └── conditions/ ├── data/ # 只放固定不变的输入数据如 pattern │ └── pattern/ ├── tool/ # 小工具脚本如日志解析、良率统计 ├── doc/ # 设计文档、操作手册、发布说明 ├── out/ # 运行产物默认不被 Git 跟踪 │ ├── log/ │ ├── datalog/ │ └── report/ ├── requirements.txt # 依赖清单如果项目是 Python ├── README.md # 克隆后如何运行 └── .gitignore这套结构有几个设计点我额外解释一下。src/main.py是整套程序的唯一入口通过命令行参数指定用哪份配置python src/main.py --config config/testplan.yamldata目录只放真正固定不变的输入。如果你发现某个 pattern 文件经常变化那说明它应该挪到配置里或者单独用 Git LFS 管理。更关键的是我要给这套目录树立一个“红线”out目录下的东西、任何人的本地调试文件、IDE 的配置目录都不能进入版本管理。.gitignore就是这套目录结构的守护者楼下讲怎么配。6. 落地时的几个细节与实战心得6.1 仓库初始化的时候先把 .gitignore 写好很多人建仓库的第一步是git init然后开始写代码这不对。第一步应该是写.gitignore。芯片测试程序的 .gitignore 至少要覆盖这四类内容编译产物、运行日志、IDE 配置、临时文件。# 编译与运行产物 build/ out/ *.dll *.exe *.o # 日志与数据记录 *.log *.txt.datalog out/log/ out/datalog/ # 大波形文件 *.wav *.bin # IDE 配置 .idea/ .vscode/ __pycache__/ *.pyc # 系统临时文件 .DS_Store Thumbs.db配好之后有个判断标准在干净环境 clone 下来跑一遍程序然后git status应该只有源代码和配置的变更绝对不应该出现日志、波形、DLL 这些东西。如果出现了说明 .gitignore 还不够。6.2 分支、标签怎么配合定版强调完目录再补一个版本管理本身的落地小经验。很多测试团队只有两个分支main和dev发版全靠 commit message。我个人建议正式发布量产物料时必须打 tag格式用v主版本.次版本.修订号例如v2.1.0。tag 的意义在于它钉死了一个 commit以后任何时候想看产线当时跑的是什么目录结构、什么配置直接git checkout v2.1.0就行。关于分支我建议不要搞太复杂main保持永远可用dev做日常开发要发布时从dev合并到main打 tag。至于每个人开发用的是自己的 feature 分支还是直接在 dev 上看团队规模。人少就直接 dev 上干活人多就按功能开分支这个不必教条。6.3 存量脏目录怎么低成本迁移有读者肯定要问我这项目已经烂了两年了几百个文件怎么搬我的建议是别做“大爆炸式重构”做增量迁移分三步走。第一步先建好新的目录骨架并配置 .gitignore然后把新仓库初始化好把 README 写清楚。第二步只把源代码和配置、文档搬到新结构里归属于src、config、doc编译产物和历史日志先不搬放在旧目录里归档。第三步跑通一次编译和冒烟测试确认没问题后把旧目录改名“archive_旧结构”。新仓库从这一个干净的 commit 开始后续开发全走新流程。这个过程不一定需要在 Git 里保留旧的历史记录。说实话如果旧目录本身混乱那些历史也没有太大保留价值清清爽爽重新开始往往比费劲清洗历史要实用得多。6.4 我踩过的几个坑直接给你避雷表最后分享几个我在实际落地中踩过的具体坑整理成一个小表希望大家直接绕开。坑现象解决文件编码不统一代码里有中文注释clone 到别的机器乱码统一用 UTF-8编辑器默认编码改掉文件路径过长Windows 下 Git 操作报错开启 Gitcore.longpaths true或者别把项目嵌太多层目录二进制文件 diff 无意义pattern 文件稍改一点diff 全是乱码用 Git LFS 管理别指望普通 diff 看二进制换行符混乱同一文件跨 Windows/Unix 提交后 diff 一片红统一设置.gitattributes强制 LFcommit message 写“fix”回滚时根本不知道当时改了啥规范格式如[test_item] 修改低压档上限这里最值得单独强调的就是 commit message。我见过太多团队在“版本可追溯”上栽跟头不是没有 Git而是大家的提交信息毫无信息量。芯片测试程序的 commit 最好形成固定的三段式改动模块、改动内容、改动原因。比如[func_test] 增加低压档功能测试 - 新增 vmin 模式下 pattern 切换逻辑 - 同步更新 limits 表中低压档上下限 - 原因该芯片在低压档出现良率异常需单独流程监控这样半年后翻 Git 日志你依然能看懂当时发生了什么。这才是版本管理最终的价值——不是让你记住改了什么而是让未来的你不需要重新推演一遍就能知道改了什么。我个人在实际操作中的体会是目录结构这件事越早治理越省心。所有“等以后有空再整理”的项目最后都变成了“再也整理不动”的项目。如果你手头正好有一个快失控的测试程序项目我的建议是别怕选一个周五下午按上面这套骨架花两个小时把目录和 .gitignore 立起来先跑通一个最小闭环。等周一大家上班发现“诶现在找代码、看 diff 都舒服多了”版本管理的风气就慢慢转起来了。这活儿不需要等领导安排你自己就能开干。