第2章:测试系统架构设计

好,咱们接着聊。上一章我们搭好了环境,这一章,我来聊聊整个测试系统的骨架——架构设计。

很多人一上来就写代码,写到后面发现改一个功能要动全身。我早期也犯过这个错。后来我总结出一套四层架构,用了好多年,基本没大改过。

2.1 四层架构,各司其职

我把整个测试系统拆成四层:硬件层、驱动层、应用层、数据层。每一层只干自己的事,不越界。

层级 职责 典型组件
硬件层 物理连接、信号调理 PXI机箱、射频开关矩阵、DUT板
驱动层 硬件抽象、指令封装 VISA驱动、DLL封装、SCPI命令
应用层 测试流程、业务逻辑 测试序列、参数配置、结果判断
数据层 数据存储、统计分析 SQLite、CSV导出、MES对接

核心原则:上层只能调用相邻下层,不能跨层调用。比如应用层不能直接操作硬件寄存器,必须通过驱动层。

2.2 硬件层:物理世界的接口

硬件层是最底层。说白了,就是你的仪器和DUT之间的物理连接。

我个人习惯把硬件层分成两部分:

  • 仪器资源:万用表、频谱仪、信号源、射频开关等
  • 接口适配:DUT板、探针卡、线缆、转接板

这里有个坑。我曾经遇到过,射频开关的寿命只有10万次,产线一天跑几千次,两个月就挂了。所以硬件层设计时,一定要考虑冗余和可替换性。

注意:硬件层的选型直接影响测试效率。比如用PXI比用GPIB快10倍以上。产线测试,时间就是钱。

2.3 驱动层:让硬件“说人话”

驱动层是硬件和软件之间的翻译官。你想想看,仪器返回的是电压值,应用层需要的是“通过/失败”,中间这层就是干这个的。

我一般这样设计驱动层:

class PowerSupply:
    def __init__(self, resource_name):
        self.rm = pyvisa.ResourceManager()
        self.instr = self.rm.open_resource(resource_name)
    
    def set_voltage(self, voltage):
        self.instr.write(f"VOLT {voltage}")
    
    def get_current(self):
        return float(self.instr.query("MEAS:CURR?"))

看到没?每个仪器一个类,方法名统一。这样上层调用时,根本不用管底层是GPIB还是USB。

我的经验:驱动层一定要做超时处理和重试机制。产线环境复杂,偶尔通信失败很正常。我曾经因为没加重试,导致整条线停了半小时。

2.4 应用层:测试的大脑

应用层是核心。它负责调度测试流程、判断结果、处理异常。

我习惯把应用层拆成三个模块:

  • 测试序列管理器:定义测试步骤的顺序和依赖关系
  • 参数配置器:管理测试限值、频率、功率等参数
  • 结果判断器:根据测试数据给出PASS/FAIL

举个例子,一个典型的TPMS测试序列:

test_sequence = [
    {"name": "电流测试", "func": test_current, "limits": (0.5, 2.0)},
    {"name": "频率测试", "func": test_frequency, "limits": (433.85, 433.95)},
    {"name": "功率测试", "func": test_power, "limits": (5.0, 10.0)},
]

这样设计的好处是,新增一个测试项,只需要在列表里加一行,不用改任何逻辑代码。

模块化设计原则:每个模块只做一件事,并且做好。比如结果判断器,只负责比较数值,不负责存储数据。

2.5 数据层:让数据说话

数据层负责把测试结果存起来,方便后续分析。

我一般用两种方式:

  • 本地存储:SQLite数据库,适合单机产线
  • 云端对接:通过MES接口上传到服务器

数据层的设计要点:

class DataLogger:
    def save_result(self, sn, test_name, value, result):
        # 保存到SQLite
        cursor.execute("INSERT INTO results VALUES (?,?,?,?)",
                      (sn, test_name, value, result))
        # 同时生成CSV备份
        with open("backup.csv", "a") as f:
            f.write(f"{sn},{test_name},{value},{result}\n")

嗯,这里要注意。数据层一定要做缓冲。产线测试频率高,如果每次测试都直接写数据库,I/O会成为瓶颈。我建议用队列缓冲,批量写入。

避坑指南:我曾经遇到过,数据库文件损坏导致整批数据丢失。从那以后,我坚持“双写”策略——数据库和CSV同时写,互相备份。

2.6 模块化设计,灵活可扩展

四层架构的核心思想就是模块化。每一层内部也应该是模块化的。

我总结了几条原则:

  1. 高内聚:一个模块只做一件事
  2. 低耦合:模块之间通过接口通信,不直接依赖
  3. 可替换:换一个仪器,只改驱动层,不动应用层
  4. 可测试:每个模块都能单独测试

举个例子,如果产线要换频谱仪,从Keysight换成Rohde & Schwarz,我只需要写一个新的驱动类,继承同一个接口。应用层代码一行都不用改。

我的习惯:每个模块都写一个单元测试。虽然前期花点时间,但后期改代码时,心里踏实多了。

2.7 架构设计的取舍

说实话,四层架构不是万能的。小项目用这个架构,反而显得臃肿。

我个人建议:

  • 产线超过2台测试工位,用四层架构
  • 只有1台工位,可以简化成三层(硬件、逻辑、数据)
  • 研发验证阶段,甚至两层就够了(硬件+脚本)

但不管怎么简化,模块化的思想不能丢。你想想看,如果代码写成一坨,后面改起来有多痛苦?

好,这一章就聊到这里。下一章我们开始动手,写一个完整的驱动层代码。