本文目录导读:

我来介绍如何系统地编写自动化测试脚本,涵盖从基础到进阶的实践方法。
测试框架选择
主流框架对比
# Python - Pytest: 简单灵活,插件丰富 - Unittest: Python内置,无需额外安装 - Robot Framework: 关键字驱动,易读性高 # JavaScript/TypeScript - Jest: React生态首选 - Playwright: 现代Web测试利器 - Cypress: 开发者友好,调试方便 # Java - Selenium: 老牌Web测试工具 - TestNG: 功能强大,支持并行 - JUnit: Java标准测试框架
编写测试脚本的基本结构
简单的Pytest测试示例
# test_login.py
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestLogin:
"""登录功能测试"""
def setup_method(self):
"""每个测试方法执行前的准备"""
self.driver = webdriver.Chrome()
self.driver.get("https://example.com")
self.wait = WebDriverWait(self.driver, 10)
def teardown_method(self):
"""每个测试方法执行后的清理"""
self.driver.quit()
def test_valid_login(self):
"""测试有效登录"""
# 定位元素
username = self.driver.find_element(By.ID, "username")
password = self.driver.find_element(By.ID, "password")
submit_btn = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
# 执行操作
username.send_keys("test_user")
password.send_keys("password123")
submit_btn.click()
# 断言结果
welcome_msg = self.wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "welcome"))
)
assert "欢迎" in welcome_msg.text
def test_invalid_login(self):
"""测试无效登录"""
username = self.driver.find_element(By.ID, "username")
password = self.driver.find_element(By.ID, "password")
username.send_keys("wrong_user")
password.send_keys("wrong_password")
error_msg = self.driver.find_element(By.CLASS_NAME, "error")
assert "用户名或密码错误" in error_msg.text
测试脚本设计模式
Page Object Model(POM)
# pages/base_page.py
class BasePage:
"""所有页面的基类"""
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def find_element(self, locator):
return self.wait.until(
EC.presence_of_element_located(locator)
)
def click(self, locator):
element = self.find_element(locator)
element.click()
def input_text(self, locator, text):
element = self.find_element(locator)
element.clear()
element.send_keys(text)
# pages/login_page.py
class LoginPage(BasePage):
"""登录页面"""
# 定位器
USERNAME = (By.ID, "username")
PASSWORD = (By.ID, "password")
SUBMIT = (By.CSS_SELECTOR, "button[type='submit']")
ERROR_MSG = (By.CLASS_NAME, "error")
def login(self, username, password):
"""执行登录操作"""
self.input_text(self.USERNAME, username)
self.input_text(self.PASSWORD, password)
self.click(self.SUBMIT)
def get_error_message(self):
"""获取错误信息"""
return self.find_element(self.ERROR_MSG).text
数据驱动测试
# test_data.json
{
"valid_cases": [
{
"username": "user1",
"password": "pass123",
"expected": "登录成功"
},
{
"username": "admin",
"password": "admin123",
"expected": "欢迎管理员"
}
],
"invalid_cases": [
{
"username": "",
"password": "",
"expected": "请输入用户名"
}
]
}
# 使用参数化
import pytest
import json
class TestLoginDataDriven:
@pytest.fixture
def test_data(self):
with open('test_data.json', 'r') as f:
return json.load(f)
@pytest.mark.parametrize("case", [
{"username": "user1", "password": "pass123", "expected": "登录成功"},
{"username": "admin", "password": "admin123", "expected": "欢迎管理员"}
])
def test_valid_login_cases(self, case):
# 测试用例逻辑
assert login(case["username"], case["password"]) == case["expected"]
等待策略
显式等待
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 等待特定条件
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "submit_button"))
)
# 等待页面加载完成
WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# 等待Ajax请求完成
WebDriverWait(driver, 10).until(
lambda driver: driver.execute_script("return jQuery.active == 0")
)
隐式等待
# 设置全局等待时间 driver.implicitly_wait(10) # 10秒
断言的使用
Pytest断言
import pytest
def test_assertions():
# 基本断言
assert result == expected
assert result != expected
assert condition is True
assert value is not None
# 集合断言
assert "substring" in text
assert 1 in [1, 2, 3]
assert {"key": "value"} in list_of_dicts
# 数值断言
assert abs(calculated - expected) < tolerance
# 自定义错误消息
assert condition, f"Condition failed with {variable}"
# 异常断言
with pytest.raises(ValueError) as exc_info:
function_that_raises()
assert "error message" in str(exc_info.value)
测试报告生成
Pytest-html
# conftest.py
import pytest
import os
from datetime import datetime
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
"""配置报告"""
if not os.path.exists('reports'):
os.makedirs('reports')
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
config.option.htmlpath = f'reports/report_{timestamp}.html'
# 运行命令
pytest test_login.py --html=reports/report.html --self-contained-html
Allure报告
# test_login.py
import allure
@allure.feature("用户登录")
class TestLogin:
@allure.story("正常登录")
@allure.severity(allure.severity_level.CRITICAL)
def test_valid_login(self):
with allure.step("输入用户名"):
username.send_keys("test_user")
with allure.step("输入密码"):
password.send_keys("password123")
with allure.step("点击登录按钮"):
submit.click()
with allure.step("验证登录成功"):
assert welcome_message.is_displayed()
# 运行命令
pytest --alluredir=./allure-results
allure serve ./allure-results
最佳实践
测试脚本组织架构
tests/
├── conftest.py # 共享fixtures
├── pages/ # 页面对象
│ ├── __init__.py
│ ├── base_page.py
│ └── login_page.py
├── test_cases/ # 测试用例
│ ├── __init__.py
│ ├── test_login.py
│ └── test_register.py
├── data/ # 测试数据
│ ├── test_data.json
│ └── test_data.yaml
├── utils/ # 工具函数
│ ├── __init__.py
│ ├── logger.py
│ └── db_utils.py
├── config/ # 配置文件
│ └── config.ini
└── reports/ # 测试报告
关键原则
# 1. 独立性
@pytest.fixture
def unique_data():
"""每个测试用例使用唯一数据"""
timestamp = int(time.time() * 1000)
return f"test_user_{timestamp}"
# 2. 可重复性
def test_repeatable():
"""测试不依赖外部状态"""
for _ in range(3):
result = function_being_tested()
assert result == expected
# 3. 可读性
def test_user_can_purchase_item():
"""描述清晰的测试名称"""
# Given: 用户已登录且有商品
# When: 用户点击购买
# Then: 订单被创建
pass
# 4. 错误捕获
def test_with_retry():
"""重试机制"""
@retry(max_attempts=3, wait=1)
def flaky_operation():
return operation_may_fail()
CI/CD集成
Jenkins配置
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git 'https://github.com/your/repo.git'
}
}
stage('Test') {
steps {
sh 'pip install -r requirements.txt'
sh 'pytest tests/ --junitxml=report.xml'
}
}
stage('Reports') {
steps {
junit 'report.xml'
}
}
}
}
GitHub Actions
name: Automated Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Run tests
run: |
pytest tests/ --html=report.html
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: test-report
path: report.html
常见问题处理
元素定位问题
def wait_for_element(driver, locator, timeout=10):
"""智能等待元素"""
try:
element = WebDriverWait(driver, timeout).until(
EC.presence_of_element_located(locator)
)
return element
except TimeoutException:
# 截图方便调试
driver.save_screenshot(f"debug_{str(locator)}.png")
raise
# 多策略定位
def find_element_flexible(driver, strategies):
"""尝试多种定位策略"""
for strategy in strategies:
try:
return driver.find_element(*strategy)
except NoSuchElementException:
continue
raise NoSuchElementException("所有定位策略都失败")
异步处理
import asyncio
from playwright.async_api import async_playwright
async def test_async_flow():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
# 异步等待
await page.wait_for_load_state('networkidle')
# 并行执行
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(page.locator("#element1").click())
task2 = tg.create_task(page.locator("#element2").click())
await browser.close()
这些是编写自动化测试脚本的核心技能,建议从简单的项目开始,逐步应用到复杂的测试场景中,记得保持测试代码的维护性和可扩展性。