4. 项目结构设计:目录规范、模块划分、配置文件管理、日志系统设计

好,我们开始聊项目结构。说实话,很多自动化测试项目做着做着就烂掉了,根因往往不是技术选型不对,而是从一开始目录就没规划好。我见过太多项目,测试脚本和配置文件混在一起,日志满天飞,最后连自己写的用例都找不着。嗯,这一章我们就来把地基打牢。

4.1 目录规范:让每个人都能一眼看懂

我个人习惯,项目根目录下只放三样东西:源码、配置、文档。其他编译产物、临时文件统统隔离出去。下面是我在多个座舱项目中沉淀下来的目录模板:

auto_test_framework/
├── src/                    # 源码主目录
│   ├── core/               # 核心引擎
│   │   ├── engine.py       # 测试执行引擎
│   │   ├── runner.py       # 用例调度器
│   │   └── reporter.py     # 报告生成器
│   ├── libs/               # 公共库
│   │   ├── adb_utils.py    # ADB 工具封装
│   │   ├── can_utils.py    # CAN 总线工具
│   │   └── logger.py       # 日志封装
│   ├── pages/              # 页面对象模型
│   │   ├── home_page.py
│   │   ├── settings_page.py
│   │   └── media_page.py
│   ├── tests/              # 测试用例
│   │   ├── smoke/          # 冒烟测试
│   │   ├── functional/     # 功能测试
│   │   └── stress/         # 压力测试
│   └── utils/              # 工具函数
│       ├── data_parser.py
│       └── screenshot.py
├── config/                 # 配置文件
│   ├── env.yaml            # 环境配置
│   ├── devices.yaml        # 设备配置
│   └── test_config.yaml    # 测试参数
├── data/                   # 测试数据
│   ├── input/              # 输入数据
│   └── output/             # 输出报告
├── logs/                   # 日志目录
├── docs/                   # 文档
├── requirements.txt
└── README.md

核心原则:按职责划分,不按文件类型划分。不要把所有的 .py 文件扔到一个目录里,那叫垃圾桶,不叫项目结构。

4.2 模块划分:高内聚、低耦合

模块划分说白了就是「谁该管什么事」。我习惯把整个框架拆成四个层次:

层次 模块 职责 依赖
L1 基础设施 core/ 引擎、调度、报告 无
L2 公共能力 libs/, utils/ ADB、CAN、日志、截图 L1
L3 业务抽象 pages/ 页面对象、操作封装 L2
L4 测试用例 tests/ 具体测试场景 L3

你想想看,如果底层引擎改了,上层用例不需要动;如果页面元素变了,只需要改 pages/ 里的文件。这就是分层的好处。我在项目中遇到过最头疼的事,就是有人把 ADB 命令直接写在用例里,结果换了台设备,所有用例都得重写。嗯,血的教训。

4.3 配置文件管理:YAML 还是 JSON?

这个问题几乎每次培训都有人问。我的答案是:YAML 用于人工编辑,JSON 用于程序交互。

为什么?YAML 可读性好,支持注释,适合写环境配置、设备参数这些需要人经常看的东西。JSON 解析快,结构严格,适合做接口传参和数据交换。

来看一个实际的 YAML 配置文件例子:

# config/env.yaml
environment:
  name: "test_lab_01"
  location: "上海嘉定"

device_pool:
  - id: "device_001"
    serial: "ZY3229G8H7"
    platform: "android"
    version: "12.0"
    resolution: "1920x1080"
  - id: "device_002"
    serial: "ZY3229G8H8"
    platform: "android"
    version: "13.0"
    resolution: "2560x1440"

test_params:
  timeout: 30
  retry_count: 3
  screenshot_on_failure: true

小技巧:YAML 里缩进必须一致,别混用空格和 Tab。我习惯用 2 个空格缩进,团队统一就好。另外,敏感信息(密码、token)不要硬编码在 YAML 里,用环境变量注入。

那什么时候用 JSON?比如测试数据和预期结果的映射,或者 CI/CD 流水线传参:

{
  "test_suite": "smoke",
  "devices": ["device_001", "device_002"],
  "report_format": "html",
  "email_notify": ["qa@company.com"]
}

4.4 日志系统设计:别等出问题了才后悔

日志这东西,平时没人关心,一出问题它就是救命稻草。我曾经排查过一个偶现的蓝牙断连问题,没有日志根本无从下手。所以日志系统从一开始就要设计好。

我一般用 Python 的 logging 模块,但会做一层封装:

# src/libs/logger.py
import logging
import os
from logging.handlers import RotatingFileHandler

class TestLogger:
    def __init__(self, name, log_dir="logs"):
        self.logger = logging.getLogger(name)
        self.logger.setLevel(logging.DEBUG)

        # 控制台输出
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)
        console_format = logging.Formatter(
            '%(asctime)s - %(levelname)s - %(message)s'
        )
        console_handler.setFormatter(console_format)

        # 文件输出,按大小轮转
        os.makedirs(log_dir, exist_ok=True)
        file_handler = RotatingFileHandler(
            filename=f"{log_dir}/{name}.log",
            maxBytes=10*1024*1024,  # 10MB
            backupCount=5,
            encoding="utf-8"
        )
        file_handler.setLevel(logging.DEBUG)
        file_format = logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
        )
        file_handler.setFormatter(file_format)

        self.logger.addHandler(console_handler)
        self.logger.addHandler(file_handler)

    def get_logger(self):
        return self.logger

注意:日志级别要区分场景。调试阶段用 DEBUG,跑回归用 INFO,出问题用 ERROR。别一股脑全打 DEBUG,日志文件会爆炸。我见过一个项目一天产生 50GB 日志,硬盘直接写满。

日志格式里我建议带上文件名和行号,这样定位问题快。另外,座舱测试经常涉及多设备并行,每条日志最好带上设备 ID:

2025-03-15 14:23:45 - device_001 - INFO - 开始执行: 蓝牙连接测试
2025-03-15 14:23:46 - device_001 - DEBUG - 发送AT指令: AT+BTCONNECT
2025-03-15 14:23:47 - device_001 - ERROR - 连接超时,重试第1次

4.5 整体结构图

下面这张图把上面讲的内容串起来,你一看就明白:

座舱自动化测试框架 — 项目结构总览 auto_test_framework/ src/ 源码 config/ 配置 data/ 数据 logs/ 日志 docs/ 文档 core/ libs/ pages/ env devices test 核心原则 按职责划分模块 | 配置文件与代码分离 | 日志自动轮转 | 文档同步更新 L1 基础设施 → L2 公共能力 → L3 业务抽象 → L4 测试用例 上层依赖下层,下层不依赖上层 依赖方向 L1 core/ L2 libs/ utils/ L3 pages/ L4 tests/

4.6 避坑指南

最后分享几个我踩过的坑:

  • 目录层级别太深。 我曾经见过一个项目嵌套了 7 层目录,找个文件要翻半天。建议不超过 4 层。
  • 配置文件不要放在 src 里。 否则每次打包都要重新配置,环境隔离形同虚设。
  • 日志一定要轮转。 不轮转的话,跑一个月测试,日志文件能上 TB 级。
  • 模块之间不要循环引用。 比如 pages 引用了 utils,utils 又引用了 pages,这种问题排查起来很痛苦。

我的习惯:项目初期花 30 分钟把目录结构定好,写个 README 说明每个目录的用途。后面所有人按规矩来,省心省力。别等到项目中期再重构,那代价太大了。