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结构化数据 YAML用例配置管理 subprocess执行引擎 超时控制与异常处理 通过率统计 用例级PASS/FAIL 历史趋势折线图 关键设计原则 模块解耦:日志解析、回归测试、看板独立运行 数据格式统一:JSON作为模块间通信的中间格式

整个流程是这样的:

  1. 设备运行测试,产生原始日志
  2. 日志解析模块提取关键信息,输出结构化JSON
  3. 回归测试框架读取JSON,执行用例,生成结果
  4. 看板模块汇总结果,生成HTML页面
核心思路:每个模块只做一件事,模块之间通过JSON文件通信。这样任何一个模块出问题,都不会影响其他模块。我曾经在一个项目中,日志解析脚本出了bug,但回归测试和看板完全不受影响,因为数据格式没变。

嗯,工具链搭建到这里,基本就成型了。你想想看,以前手动分析日志、手动跑回归、手动汇总报告,现在全自动化了。团队效率提升不是一点半点。