4、自动化工具链搭建:Python脚本实现日志解析、自动化回归测试框架设计、持续集成中的测试结果看板
这一章,我们来聊聊自动化工具链。说实话,很多团队做嵌入式测试,工具链还停留在“手动点按钮、眼睛盯日志”的阶段。我见过不少项目,测试人员每天花两三个小时在日志里翻找关键字,效率低不说,还容易漏掉关键信息。
自动化工具链的核心就三件事:日志解析、回归测试框架、结果看板。说白了,就是把重复劳动交给机器,让人去做更有价值的分析工作。
4.1 Python脚本实现日志解析
嵌入式设备的日志,格式五花八门。有的用时间戳+级别+模块,有的干脆就是纯文本。我个人的习惯是,先统一日志格式,再写解析脚本。
我的经验:日志解析脚本不要写得太“死”。用正则表达式匹配关键字段,比硬编码字符串位置要灵活得多。我曾经在一个项目中,日志格式改了三次,幸好用了正则,改个模式就搞定。
下面是一个典型的日志解析脚本框架:
import re
import json
from datetime import datetime
class LogParser:
def __init__(self, log_file):
self.log_file = log_file
self.entries = []
def parse(self):
# 匹配 [时间] [级别] [模块] 消息
pattern = r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(\w+)\] \[(\w+)\] (.+)'
with open(self.log_file, 'r') as f:
for line in f:
match = re.match(pattern, line)
if match:
entry = {
'timestamp': match.group(1),
'level': match.group(2),
'module': match.group(3),
'message': match.group(4)
}
self.entries.append(entry)
return self.entries
def filter_by_level(self, level):
return [e for e in self.entries if e['level'] == level]
def export_json(self, output_file):
with open(output_file, 'w') as f:
json.dump(self.entries, f, indent=2)
# 使用示例
parser = LogParser('test_run_2024.log')
parser.parse()
errors = parser.filter_by_level('ERROR')
print(f"发现 {len(errors)} 条错误日志")
parser.export_json('parsed_logs.json')
这个脚本能做什么?
- 自动提取时间、级别、模块、消息四个字段
- 按级别过滤(比如只看ERROR和FATAL)
- 导出为JSON,方便后续处理
注意:日志文件如果很大(比如超过100MB),逐行读取是必须的。千万别用readlines()一次性加载到内存,我见过有人这么干,结果8GB内存的机器直接卡死。
4.2 自动化回归测试框架设计
回归测试框架,说白了就是“一键跑所有用例,自动比对结果”。我设计框架时,会考虑三个层次:用例管理、执行引擎、结果比对。
下面是一个轻量级框架的设计思路:
| 层次 | 职责 | 实现方式 |
|---|---|---|
| 用例管理 | 定义测试用例、输入输出、预期结果 | YAML/JSON配置文件 |
| 执行引擎 | 调度用例、控制执行顺序、超时处理 | Python unittest/pytest |
| 结果比对 | 实际输出 vs 预期结果,生成差异报告 | 自定义比对器 + diff工具 |
我习惯用YAML来管理测试用例,因为可读性好:
# test_cases.yaml
test_suite:
- name: "UART通信测试"
command: "./test_uart"
args: ["--baud", "115200"]
expected_output: "UART test PASS"
timeout: 30
- name: "SPI读写测试"
command: "./test_spi"
args: ["--mode", "0"]
expected_output: "SPI read/write OK"
timeout: 20
- name: "内存压力测试"
command: "./test_memory"
args: ["--size", "1024"]
expected_output: "Memory test completed"
timeout: 120
执行引擎的核心逻辑:
import subprocess
import yaml
import time
class RegressionRunner:
def __init__(self, config_file):
with open(config_file, 'r') as f:
self.cases = yaml.safe_load(f)['test_suite']
self.results = []
def run_all(self):
for case in self.cases:
print(f"运行用例: {case['name']}")
start = time.time()
try:
result = subprocess.run(
[case['command']] + case['args'],
capture_output=True,
text=True,
timeout=case['timeout']
)
passed = (result.stdout.strip() == case['expected_output'])
self.results.append({
'name': case['name'],
'passed': passed,
'actual': result.stdout.strip(),
'duration': time.time() - start
})
except subprocess.TimeoutExpired:
self.results.append({
'name': case['name'],
'passed': False,
'actual': 'TIMEOUT',
'duration': case['timeout']
})
return self.results
避坑指南:我曾经遇到过一个问题——测试用例跑得很快,但结果比对总是失败。后来发现是换行符不一致:Windows用\r\n,Linux用\n。解决方案是在比对前统一strip()掉空白字符。
4.3 持续集成中的测试结果看板
测试结果看板,是给整个团队看的。我建议用HTML生成静态页面,不需要数据库,简单直接。
下面是一个看板生成脚本的核心部分:
def generate_dashboard(results, output_file='dashboard.html'):
total = len(results)
passed = sum(1 for r in results if r['passed'])
failed = total - passed
pass_rate = (passed / total) * 100 if total > 0 else 0
html = f'''<!DOCTYPE html>
<html>
<head>
<title>测试结果看板</title>
<style>
body {{ font-family: Arial; padding: 20px; }}
.summary {{ font-size: 24px; margin: 20px 0; }}
.pass {{ color: green; }}
.fail {{ color: red; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
</style>
</head>
<body>
<h1>回归测试结果看板</h1>
<div class="summary">
通过率: <span class="pass">{pass_rate:.1f}%</span>
({passed}/{total} 通过)
</div>
<table>
<tr><th>用例名称</th><th>结果</th><th>耗时(秒)</th></tr>'''
for r in results:
status = 'PASS' if r['passed'] else 'FAIL'
color = 'pass' if r['passed'] else 'fail'
html += f'''<tr>
<td>{r['name']}</td>
<td class="{color}">{status}</td>
<td>{r['duration']:.2f}</td>
</tr>'''
html += '''</table>
</body>
</html>'''
with open(output_file, 'w') as f:
f.write(html)
print(f"看板已生成: {output_file}")
这个看板会显示:
- 总体通过率(大号数字,一目了然)
- 每个用例的通过/失败状态
- 执行耗时(方便发现性能退化)
我的建议:看板里再加一列“历史趋势”,比如最近10次构建的通过率折线图。这样一眼就能看出代码改动是否引入了回归问题。我通常用matplotlib生成图片,嵌入到HTML里。
4.4 整体工具链流程
把上面三块串起来,就是完整的自动化工具链。下面这张图展示了数据流向:
整个流程是这样的:
- 设备运行测试,产生原始日志
- 日志解析模块提取关键信息,输出结构化JSON
- 回归测试框架读取JSON,执行用例,生成结果
- 看板模块汇总结果,生成HTML页面
核心思路:每个模块只做一件事,模块之间通过JSON文件通信。这样任何一个模块出问题,都不会影响其他模块。我曾经在一个项目中,日志解析脚本出了bug,但回归测试和看板完全不受影响,因为数据格式没变。
嗯,工具链搭建到这里,基本就成型了。你想想看,以前手动分析日志、手动跑回归、手动汇总报告,现在全自动化了。团队效率提升不是一点半点。