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阶段。这样搭出来的环境,才是测试开发真正能长期使用的环境。