—
前言
大家好,我是测试架构师。从业8年,搭建过多个自动化测试框架。很多测试同行问我:”框架该怎么设计?”今天这篇文章,我想系统分享自动化测试框架的设计理念和实践经验。
—
一、为什么需要测试框架?
没有框架的痛点
直接写测试脚本的问题:
- 代码重复:每个测试都要写相同的初始化、清理代码
- 维护困难:需求变化时,大量脚本需要修改
- 难以扩展:增加新功能困难
- 团队协作:代码风格不统一
框架的价值
好的测试框架带来:
- 代码复用:公共逻辑封装
- 易于维护:修改一处,全局生效
- 标准化:统一的代码风格和流程
- 可扩展:插件化设计
—
二、分层架构设计
三层架构模型
经典的自动化测试框架分层:
┌─────────────────────────────────────┐
│ 测试用例层 (Cases) │ 业务测试逻辑
├─────────────────────────────────────┤
│ 业务逻辑层 (Services) │ 业务操作封装
├─────────────────────────────────────┤
│ 页面对象层 (Pages) │ 元素定位、操作
├─────────────────────────────────────┤
│ 基础设施层 (Core) │ Driver、配置、工具
└─────────────────────────────────────┘
各层职责
基础设施层(Core):
- Driver初始化和管理
- 配置读取
- 日志处理
- 报告生成
- 工具函数
页面对象层(Pages):
- 元素定位
- 页面操作封装
- 页面状态判断
业务逻辑层(Services):
- 业务流程封装
- 数据处理
- API调用
测试用例层(Cases):
- 测试逻辑实现
- 断言验证
- 数据准备
—
三、框架目录结构
Python项目结构
test_framework/
├── config/ # 配置文件
│ ├── config.yaml # 主配置
│ ├── dev.yaml # 开发环境
│ └── prod.yaml # 生产环境
├── core/ # 基础设施层
│ ├── driver.py # Driver管理
│ ├── config_loader.py # 配置加载
│ ├── logger.py # 日志模块
│ ├── report.py # 报告生成
│ └── utils.py # 工具函数
├── pages/ # 页面对象层
│ ├── base_page.py # 页面基类
│ ├── login_page.py # 登录页面
│ ├── home_page.py # 首页
│ └── order_page.py # 订单页面
├── services/ # 业务逻辑层
│ ├── user_service.py # 用户业务
│ ├── order_service.py # 订单业务
│ └── auth_service.py # 认证业务
├── tests/ # 测试用例层
│ ├── test_login.py # 登录测试
│ ├── test_order.py # 订单测试
│ └── conftest.py # Pytest配置
├── data/ # 测试数据
│ ├── users.yaml # 用户数据
│ ├── orders.yaml # 订单数据
│ └── test_cases.csv # 测试数据集
├── reports/ # 测试报告
├── logs/ # 日志文件
├── requirements.txt # 依赖
└── pytest.ini # Pytest配置
—
四、核心模块实现
Driver管理
python
core/driver.py
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from core.config_loader import Config
class DriverManager:
“””WebDriver管理类”””
_instance = None
_driver = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def get_driver(self):
“””获取Driver实例”””
if self._driver is None:
self._driver = self._create_driver()
return self._driver
def _create_driver(self):
“””创建Driver”””
config = Config().get(“browser”)
options = webdriver.ChromeOptions()
if config.get(“headless”):
options.add_argument(“–headless”)
options.add_argument(“–no-sandbox”)
options.add_argument(“–disable-dev-shm-usage”)
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
driver.maximize_window()
return driver
def quit_driver(self):
“””关闭Driver”””
if self._driver:
self._driver.quit()
self._driver = None
页面基类
python
pages/base_page.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from core.driver import DriverManager
from core.logger import Logger
class BasePage:
“””页面基类”””
def __init__(self):
self.driver = DriverManager().get_driver()
self.logger = Logger()
self.wait = WebDriverWait(self.driver, 10)
def find_element(self, locator):
“””定位元素”””
self.logger.info(f”定位元素: {locator}”)
return self.wait.until(EC.presence_of_element_located(locator))
def click(self, locator):
“””点击元素”””
element = self.find_element(locator)
self.logger.info(f”点击元素: {locator}”)
element.click()
def input_text(self, locator, text):
“””输入文本”””
element = self.find_element(locator)
self.logger.info(f”输入文本: {text}”)
element.clear()
element.send_keys(text)
def get_text(self, locator):
“””获取文本”””
element = self.find_element(locator)
return element.text
def is_visible(self, locator):
“””判断元素可见”””
try:
self.wait.until(EC.visibility_of_element_located(locator))
return True
except:
return False
业务服务类
python
services/auth_service.py
from pages.login_page import LoginPage
from pages.home_page import HomePage
from core.logger import Logger
class AuthService:
“””认证业务服务”””
def __init__(self):
self.login_page = LoginPage()
self.home_page = HomePage()
self.logger = Logger()
def login(self, username, password):
“””登录流程”””
self.logger.info(f”登录: {username}”)
self.login_page.open()
self.login_page.input_username(username)
self.login_page.input_password(password)
self.login_page.click_login()
return self.home_page.is_logged_in()
def logout(self):
“””退出登录”””
self.logger.info(“退出登录”)
self.home_page.click_logout()
—
五、数据驱动设计
数据源类型
框架支持多种数据源:
| 数据源 | 格式 | 用途 |
|---|---|---|
| YAML | 配置数据 | 环境配置、测试参数 |
| CSV | 表格数据 | 批量测试数据 |
| JSON | 结构化数据 | 复杂测试数据 |
| Excel | 表格数据 | 业务数据 |
YAML数据管理
yaml
data/users.yaml
users:
valid_user:
username: “testuser”
password: “password123”
expected: “success”
invalid_user:
username: “wronguser”
password: “wrongpass”
expected: “failure”
empty_user:
username: “”
password: “”
expected: “validation_error”
CSV数据驱动
python
tests/test_login.py
import pytest
import csv
def load_test_data():
“””加载CSV测试数据”””
with open(“data/test_cases.csv”, “r”) as f:
reader = csv.DictReader(f)
return list(reader)
@pytest.mark.parametrize(“data”, load_test_data())
def test_login(data):
“””数据驱动登录测试”””
service = AuthService()
result = service.login(data[“username”], data[“password”])
assert result == (data[“expected”] == “success”)
—
六、配置管理
配置文件设计
yaml
config/config.yaml
browser:
name: chrome
headless: false
window_size: maximize
test:
timeout: 10
retry: 3
screenshot_on_fail: true
report:
format: allure
output_dir: reports/
environment:
base_url: http://test.example.com
api_url: http://api.test.example.com
配置加载器
python
core/config_loader.py
import yaml
from pathlib import Path
class Config:
“””配置管理”””
_instance = None
_config = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._load_config()
return cls._instance
def _load_config(self):
“””加载配置”””
config_path = Path(“config/config.yaml”)
with open(config_path, “r”) as f:
self._config = yaml.safe_load(f)
def get(self, key, default=None):
“””获取配置”””
keys = key.split(“.”)
value = self._config
for k in keys:
if isinstance(value, dict):
value = value.get(k)
else:
return default
return value if value else default
—
七、日志与报告
日志模块
python
core/logger.py
import logging
from datetime import datetime
class Logger:
“””日志管理”””
def __init__(self, name=”test”):
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
# 文件Handler
file_handler = logging.FileHandler(
f”logs/test_{datetime.now().strftime(‘%Y%m%d’)}.log”
)
file_handler.setLevel(logging.DEBUG)
# 控制台Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 格式化
formatter = logging.Formatter(
“%(asctime)s – %(name)s – %(levelname)s – %(message)s”
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)
def info(self, msg):
self.logger.info(msg)
def error(self, msg):
self.logger.error(msg)
def debug(self, msg):
self.logger.debug(msg)
—
八、框架最佳实践
单例模式应用
Driver、Config、Logger使用单例模式,避免重复创建。
Page Object原则
- 一个页面一个类
- 元素定位器集中管理
- 操作方法命名清晰
业务逻辑封装
- 业务流程独立封装
- 不在测试用例中写业务逻辑
- Service层调用Page层
测试用例规范
- 用例命名:test_模块_功能_场景
- 一个用例验证一个场景
- 断言要明确、完整
—
总结
自动化测试框架设计要点:
| 要点 | 内容 |
|---|---|
| 分层架构 | Cases –
|
| Driver管理 | 单例模式、自动初始化 |
| Page Object | 元素定位、操作封装 |
| 数据驱动 | YAML/CSV/JSON数据源 |
| 配置管理 | 环境隔离、参数配置 |
| 日志报告 | 日志记录、报告生成 |
—
下期预告:Jenkins测试流水线搭建 – CI/CD中的自动化测试集成实战