4. 测试开发环境搭建
光模块测试开发环境不是把仪器、电脑和软件堆在一起就能跑起来,它需要解决四个层面的问题:如何与仪器通信、用什么语言和框架组织测试逻辑、如何管理代码与配置、如何让测试在提交后自动执行并给出可信结论。本章围绕这四条主线,结合高速光模块(如400G QSFP-DD DR4/FR4、800G OSFP)的典型测试场景,说明每一步的落地方式。
一、仪器控制:从物理链路到可编程接口
高速光模块测试常用的仪器包括误码仪(BERT,如Keysight M8040A、Anritsu MP2110A)、采样示波器(如Keysight DCA-X N1000系列)、光功率计与衰减器、可编程电源、温箱以及光开关矩阵。这些仪器与上位机的连接方式主要有三种:LAN(VXI-11/Hislip/Socket)、USB-TMC和GPIB。现代BERT和示波器普遍支持SCPI over TCP Socket,端口通常为5025,这是首选方式,因为它不依赖VISA库的复杂安装,跨平台性好。
以Python控制Keysight M8040A为例,最简通信链路如下:
import socket
class ScpiSocket:
def __init__(self, ip, port=5025, timeout=10):
self.sock = socket.create_connection((ip, port), timeout=timeout)
self.sock.settimeout(timeout)
def write(self, cmd):
self.sock.sendall((cmd + "\n").encode())
def query(self, cmd):
self.write(cmd)
return self.sock.recv(65536).decode().strip()
def close(self):
self.sock.close()
# 示例:查询BERT模块标识与误码率
bert = ScpiSocket("192.168.1.50")
print(bert.query("*IDN?"))
# 假设返回: KEYSIGHT,M8040A,MY12345678,A.01.02
bert.write(":SOURce1:PATTern PRBS31")
bert.write(":SENSe1:EYE:BER:STATe ON")
ber = float(bert.query(":SENSe1:EYE:BER?"))
print(f"当前BER = {ber:.3e}")
这里的关键不是代码本身,而是命令集的确认。不同厂商、不同型号的SCPI树差异很大,例如误码率查询在M8040A上是:SENSe1:EYE:BER?,而在某些Anritsu设备上可能是:MEASure:BER?。因此,环境搭建的第一步是建立一份仪器命令映射表,把“设置速率”“设置码型”“读取BER”“读取光功率”等抽象动作映射到具体SCPI命令。这份映射表应作为配置文件独立于测试代码,换仪器时只改配置,不改测试逻辑。
另一个容易被忽视的点是状态同步与超时。BERT切换码型后需要等待锁定,示波器触发后需要等待采集完成。直接读取往往得到旧值或报错。推荐做法是使用*OPC?或轮询状态寄存器,并设置合理超时(如码型切换后等待2~5秒)。在示例中,若:SENSe1:EYE:BER?返回9.9E+37,通常表示尚未锁定或无有效结果,应视为无效值而非真实误码率。
二、编程语言与自动化框架选型
光模块测试开发的语言选择集中在Python和C#/C++之间。Python的优势是仪器库丰富(PyVISA、pyserial)、数据处理快(NumPy、pandas)、与CI集成简单;C#在Windows桌面交互和产线长期运行稳定性上有优势。对于研发验证和自动化回归,Python是更主流的选择。
框架层面,不建议从零写调度。常见方案有三类:pytest + 自定义仪器插件、Robot Framework、以及厂商自带的测试执行器(如Keysight Test Exec SL)。pytest的fixture机制非常适合管理仪器生命周期:
import pytest
@pytest.fixture(scope="module")
def bert():
inst = ScpiSocket("192.168.1.50")
inst.write("*RST")
yield inst
inst.close()
@pytest.fixture(scope="module")
def dut_power():
# 假设通过串口控制可编程电源
return ProgrammableSupply("COM3")
def test_400g_dr4_ber(bert, dut_power):
dut_power.set_voltage(3.3)
bert.write(":SOURce1:PATTern PRBS31Q")
bert.write(":SOURce1:RATE 53.125GBd")
ber = float(bert.query(":SENSe1:EYE:BER?"))
assert ber < 1e-12, f"BER超标: {ber:.3e}"
这里scope="module"表示BERT在整个测试模块中只初始化一次,避免反复连接造成的时间浪费和状态漂移。测试用例本身只关心“设电压、设码型、读BER、判断”,仪器细节被封装在fixture和驱动层。这种分层是自动化框架能否长期维护的关键。
三、版本管理与配置分离
测试代码必须纳入Git管理,但更重要的是把“会变的”和“不变的”分开。不变的:测试逻辑、判断算法、报告生成。会变的:仪器IP、SCPI命令、DUT型号对应的速率和码型、判定阈值。后者应放在YAML或JSON配置中:
# config/dr4_400g.yaml
dut:
model: QSFP-DD DR4
rate_gbd: 53.125
pattern: PRBS31Q
ber_limit: 1.0e-12
instruments:
bert:
ip: 192.168.1.50
driver: keysight_m8040a
scope:
ip: 192.168.1.51
driver: keysight_n1000
测试代码读取配置后动态选择驱动。这样新增一个800G型号时,只需新增一份YAML和对应驱动,不需要复制粘贴测试脚本。Git分支策略上,建议main保持可发布状态,功能开发在feature/xxx分支,通过Pull Request合并,合并前必须通过CI中的冒烟测试。
四、持续集成在光模块测试中的落地
CI在软件行业是标配,但在硬件测试中落地需要解决两个特殊问题:仪器资源是独占的,测试耗时较长。因此不能简单地在每次push后跑全量测试。合理的做法是分层:
- 提交级(每次push):只跑不依赖真实仪器的单元测试,例如SCPI命令拼接、配置解析、BER判定逻辑、报告字段生成。这些用mock仪器即可,耗时控制在1分钟内。
- 每日构建(nightly):在专用测试机上跑真实仪器冒烟测试,例如单通道400G DR4的BER和光功率,耗时约10~20分钟。
- 发布级(tag或手动触发):跑全通道、全温度点的完整回归,耗时数小时,结果归档。
以GitLab CI为例,.gitlab-ci.yml可以这样组织:
stages:
- unit
- smoke
- regression
unit_test:
stage: unit
script:
- pytest tests/unit --junitxml=report_unit.xml
artifacts:
reports:
junit: report_unit.xml
smoke_test:
stage: smoke
tags: [optical-bench]
script:
- pytest tests/smoke --junitxml=report_smoke.xml
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
artifacts:
reports:
junit: report_smoke.xml
tags: [optical-bench]确保任务只在连接了仪器的runner上执行。JUnit报告让CI界面直接显示哪些用例失败、失败在哪个通道。对于BER测试,建议在报告中同时记录原始BER值和测试时间戳,便于后续追溯是设备漂移还是代码问题。
五、一个可执行的环境检查清单
在正式编写测试用例之前,建议先完成以下检查,确认环境可用:
| 检查项 | 操作 | 通过标准 |
|---|---|---|
| 仪器网络连通 | ping 192.168.1.50 | 丢包率0%,延迟<1ms |
| SCPI通信 | 发送*IDN?并读取 | 返回包含厂商和型号的字符串 |
| BERT码型切换 | 设置PRBS31Q后查询 | 返回PRBS31Q,且*OPC?在5秒内完成 |
| DUT供电 | 设置3.3V并读取电流 | 电流在模块规格范围内(如0.8~1.2A) |
| 光路连接 | 读取光功率 | 在预期范围内(如-3~+2dBm) |
| CI runner | 手动触发unit阶段 | 1分钟内完成,报告可下载 |
这份清单的价值在于:当测试失败时,可以快速区分是环境问题还是DUT问题。例如BER始终为9.9E+37,先查光功率是否正常、码型是否锁定,而不是直接怀疑模块坏。环境搭建的终点不是“能跑”,而是“跑出来的结果可信、可复现、可追溯”。
最后需要强调,光模块测试环境的搭建是一个迭代过程。第一版可以只支持一种型号、一台BERT、手动触发;随着测试用例增多,再逐步引入配置分离、CI分层和报告归档。不要一开始就追求大而全的框架,而应让每一层封装都对应一个真实的痛点:仪器命令太乱就抽驱动,配置太散就抽YAML,回归太慢就分CI阶段。这样搭出来的环境,才是测试开发真正能长期使用的环境。