第四章:报告模板引擎——Python Jinja2模板语法、HTML模板设计、CSS样式嵌入

说实话,做CANoe测试这么多年,我最头疼的事不是写测试脚本,而是整理报告。每次跑完几百个测试用例,光是把数据填进Word模板就得花大半天。后来我接触了Jinja2模板引擎,才发现——原来报告可以自动生成,而且还能做得很好看。

这一章,我就带你搞定报告模板的核心技术。说白了,就是让Python替你写HTML报告。

4.1 为什么选Jinja2?

你可能想问:Python能生成HTML的方式多了去了,为什么非要用Jinja2?

我个人的习惯是:代码和数据要分离。你想想看,如果直接在Python里拼HTML字符串,改一次样式就得改代码,维护起来太痛苦了。Jinja2把模板和逻辑分开,模板工程师只管设计,测试工程师只管填数据,各司其职。

核心优势:

  • 模板与Python代码解耦,修改样式不影响逻辑
  • 支持循环、条件判断,适合处理测试用例列表
  • 模板可复用,一套模板适配不同项目

我在项目中遇到过这样的情况:客户突然要求报告里加一列“测试时长”。如果是硬编码的HTML,我得改几十个地方。但用Jinja2,只需要在模板里加一个 {{ item.duration }},数据层加个字段就完事了。

4.2 Jinja2基础语法速览

Jinja2的语法非常直观,你只要记住三种标记:

标记 用途 示例
{{ ... }} 输出变量 {{ test_name }}
{% ... %} 控制结构 {% for item in items %}
{# ... #} 注释 {# 这里写注释 #}

嗯,这里要注意:注释不会出现在最终生成的HTML里,适合写一些给模板维护者看的说明。

4.3 实战:设计一个测试报告模板

我们先从最简单的开始。假设你要生成一个测试用例列表,每个用例有编号、名称、状态和耗时。

这是Python端传递的数据结构:

# Python代码
test_data = {
    "project": "VCU_BMS_Test_v2.1",
    "tester": "张工",
    "date": "2025-03-20",
    "cases": [
        {"id": "TC001", "name": "CAN总线唤醒测试", "status": "PASS", "duration": 1.2},
        {"id": "TC002", "name": "报文超时检测", "status": "FAIL", "duration": 0.8},
        {"id": "TC003", "name": "DTC故障注入", "status": "PASS", "duration": 2.5}
    ]
}

对应的Jinja2模板长这样:

<!-- report_template.html -->
<h2>{{ project }} 测试报告</h2>
<p>测试人员:{{ tester }} | 测试日期:{{ date }}</p>

<table border="1">
  <tr>
    <th>用例编号</th>
    <th>用例名称</th>
    <th>状态</th>
    <th>耗时(s)</th>
  </tr>
  {% for case in cases %}
  <tr>
    <td>{{ case.id }}</td>
    <td>{{ case.name }}</td>
    <td>
      {% if case.status == "PASS" %}
        <span style="color:green">通过</span>
      {% else %}
        <span style="color:red">失败</span>
      {% endif %}
    </td>
    <td>{{ case.duration }}</td>
  </tr>
  {% endfor %}
</table>

你看,{% for %} 循环自动遍历所有用例,{% if %} 根据状态显示不同颜色。这就是模板引擎的威力——数据驱动展示

我的小技巧:在模板里尽量少写复杂逻辑。如果状态判断规则很复杂,我习惯在Python端预处理成“status_text”和“status_color”两个字段,模板只负责输出。这样模板更干净,也更容易调试。

4.4 CSS样式嵌入——让报告好看一点

说实话,纯HTML的报告太素了。客户看了皱眉头,领导看了不满意。我建议你把CSS直接嵌入到模板里,这样生成的就是一个独立的HTML文件,发邮件、存档案都方便。

我的做法是在模板头部加一个 <style> 块:

<style>
  body {
    font-family: 'Microsoft YaHei', sans-serif;
    background-color: #f0f8ff;  /* 爱丽丝蓝,柔和 */
    margin: 20px;
  }
  h2 {
    color: #2c3e50;
    border-bottom: 3px solid #3498db;
    padding-bottom: 8px;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    background-color: #ffffff;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  }
  th {
    background-color: #3498db;
    color: white;
    padding: 10px;
  }
  td {
    padding: 8px;
    border-bottom: 1px solid #ecf0f1;
  }
  .pass { color: #27ae60; font-weight: bold; }
  .fail { color: #e74c3c; font-weight: bold; }
  .summary {
    background-color: #e8f8f5;
    padding: 15px;
    border-radius: 8px;
    margin-top: 20px;
  }
</style>

然后模板里的状态显示就可以用CSS类了:

<td><span class="{{ 'pass' if case.status == 'PASS' else 'fail' }}">{{ case.status }}</span></td>

这样改样式只需要改CSS,不用动模板结构。我曾经因为客户要求把“通过”从绿色改成蓝色,只改了一行CSS代码,5分钟搞定。

4.5 高级技巧:模板继承与宏

当你的报告越来越复杂——比如有封面、目录、测试详情、波形截图、统计图表——你会发现每个页面都有重复的头部和尾部。这时候就该用模板继承了。

先写一个基础模板 base.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{% block title %}默认标题{% endblock %}</title>
  <style>
    /* 全局样式 */
  </style>
</head>
<body>
  <div class="header">
    <h1>CANoe测试报告</h1>
  </div>
  <div class="content">
    {% block content %}{% endblock %}
  </div>
  <div class="footer">
    <p>公众号:蓝海资料掘金营,微信deep3321</p>
  </div>
</body>
</html>

然后具体的报告页面继承它:

{% extends "base.html" %}
{% block title %}CAN总线测试报告{% endblock %}
{% block content %}
  <h2>测试概览</h2>
  <p>本次测试共执行 {{ total }} 个用例...</p>
  <!-- 具体内容 -->
{% endblock %}

这样做的好处是:所有页面的头部和尾部保持一致,改一次全局生效。我在做多章节报告时特别喜欢用这个特性。

避坑指南:我曾经犯过一个错误——在模板里直接写死了文件路径。结果换了一台电脑,所有图片都显示不出来。后来我改用相对路径,并且把图片base64编码后直接嵌入HTML。虽然文件大了点,但胜在稳定,发邮件再也不用打包附件了。

4.6 从Python渲染模板

最后一步,用Python把数据和模板合在一起:

from jinja2 import Environment, FileSystemLoader

# 加载模板目录
env = Environment(loader=FileSystemLoader('./templates'))
template = env.get_template('report_template.html')

# 渲染
html_output = template.render(test_data)

# 保存到文件
with open('test_report.html', 'w', encoding='utf-8') as f:
    f.write(html_output)

print("报告已生成:test_report.html")

就这么简单。你跑完CANoe测试,数据一整理,调用这个脚本,一份漂亮的HTML报告就自动生成了。

4.7 本章小结

这一章我们聊了:

  • Jinja2的三种标记:{{ }}{% %}{# #}
  • 如何用循环和条件判断生成测试用例表格
  • CSS样式嵌入让报告更专业
  • 模板继承实现页面复用
  • Python渲染模板的完整流程

我个人觉得,模板引擎是自动化报告中最值得投入时间学习的部分。一旦你掌握了它,以后每次出报告就是几秒钟的事。下一章,我们会把CANoe的测试数据真正接入进来,实现全自动的报告生成流水线。

嗯,到时候你就知道,什么叫“一键生成报告”了。