第4章:文件操作实战:使用 open()、pathlib 读写配置文件、日志文件和测试报告

文件操作,说白了就是验证工程师的「基本功」。你想想看,每天跑仿真、看日志、写报告,哪样离得开文件?我刚开始做验证那会儿,还傻乎乎地手动复制粘贴日志内容,后来被 mentor 骂了一顿,才老老实实把文件操作练扎实了。

这一章,咱们就聊聊怎么用 Python 的 open()pathlib 来搞定配置文件、日志文件和测试报告。嗯,这两个工具用好了,能省下你不少头发。

4.1 传统 open() 函数:基本功要扎实

open() 是 Python 内置的文件操作函数,简单直接。我个人习惯用它来处理小文件,比如几百行的配置文件。

4.1.1 基本用法

# 读取配置文件
with open('config.ini', 'r') as f:
    content = f.read()
    print(content)

# 写入日志文件
with open('sim.log', 'w') as f:
    f.write('[INFO] Simulation started\n')
    f.write('[INFO] Test case passed\n')

这里有个关键点:一定要用 with 语句。为什么?因为 with 会自动帮你关闭文件,省得你忘了关导致文件句柄泄漏。我在项目中遇到过一位同事,跑了三天三夜的仿真,结果因为文件没关好,日志写到一半就崩了……那叫一个惨。

4.1.2 模式选择

模式 说明 常用场景
'r' 只读 读取配置文件
'w' 写入(覆盖) 生成新报告
'a' 追加 写日志文件
'rb' 二进制只读 读取波形文件
⚠️ 避坑指南
我曾经用 'w' 模式打开一个重要的配置文件,结果内容全被清空了……后来我养成了一个习惯:写文件之前,先备份原文件。尤其是配置文件,千万别手滑。

4.2 pathlib:现代文件操作利器

pathlib 是 Python 3.4 引入的模块,它把文件路径当作对象来处理。说实话,我第一次用的时候觉得「这不就是花架子吗?」但用久了才发现,真香。

4.2.1 路径操作

from pathlib import Path

# 创建路径对象
log_dir = Path('./logs')
report_dir = Path('./reports')

# 检查目录是否存在,不存在则创建
log_dir.mkdir(exist_ok=True)
report_dir.mkdir(parents=True, exist_ok=True)

# 拼接路径
log_file = log_dir / 'sim_2024.log'
report_file = report_dir / 'test_report.html'

你看,用 / 拼接路径,比 os.path.join() 清爽多了。我个人习惯用 pathlib 来处理所有路径相关的操作,尤其是跨平台项目——Windows 和 Linux 的路径分隔符不一样,pathlib 会自动帮你处理好。

4.2.2 文件读写

# 读取配置文件
config = Path('config.yaml').read_text()
print(config)

# 写入测试报告
report_content = """
<h1>Test Report</h1>
<p>Passed: 42</p>
<p>Failed: 3</p>
"""
Path('report.html').write_text(report_content)
💡 小技巧
pathlibread_text()write_text() 方法,内部已经帮你处理了文件打开和关闭。对于小文件,一行代码搞定,省心省力。

4.3 实战:读写配置文件

配置文件在验证项目中太常见了。比如测试用例的参数、仿真环境的设置,都放在配置文件里。我一般用 .ini.yaml 格式。

4.3.1 读取 INI 配置文件

import configparser

config = configparser.ConfigParser()
config.read('test_config.ini')

# 读取参数
sim_time = config.get('SIMULATION', 'time')
test_name = config.get('TEST', 'name')

print(f'Simulation time: {sim_time}')
print(f'Test name: {test_name}')

嗯,这里要注意:configparser 默认会把键名转成小写。如果你需要保留大小写,可以设置 ConfigParser(allow_no_value=True)

4.3.2 读取 YAML 配置文件

import yaml

with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)

# 读取嵌套参数
sim_time = config['simulation']['time']
test_cases = config['test']['cases']

print(f'Test cases: {test_cases}')
🔑 关键点
我个人更推荐 YAML 格式,因为它支持嵌套结构,可读性更好。但要注意:yaml.load() 有安全风险,一定要用 yaml.safe_load()

4.4 实战:日志文件管理

日志文件是验证工程师的「黑匣子」。仿真出问题了,第一件事就是翻日志。我见过有人把日志写得跟天书一样,结果 debug 的时候自己都看不懂……

4.4.1 使用 logging 模块

import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('simulation.log'),
        logging.StreamHandler()
    ]
)

# 记录日志
logging.info('Simulation started')
logging.warning('Clock frequency mismatch detected')
logging.error('Test case failed: timeout')

你看,用 logging 模块,日志会自动带上时间戳和级别。我曾经接手过一个项目,日志全是 print() 输出的,找问题的时候简直想骂人……从那以后,我坚持用 logging

4.4.2 日志轮转

from logging.handlers import RotatingFileHandler

handler = RotatingFileHandler(
    'sim.log',
    maxBytes=10*1024*1024,  # 10MB
    backupCount=5
)

logging.getLogger().addHandler(handler)
⚠️ 注意
仿真跑个几天几夜,日志文件能大到几十 GB。如果不做轮转,磁盘空间会被撑爆。我建议设置 maxBytes 为 10MB 或 50MB,保留 3-5 个备份文件。

4.5 实战:生成测试报告

测试报告是验证工作的「成绩单」。老板不看你的代码多漂亮,只看报告里 pass/fail 的数字。所以,报告要清晰、直观、自动化。

4.5.1 生成 HTML 报告

from pathlib import Path

def generate_report(test_results):
    """生成测试报告"""
    html_content = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Test Report</title>
        <style>
            .pass {{ color: green; }}
            .fail {{ color: red; }}
        </style>
    </head>
    <body>
        <h1>Test Report</h1>
        <table border="1">
            <tr>
                <th>Test Case</th>
                <th>Status</th>
                <th>Duration</th>
            </tr>
    """

    for test in test_results:
        status_class = 'pass' if test['status'] == 'PASS' else 'fail'
        html_content += f"""
            <tr>
                <td>{test['name']}</td>
                <td class="{status_class}">{test['status']}</td>
                <td>{test['duration']}s</td>
            </tr>
        """

    html_content += """
        </table>
    </body>
    </html>
    """

    Path('test_report.html').write_text(html_content)
    print('Report generated: test_report.html')

4.5.2 生成 CSV 报告

import csv

def generate_csv_report(test_results):
    """生成 CSV 格式报告"""
    with open('test_report.csv', 'w', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(['Test Case', 'Status', 'Duration'])
        
        for test in test_results:
            writer.writerow([
                test['name'],
                test['status'],
                test['duration']
            ])
    
    print('CSV report generated')
💡 建议
我个人习惯同时生成 HTML 和 CSV 两种格式。HTML 给老板看,直观漂亮;CSV 给自己用,方便后续数据分析。你想想看,老板看到漂亮的 HTML 报告,心情好了,项目验收也顺利多了。

4.6 综合示例:自动化文件操作脚本

最后,咱们来个综合示例。把配置文件读取、日志记录、报告生成串起来,形成一个完整的自动化脚本。

from pathlib import Path
import logging
import configparser
import csv
from datetime import datetime

class VerificationAutomation:
    def __init__(self, config_path):
        self.config_path = Path(config_path)
        self.log_dir = Path('./logs')
        self.report_dir = Path('./reports')
        
        # 创建目录
        self.log_dir.mkdir(exist_ok=True)
        self.report_dir.mkdir(exist_ok=True)
        
        # 配置日志
        self.setup_logging()
        
        # 读取配置
        self.config = self.read_config()
    
    def setup_logging(self):
        log_file = self.log_dir / f'sim_{datetime.now():%Y%m%d_%H%M%S}.log'
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger(__name__)
    
    def read_config(self):
        config = configparser.ConfigParser()
        config.read(self.config_path)
        self.logger.info(f'Configuration loaded from {self.config_path}')
        return config
    
    def run_tests(self):
        """模拟测试执行"""
        test_results = []
        test_cases = ['test_alu', 'test_mem', 'test_ctrl']
        
        for test in test_cases:
            self.logger.info(f'Running test: {test}')
            # 模拟测试结果
            result = {
                'name': test,
                'status': 'PASS',
                'duration': 12.5
            }
            test_results.append(result)
            self.logger.info(f'Test {test}: {result["status"]}')
        
        return test_results
    
    def generate_report(self, test_results):
        """生成测试报告"""
        report_file = self.report_dir / f'report_{datetime.now():%Y%m%d_%H%M%S}.html'
        
        html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <title>Test Report</title>
            <style>
                body {{ font-family: Arial; margin: 20px; }}
                .pass {{ color: green; }}
                .fail {{ color: red; }}
                table {{ border-collapse: collapse; width: 100%; }}
                th, td {{ border: 1px solid #ddd; padding: 8px; }}
                th {{ background-color: #4CAF50; color: white; }}
            </style>
        </head>
        <body>
            <h1>Test Report</h1>
            <p>Generated: {datetime.now()}</p>
            <table>
                <tr>
                    <th>Test Case</th>
                    <th>Status</th>
                    <th>Duration (s)</th>
                </tr>
        """
        
        for test in test_results:
            status_class = 'pass' if test['status'] == 'PASS' else 'fail'
            html_content += f"""
                <tr>
                    <td>{test['name']}</td>
                    <td class="{status_class}">{test['status']}</td>
                    <td>{test['duration']}</td>
                </tr>
            """
        
        html_content += """
            </table>
        </body>
        </html>
        """
        
        report_file.write_text(html_content)
        self.logger.info(f'Report generated: {report_file}')
    
    def run(self):
        """主流程"""
        self.logger.info('=' * 50)
        self.logger.info('Verification Automation Started')
        self.logger.info('=' * 50)
        
        test_results = self.run_tests()
        self.generate_report(test_results)
        
        self.logger.info('=' * 50)
        self.logger.info('Verification Automation Completed')
        self.logger.info('=' * 50)

# 使用示例
if __name__ == '__main__':
    automation = VerificationAutomation('test_config.ini')
    automation.run()
🎯 核心要点
  • open():适合小文件,记得用 with 语句
  • pathlib:现代文件操作,路径处理更优雅
  • logging:专业日志管理,别再用 print()
  • 报告生成:HTML 给老板看,CSV 给自己用

好了,这一章的内容就到这里。文件操作看似简单,但用好了能极大提升你的工作效率。我建议你把这些代码跑一遍,然后试着改一改,比如加上日志轮转、支持更多配置文件格式。嗯,动手试试吧,光看是学不会的。