从零搭建Python自动化测试框架:基于pytest与Selenium的工程实践
1. 项目概述:为什么我们需要一个自己的测试框架?
每次看到团队里测试同学还在手动点点点,或者写着一堆零散、重复、难以维护的脚本,我就觉得是时候聊聊自动化测试框架了。这玩意儿听起来高大上,好像是大厂专利,但其实没那么玄乎。简单说,它就是你给自己和团队搭建的一套“自动化测试工作流”和“工具箱”,目的是把那些重复、枯燥、容易出错的测试任务,用代码和规则管起来,让测试执行更高效,结果更可靠,维护成本更低。
你可能用过 Selenium 录个脚本,或者用 pytest 写几个测试函数,这算自动化,但离“框架”还有距离。一个成型的框架,意味着标准化。比如,所有测试用例的编写有固定的模板和目录结构;测试数据(账号、URL、预期结果)和测试脚本是分离的,改数据不用动代码;测试报告长得都一样,谁都能一眼看懂;环境切换(测试、预发布、生产)只需要改一个配置项。这些好处,在项目初期可能不明显,但随着用例数量从几十个涨到几百上千个,没有框架的团队会陷入“脚本地狱”——找用例像大海捞针,改一个地方要动十个文件,环境一变全盘崩溃。
所以,这个“从0到1搭建高效测试框架”的目标,就是带你一步步走出混沌,用 Python 这门对测试极其友好的语言,构建一个结构清晰、易于扩展、团队协作顺畅的自动化测试基石。无论你是测试开发新手,还是想优化现有流程的资深测试,这套方法都能直接拿来用。
2. 核心需求与设计思路拆解
在动手敲代码之前,我们必须想清楚:这个框架到底要解决哪些痛点?基于常见的团队需求,我总结了以下几个核心设计目标,这也是我们后续所有步骤的指导思想。
2.1 核心需求解析
- 用例与代码分离 :测试逻辑(代码)应该和测试数据(输入、预期输出)分开。这样,当业务数据变更时(比如登录账号密码改了),我们只需要更新数据文件,而不是去翻找和修改每一个测试脚本。这是框架可维护性的基石。
- 执行灵活性与可配置性 :框架必须能轻松地在不同环境(测试、预发布)下运行,并能灵活地选择执行哪些用例(按模块、按标签、按优先级)。通过配置文件来管理环境变量和全局参数是必须的。
- 日志与报告清晰直观 :自动化测试不是运行完就完了。我们需要详尽的执行日志来定位问题,更需要一份美观、信息丰富的测试报告,让项目经理、产品经理等非技术人员也能一目了然地了解测试结果。报告最好能附带截图,对于UI自动化尤其重要。
- 失败重试与稳定性处理 :网络波动、页面加载慢、元素偶尔定位不到……这些不稳定因素会导致用例“假失败”。框架应该具备失败自动重试机制,并能在关键步骤失败时自动截图,保存现场证据。
- 易于集成与持续运行 :框架最终要融入CI/CD(持续集成/持续部署)流水线。这意味着它应该能通过命令行方便地触发,并且生成机器可读(如JUnit XML格式)和人工可读的报告,方便Jenkins、GitLab CI等工具集成。
- 低学习成本与团队协作 :框架结构应该直观,编写新用例的模板应该简单。要建立清晰的目录规范和代码规范,让新成员能快速上手,避免每个人一套写法。
2.2 技术选型与思路
基于以上需求,我们选择 Python 作为主力语言,因为它语法简洁、生态丰富。框架的核心将围绕以下几个明星库搭建:
- pytest :测试框架的不二之选。它比 unittest 更简洁强大,支持丰富的插件(失败重试、并行执行、html报告等)、灵活的fixture(用于测试前置和后置操作)和参数化,完美契合我们的需求。
- Selenium :Web UI自动化的标准。我们将用它来驱动浏览器,模拟用户操作。
- Requests :HTTP接口测试的利器。轻量、高效,用于构建我们的API测试层。
- Allure 或 pytest-html :用于生成测试报告。Allure报告非常强大美观,但需要额外安装Java环境;pytest-html更轻量,开箱即用。我们将以pytest-html为例,兼顾简便与实用。
- OpenPyXL / YAML / JSON :用于管理测试数据。根据团队习惯,可以选择Excel、YAML或JSON文件来存储数据。YAML和JSON更受开发者欢迎,因为它们易于版本控制。
- logging :Python标准库的日志模块,足够记录详细的执行过程。
- configparser 或 python-dotenv :用于管理配置文件。我们将使用
.ini文件或.env文件来存放环境配置。
整体架构思路是“分层设计”:将测试数据、页面对象(对于UI)、测试用例、通用工具、配置、报告输出等分门别类,放在不同的目录中。这样结构清晰,职责单一。
3. 环境准备与项目初始化
工欲善其事,必先利其器。我们先来把“厨房”收拾好。
3.1 Python与IDE环境搭建
首先,确保你安装了Python(建议3.8及以上版本)。可以从Python官网下载安装包,安装时务必勾选“Add Python to PATH”。安装后,在命令行输入 python --version 验证。
我强烈推荐使用 PyCharm (社区版免费)或 VS Code 作为集成开发环境。它们对Python项目管理和调试的支持非常友好。以VS Code为例,你需要安装Python扩展,并配置好解释器。
接下来,为我们的框架创建一个独立的虚拟环境。这能避免项目间的包版本冲突。在项目根目录下打开终端,执行:
# 创建虚拟环境,环境文件夹名为 venv
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
激活后,命令行提示符前会出现 (venv) 标识。
3.2 核心依赖库安装
在虚拟环境激活的状态下,我们安装框架所需的库。建议使用 requirements.txt 文件来管理依赖。先在项目根目录创建一个 requirements.txt 文件,内容如下:
pytest>=7.0.0
selenium>=4.0.0
requests>=2.28.0
pytest-html>=3.2.0
pytest-rerunfailures>=10.0 # 用于失败重试
openpyxl>=3.0.0 # 用于读取Excel测试数据
PyYAML>=6.0 # 用于读取YAML测试数据
python-dotenv>=0.20.0 # 用于读取.env配置
webdriver-manager>=3.8.0 # 自动管理浏览器驱动
然后,在终端执行安装命令:
pip install -r requirements.txt
webdriver-manager 是个神器,它能自动下载和匹配对应版本的浏览器驱动(如ChromeDriver),省去手动下载配置的麻烦。
3.3 项目目录结构设计
清晰的目录结构是框架的骨架。在项目根目录下,创建如下文件夹和文件:
your_project_name/
├── configs/ # 配置文件目录
│ ├── __init__.py
│ ├── config.ini # 主配置文件
│ └── env_config.py # 环境配置读取类
├── data/ # 测试数据目录
│ ├── __init__.py
│ ├── test_data.yaml # 或 test_data.json, test_cases.xlsx
│ └── data_loader.py # 数据读取工具类
├── page_objects/ # 页面对象模型(PO)目录,UI自动化核心
│ ├── __init__.py
│ ├── base_page.py # 页面基类
│ └── login_page.py # 示例:登录页面类
├── test_cases/ # 测试用例目录
│ ├── __init__.py
│ ├── conftest.py # pytest共享fixture
│ ├── test_api/ # 接口测试用例
│ │ ├── __init__.py
│ │ └── test_user_api.py
│ └── test_ui/ # UI测试用例
│ ├── __init__.py
│ └── test_login.py
├── utils/ # 通用工具目录
│ ├── __init__.py
│ ├── logger.py # 日志工具
│ └── common_utils.py # 其他通用函数
├── reports/ # 测试报告输出目录(.gitignore忽略)
│ └── assets/ # 存放失败截图等
├── logs/ # 日志文件目录(.gitignore忽略)
├── requirements.txt # 项目依赖
└── pytest.ini # pytest配置文件
每个目录下的 __init__.py 文件(可以是空文件)是为了让Python将这个目录视为一个包(package),从而可以相互导入模块。
注意 :
reports/和logs/文件夹因为存放的是每次运行生成的动态文件,应该被添加到.gitignore文件中,避免提交到代码仓库。
4. 核心模块实现详解
架子搭好了,现在我们开始砌墙,实现框架最核心的几个模块。
4.1 配置管理模块实现
配置是框架的“开关面板”。我们使用 config.ini 文件来集中管理所有环境相关的变量。 在 configs/config.ini 中写入:
[test]
base_url = https://test.example.com
username = test_user
password = test_pass123
headless = false # 浏览器是否无头模式运行
browser = chrome
timeout = 10
[prod]
base_url = https://example.com
username = prod_user
password = prod_pass456
headless = true
browser = chrome
timeout = 10
然后,创建 configs/env_config.py 来读取这些配置:
import os
from configparser import ConfigParser
from pathlib import Path
class Config:
"""配置管理类"""
def __init__(self, env='test'):
self.config = ConfigParser()
# 获取config.ini文件的绝对路径
config_path = Path(__file__).parent / 'config.ini'
self.config.read(config_path, encoding='utf-8')
self.env = env
def get(self, key, section=None):
"""获取配置项"""
if not section:
section = self.env
return self.config.get(section, key)
def getboolean(self, key, section=None):
"""获取布尔型配置项"""
if not section:
section = self.env
return self.config.getboolean(section, key)
def getint(self, key, section=None):
"""获取整型配置项"""
if not section:
section = self.env
return self.config.getint(section, key)
# 创建一个全局配置实例,默认使用test环境
# 可以通过环境变量 CONFIG_ENV 来覆盖,方便CI/CD集成
current_env = os.getenv('CONFIG_ENV', 'test')
config = Config(current_env)
这样,在代码的任何地方,我们都可以通过 from configs.env_config import config 来使用 config.get('base_url') 获取当前环境的配置,切换环境只需改一个环境变量。
4.2 日志模块封装
好的日志是调试的救命稻草。我们封装一个简单的日志工具。在 utils/logger.py 中:
import logging
import os
from pathlib import Path
from datetime import datetime
def setup_logger(name='auto_test', log_level=logging.INFO):
"""
设置并返回一个logger实例
"""
# 创建logger
logger = logging.getLogger(name)
logger.setLevel(log_level)
# 避免重复添加handler
if logger.handlers:
return logger
# 创建logs目录
log_dir = Path(__file__).parent.parent / 'logs'
log_dir.mkdir(exist_ok=True)
# 日志文件名带日期
log_file = log_dir / f'test_{datetime.now().strftime("%Y%m%d")}.log'
# 创建文件handler
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(log_level)
# 创建控制台handler
console_handler = logging.StreamHandler()
console_handler.setLevel(log_level)
# 设置日志格式
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加handler到logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
# 创建一个全局logger实例
logger = setup_logger()
在测试用例中,导入这个logger: from utils.logger import logger ,然后就可以用 logger.info(“开始登录操作”) 、 logger.error(“元素未找到”) 来记录信息了。日志会同时输出到控制台和按日期分割的文件中,格式里包含了文件名和行号,定位问题非常方便。
4.3 测试数据驱动实现
数据驱动测试是框架的灵魂。这里以YAML格式为例,展示如何将数据与脚本分离。 首先,在 data/test_data.yaml 中定义数据:
login_test:
positive:
- username: "correct_user"
password: "correct_pass"
expected: "登录成功"
negative:
- username: "wrong_user"
password: "correct_pass"
expected: “用户名或密码错误”
- username: "correct_user"
password: ""
expected: “密码不能为空”
然后,创建 data/data_loader.py 来加载这些数据:
import yaml
import json
from pathlib import Path
class DataLoader:
@staticmethod
def load_yaml(file_name):
"""加载YAML文件"""
file_path = Path(__file__).parent / file_name
with open(file_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
@staticmethod
def load_json(file_name):
"""加载JSON文件"""
file_path = Path(__file__).parent / file_name
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
# 示例:获取登录测试数据
test_data = DataLoader.load_yaml('test_data.yaml')
login_positive_cases = test_data['login_test']['positive']
在测试用例中,我们将使用pytest的 @pytest.mark.parametrize 装饰器,配合这些数据来运行多条测试。这样,增加一个新的测试场景,只需要在YAML文件里加一组数据,无需修改测试代码。
4.4 页面对象模型(PO)封装(针对UI测试)
页面对象模型是UI自动化测试的最佳实践,它将页面元素定位和操作封装成类,使测试脚本更清晰,元素变更时只需修改页面类。 首先,在 page_objects/base_page.py 中定义一个所有页面类的基类:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from utils.logger import logger
class BasePage:
"""页面基类,封装通用操作"""
def __init__(self, driver):
self.driver = driver
self.timeout = 10 # 默认等待超时时间
def find_element(self, locator):
"""查找单个元素,显式等待"""
try:
logger.info(f"查找元素: {locator}")
element = WebDriverWait(self.driver, self.timeout).until(
EC.presence_of_element_located(locator)
)
return element
except TimeoutException:
logger.error(f"元素未找到: {locator}")
# 这里可以添加截图操作
raise
def click(self, locator):
"""点击元素"""
element = self.find_element(locator)
element.click()
logger.info(f"点击元素: {locator}")
def input_text(self, locator, text):
"""输入文本"""
element = self.find_element(locator)
element.clear()
element.send_keys(text)
logger.info(f"在元素 {locator} 中输入: {text}")
def get_text(self, locator):
"""获取元素文本"""
element = self.find_element(locator)
text = element.text
logger.info(f"获取元素 {locator} 文本: {text}")
return text
然后,实现一个具体的页面,例如 page_objects/login_page.py :
from selenium.webdriver.common.by import By
from page_objects.base_page import BasePage
class LoginPage(BasePage):
# 页面元素定位器(推荐使用元组形式)
USERNAME_INPUT = (By.ID, 'username')
PASSWORD_INPUT = (By.ID, 'password')
LOGIN_BUTTON = (By.XPATH, '//button[@type="submit"]')
ERROR_MSG = (By.CLASS_NAME, 'error-message')
SUCCESS_MSG = (By.ID, 'welcome')
def __init__(self, driver):
super().__init__(driver)
self.driver = driver
def login(self, username, password):
"""登录操作"""
self.input_text(self.USERNAME_INPUT, username)
self.input_text(self.PASSWORD_INPUT, password)
self.click(self.LOGIN_BUTTON)
def get_error_message(self):
"""获取错误信息"""
return self.get_text(self.ERROR_MSG)
def get_welcome_message(self):
"""获取登录成功欢迎信息"""
return self.get_text(self.SUCCESS_MSG)
这样,在测试脚本中,我们只需要关心业务逻辑: login_page.login(“user”, “pass”) ,而不用关心元素是怎么找到的、怎么点击的。如果页面元素ID变了,我们只需要修改 LoginPage 类中的定位器即可。
5. 测试用例编写与组织
有了前面的基础设施,现在可以愉快地编写测试用例了。我们将遵循pytest的规则来组织用例。
5.1 编写第一个API测试用例
在 test_cases/test_api/test_user_api.py 中,我们编写一个简单的用户接口测试:
import pytest
import requests
from configs.env_config import config
from utils.logger import logger
class TestUserAPI:
"""用户相关API测试"""
base_url = config.get('base_url')
api_prefix = '/api/v1'
def test_get_user_info_success(self):
"""测试成功获取用户信息"""
user_id = 1
url = f"{self.base_url}{self.api_prefix}/users/{user_id}"
logger.info(f"请求URL: {url}")
response = requests.get(url)
logger.info(f"响应状态码: {response.status_code}")
logger.info(f"响应体: {response.text}")
# 断言
assert response.status_code == 200
json_data = response.json()
assert json_data['id'] == user_id
assert 'username' in json_data
assert 'email' in json_data
@pytest.mark.parametrize("user_id, expected_status", [
(999, 404), # 不存在的用户
('abc', 400), # 无效的用户ID格式
])
def test_get_user_info_failure(self, user_id, expected_status):
"""测试获取用户信息失败场景(参数化)"""
url = f"{self.base_url}{self.api_prefix}/users/{user_id}"
response = requests.get(url)
assert response.status_code == expected_status
这个例子展示了基本的API测试结构:准备请求、发送请求、记录日志、断言响应。 @pytest.mark.parametrize 装饰器让我们能用多组数据运行同一个测试函数,高效覆盖多种边界情况。
5.2 编写第一个UI测试用例
UI测试需要管理浏览器的生命周期。我们使用pytest的fixture来实现。首先,在 test_cases/conftest.py 中定义全局fixture:
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from configs.env_config import config
from utils.logger import logger
@pytest.fixture(scope="function") # 每个测试函数执行一次
def driver():
"""初始化WebDriver"""
options = webdriver.ChromeOptions()
if config.getboolean('headless'):
options.add_argument('--headless') # 无头模式
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
options.add_argument('--window-size=1920,1080')
# 使用webdriver-manager自动管理驱动
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=options)
driver.implicitly_wait(config.getint('timeout')) # 隐式等待
logger.info("Chrome浏览器已启动")
yield driver # 将driver对象提供给测试用例
# 测试结束后执行清理
driver.quit()
logger.info("Chrome浏览器已关闭")
@pytest.fixture
def login_page(driver):
"""提供登录页面对象"""
from page_objects.login_page import LoginPage
return LoginPage(driver)
然后,在 test_cases/test_ui/test_login.py 中编写UI测试:
import pytest
import allure # 可选,用于生成更漂亮的Allure报告步骤
from data.data_loader import DataLoader
from configs.env_config import config
# 加载测试数据
test_data = DataLoader.load_yaml('test_data.yaml')
positive_cases = test_data['login_test']['positive']
negative_cases = test_data['login_test']['negative']
class TestLogin:
"""登录功能测试"""
@pytest.mark.parametrize("case_data", positive_cases)
def test_login_success(self, driver, login_page, case_data):
"""正向用例:登录成功"""
username = case_data['username']
password = case_data['password']
expected = case_data['expected']
# 访问登录页
driver.get(config.get('base_url') + '/login')
# 执行登录操作
login_page.login(username, password)
# 断言
welcome_text = login_page.get_welcome_message()
assert expected in welcome_text
# 也可以断言URL跳转等
@pytest.mark.parametrize("case_data", negative_cases)
def test_login_failure(self, driver, login_page, case_data):
"""反向用例:登录失败"""
username = case_data['username']
password = case_data['password']
expected = case_data['expected']
driver.get(config.get('base_url') + '/login')
login_page.login(username, password)
error_text = login_page.get_error_message()
assert expected in error_text
注意,测试用例函数名要以 test_ 开头,pytest才能自动发现它们。我们通过 driver 和 login_page 这两个fixture,轻松获得了浏览器驱动和页面对象。数据驱动让用例非常简洁。
6. 高级特性与框架优化
基础框架跑通后,我们可以加入一些“高级”特性,让它更健壮、更高效。
6.1 失败重试与截图功能
测试环境不稳定导致用例偶发失败很常见。我们可以用 pytest-rerunfailures 插件来实现失败自动重试。在 pytest.ini 配置文件中添加:
[pytest]
addopts = --html=reports/report.html --self-contained-html --reruns 2 --reruns-delay 1
testpaths = test_cases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
--reruns 2 表示失败后重试2次, --reruns-delay 1 表示每次重试间隔1秒。
同时,我们需要增强框架,在测试失败时自动截图。修改 conftest.py 中的 driver fixture,或者创建一个新的fixture:
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""
获取测试用例执行结果的钩子函数,用于实现失败截图。
"""
outcome = yield
report = outcome.get_result()
# 只关注用例调用(call)阶段,且是失败或错误的情况
if report.when == "call" and report.failed:
# 尝试从fixture中获取driver对象
for name, fixture_value in item.funcargs.items():
if name == "driver" and hasattr(fixture_value, 'get_screenshot_as_file'):
# 确保reports/assets目录存在
import os
screenshot_dir = os.path.join(os.path.dirname(__file__), '..', 'reports', 'assets')
os.makedirs(screenshot_dir, exist_ok=True)
# 生成截图文件名
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
screenshot_path = os.path.join(screenshot_dir, f"{item.name}_{timestamp}.png")
fixture_value.save_screenshot(screenshot_path)
# 将截图路径添加到html报告中(需要pytest-html支持)
if hasattr(report, 'extra'):
from pytest_html import extras
report.extra.append(extras.image(screenshot_path))
logger.error(f"测试失败,截图已保存至: {screenshot_path}")
break
这段代码是一个pytest钩子,它会在每个测试用例执行后检查结果。如果失败了,它会遍历测试用例的参数,找到名为 driver 的fixture(即我们的浏览器对象),然后调用其截图方法保存图片,并尝试将图片链接添加到HTML报告中。
6.2 测试报告生成
我们使用 pytest-html 插件生成报告。配置已经在 pytest.ini 的 addopts 中指定了 --html=reports/report.html 。运行测试后,会在 reports 目录下生成一个独立的HTML文件。报告里会包含测试概述、通过/失败/跳过的用例列表、每个用例的执行时长,以及我们上面添加的失败截图链接。
如果你需要更强大、更美观的报告,可以考虑使用 Allure 。它需要额外安装Java和Allure命令行工具,但生成的报告支持趋势图、用例分类、步骤详情等高级功能,非常适合大型项目。集成Allure需要安装 pytest-allure 插件,并在运行命令中添加 --alluredir=./allure-results 参数。
6.3 测试用例标记与筛选
当用例成百上千时,我们可能只想运行某一类用例。pytest的mark功能可以轻松实现。 首先,在 pytest.ini 中注册自定义标记,避免警告:
[pytest]
# ... 其他配置 ...
markers =
smoke: 冒烟测试用例
regression: 回归测试用例
slow: 执行较慢的用例
然后,在测试用例上打标记:
import pytest
class TestLogin:
@pytest.mark.smoke # 标记为冒烟测试
def test_login_with_admin(self):
pass
@pytest.mark.regression
@pytest.mark.slow
def test_login_with_multiple_roles(self):
pass
运行测试时,可以按标记筛选:
# 只运行冒烟测试
pytest -m smoke
# 运行除了慢用例外的所有用例
pytest -m "not slow"
# 运行回归测试或冒烟测试
pytest -m "regression or smoke"
7. 框架运行、集成与维护
框架搭建完毕,最后一步是让它能方便地运行,并融入团队的工作流。
7.1 运行测试与常用命令
在项目根目录下,我们可以通过不同的pytest命令来运行测试:
# 1. 运行所有测试
pytest
# 2. 运行指定目录下的测试
pytest test_cases/test_ui/
# 3. 运行指定文件中的测试
pytest test_cases/test_ui/test_login.py
# 4. 运行指定类中的测试
pytest test_cases/test_ui/test_login.py::TestLogin
# 5. 运行指定测试方法
pytest test_cases/test_ui/test_login.py::TestLogin::test_login_success
# 6. 使用标记筛选运行
pytest -m smoke
pytest -m "not slow"
# 7. 输出详细日志
pytest -v
# 输出更详细的日志,包括print语句
pytest -v -s
# 8. 生成JUnit XML格式报告,便于CI工具(如Jenkins)解析
pytest --junitxml=reports/junit.xml
为了方便团队使用,可以在项目根目录创建一个 run_tests.py 脚本或 Makefile 来封装常用命令。
7.2 集成到CI/CD流水线
自动化测试只有集成到CI/CD中,每次代码提交后自动运行,才能真正发挥作用。以GitLab CI为例,可以在项目根目录创建 .gitlab-ci.yml 文件:
stages:
- test
ui-automation-test:
stage: test
image: python:3.9-slim # 使用带有Python的Docker镜像
before_script:
- apt-get update && apt-get install -y wget unzip chromium chromium-driver # 安装浏览器和驱动
- pip install -r requirements.txt
script:
- export CONFIG_ENV=test # 设置测试环境
- pytest test_cases/ --html=reports/report.html --self-contained-html --junitxml=reports/junit.xml
artifacts:
when: always
paths:
- reports/
expire_in: 1 week
only:
- merge_requests # 仅在合并请求时触发
- main # 或在推送到主分支时触发
这样,每当有代码合并请求时,GitLab会自动在一个干净的环境中安装依赖,运行所有测试,并生成报告。测试结果(通过/失败)会直接显示在合并请求页面上。
7.3 框架维护与最佳实践
框架不是一劳永逸的,需要持续维护。这里分享几个关键实践:
- 代码审查 :所有测试代码(包括页面对象、工具类、测试用例)的提交都应进行代码审查,确保符合框架规范。
- 定期重构 :随着业务变化,及时重构页面对象和工具方法,删除重复代码,优化定位策略(如优先使用ID、CSS Selector,谨慎使用XPath)。
- 用例稳定性监控 :关注CI流水线中用例的通过率。对经常“假失败”(flaky)的用例要重点分析,是环境问题、脚本问题还是应用本身不稳定,并加以修复或标记。
- 数据与环境隔离 :测试数据必须是独立的,不能影响线上或其他测试环境。使用测试专用的账号、数据,并在用例执行前后做好清理(如删除测试创建的数据)。
- 文档与培训 :维护一个简单的框架使用Wiki,记录目录结构、编写规范、常用命令。对新成员进行框架使用的培训。
踩过不少坑之后,我最大的体会是: 不要追求一步到位的大而全框架 。先从解决团队当前最痛的1-2个问题开始(比如先把冒烟测试自动化),采用本文这种渐进式的方式搭建,让框架随着项目和团队一起成长。初期结构清晰、易于扩展比功能繁多更重要。当团队所有人都能遵循同一套规范,顺畅地添加和维护用例时,这个框架的价值就真正体现出来了。
更多推荐
所有评论(0)