4. 自动化框架设计:模块化设计思想、配置文件管理、日志系统搭建
做光模块测试自动化,框架设计是地基。地基没打好,后面写再多脚本也是白搭。我见过太多团队,一开始图省事,把所有代码塞到一个文件里。结果呢?换一个测试项目,整个脚本就得重写。今天咱们聊聊怎么搭一个靠谱的框架。
4.1 模块化设计思想
说白了,模块化就是把大问题拆成小问题。每个模块只干一件事,干好一件事。
我个人的习惯是把测试框架分成三层:
- 驱动层:跟仪器仪表打交道。比如控制光功率计、误码仪、光谱仪。
- 业务层:封装测试流程。比如“测发射功率”、“测灵敏度”。
- 执行层:调度测试用例,生成报告。
为什么要这么分?你想想看,如果驱动层和业务层混在一起,换一台不同品牌的误码仪,你就要改所有测试用例。分开之后,只需要改驱动层的一个文件就行。
核心原则:高内聚,低耦合
每个模块内部要足够完整,模块之间依赖要尽量少。
我在项目中遇到过这样一个坑:有个同事把仪器初始化代码写在了每个测试用例里。结果仪器换了IP地址,他改了30多个文件。嗯,从那以后我强制要求:所有仪器控制代码,只准放在驱动层。
4.2 配置文件管理(YAML/JSON)
测试参数不能硬编码在代码里。为什么?因为产线换一个型号的光模块,参数就全变了。你总不能每次都改代码吧?
我推荐用YAML。JSON虽然也行,但YAML可读性更好,支持注释,写起来也省事。
来看一个典型的配置文件:
# config.yaml
device:
power_meter:
type: "keysight_8163B"
ip: "192.168.1.100"
port: 5025
oscilloscope:
type: "tektronix_MSO54"
ip: "192.168.1.101"
test_params:
tx_power:
wavelength: 1310
min: -3.0
max: 2.0
unit: "dBm"
sensitivity:
bit_rate: 25.78125
ber_threshold: 1e-12
prbs_pattern: "PRBS31"
report:
format: "html"
output_dir: "./reports"
include_charts: true
读取配置的代码也很简单:
import yaml
def load_config(path="config.yaml"):
with open(path, 'r') as f:
config = yaml.safe_load(f)
return config
# 使用
cfg = load_config()
pm_ip = cfg['device']['power_meter']['ip']
tx_min = cfg['test_params']['tx_power']['min']
小技巧:配置文件中可以加一个 version 字段。这样当配置格式变化时,脚本可以自动判断是否需要迁移。我曾经因为配置格式改了没通知,导致产线停了半小时...
4.3 日志系统搭建
日志这东西,平时觉得没用,出问题的时候才知道有多重要。产线上的测试,一跑就是几千个模块。哪个模块在哪一步失败了?为什么失败?没有日志,你只能靠猜。
我建议用Python的logging模块,别自己造轮子。它支持分级、支持输出到文件和终端、支持按大小自动轮转。
一个成熟的日志配置:
import logging
import logging.handlers
import os
def setup_logger(name="test_automation", log_dir="./logs"):
os.makedirs(log_dir, exist_ok=True)
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# 文件日志 - 记录所有信息
file_handler = logging.handlers.RotatingFileHandler(
os.path.join(log_dir, "test.log"),
maxBytes=10*1024*1024, # 10MB
backupCount=5
)
file_handler.setLevel(logging.DEBUG)
# 控制台日志 - 只显示INFO及以上
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 格式化
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# 使用
log = setup_logger()
log.info("开始测试模块 S/N: 2024A0001")
log.debug("设置光功率计波长: 1310nm")
log.error("误码率超标: 2.3e-9 > 1e-12")
注意:日志级别要合理使用。DEBUG用于开发调试,INFO记录正常流程,WARNING提示潜在问题,ERROR记录失败。别把所有信息都打成INFO,否则日志文件会膨胀得很快。
日志里还要记录关键信息:模块序列号、测试时间、测试版本、仪器状态。这样出了问题才能复现。我曾经排查一个间歇性故障,就是靠日志里记录的仪器返回码,发现是GPIB线接触不良。
4.4 整体框架结构
把上面三块合在一起,一个典型的项目结构是这样的:
test_automation/
├── config/
│ ├── config.yaml # 主配置
│ └── device_map.yaml # 仪器映射
├── drivers/
│ ├── power_meter.py # 光功率计驱动
│ ├── ber_tester.py # 误码仪驱动
│ └── osa.py # 光谱仪驱动
├── tests/
│ ├── tx_power_test.py # 发射功率测试
│ ├── sensitivity_test.py # 灵敏度测试
│ └── eye_mask_test.py # 眼图模板测试
├── utils/
│ ├── logger.py # 日志工具
│ └── report.py # 报告生成
├── main.py # 主入口
└── requirements.txt # 依赖清单
这个结构我用了好几年,从10G到400G,从单模到多模,基本没大改过。每次新项目,改改配置文件,加几个测试用例,框架本身几乎不动。
一句话总结:模块化让代码好维护,配置文件让参数好管理,日志系统让问题好排查。这三样做好了,你的自动化框架就稳了。
这个图就是咱们框架的核心逻辑。从上到下,层层调用。每一层只跟相邻层打交道,不跨层调用。这样做的好处是:换仪器只改驱动层,加测试用例只改业务层,改参数只改配置文件。
嗯,框架设计就聊到这儿。记住一句话:好的框架是改出来的,不是设计出来的。先跑通一个最简单的流程,然后慢慢迭代。别一开始就想搞个大而全的东西,那样反而容易把自己绕进去。