碎碎念
自从软件交付节奏不断加快,测试从“事后补救”走向“开发驱动”,测试框架的选型直接决定了项目的维护效率与工程质量。在自动化测试领域,Selenium 凭借其强大的浏览器驱动能力早已成为行业标准,而 PyTest 以简洁灵活的语法、强大的插件生态迅速崛起,成为 Python 测试框架的首选。将两者结合,能显著提升测试代码的组织性与可维护性,实现测试逻辑与浏览器操作的高效协同。
本文将以 12306 网站为测试对象,深入讲解如何基于 Selenium 与 pytest 构建一套结构清晰、可扩展、适配持续集成的自动化测试体系。从项目结构设计到用例管理,从参数化配置到断言优化,我们将以实际代码为载体,探索自动化测试在复杂 Web 场景下的最佳实践路径。
需要提醒的是,本文假设你已经学习并实践了 Selenium WebDriver — Web自动化测试标准工具 – Ethan的博客 ,该文章中有对任务背景及 Selenium 用法的必要介绍。你可以在 Ethan的云盘中获取本文的完整实践代码,Good Luck!
1. Pytest 概述
1.1 Pytest 历史
pytest的历史始于2002年PyPy项目,其团队在开发过程中特别重视测试框架的改进。最初他们使用Python标准库的unittest.py,但发现其过于繁琐。2004年,核心开发者Holger Krekel在Europython大会上提出了"std"项目(后改名为py库),其中包含了一个名为std.utest的子模块,这就是pytest的前身。这个模块创新性地实现了"魔法断言"功能,能够自动解析断言表达式并输出详细的错误信息,大幅提升了测试的便利性。2004年9月,py库正式从PyPy项目中分离出来独立发展。经过多年的完善,2010年发布的pytest 2.0.0标志着它成为一个完全独立的测试框架。2016年的3.0.0版本又将命令行入口从py.test简化为pytest,使其更加易用。如今,pytest凭借其简洁的语法和强大的插件系统,已成为Python生态系统中最受欢迎的测试框架,而其"零样板代码"的设计理念从诞生之初就一直延续至今。
1.2 Pytest 安装
你可以通过 pip
安装 pytest
包:
pip install pytest
当然,比起直接用 pip
安装,我更推荐使用 Anaconda 来管理环境和依赖。你可以在左边栏 Environments 里方便的搜索到 pytest
包 :
在终端键入以下命令,如出现版本号则说明安装成功:
pytest --version
2. Pytest 基本用法
在继续 12306 测试的第四步前,了解一些最基本的 Pytest 知识是必要的。
2.1 Pytest 默认测试用例
pytest
默认测试用例的格式:
- 模块名:模块通常被统一放在一个
testcases
文件夹中,需要保证模块名以test_
开头或_test
结尾,例如test_demo1.py
或demo2_test.py
- 类名:测试类类名必须以
Test
开头,并且不能带有init
方法 - 方法名:测试方法名(Case 名)必须以
test_
开头,例如test_demo1(self)
、test_demo2(self)
2.2 全局配置文件 pytest.ini
我们可以在 pytest.ini
中进行一些属性的配置来指定 Pytest 按何种方式执行测试,在此之前需要在根目录下创建,名称必须是 pytest.ini
。以下是常见的属性配置:
[pytest]
# 执行pytest命令的附加参数
addopts = ‐vs
# 默认的执行路径,它会默认执行该文件夹下所有的满足条件的测试case
testpaths = ./testcases
# 文件命名规则
python_files = test_*.py
# 类名命名规则
python_classes = Test*
# Case命名规则
python_functions = test_*
# 标记
markers =
# 冒烟规则
smoke:冒烟用例
product_manage:商品管理
是不是有点像 docker-compose. yaml 或 build. gradle 之类的配置文件 ?
2.3 执行 pytest
最简单的执行方式就是直接在终端命令行输入 pytest
,如果存在 pytest.ini
,它会根据文件内容执行测试;如果没有就按照默认格式执行测试。我们可以通过一些参数来强化 pytest 参数指令:
# -vs: -v输出详细信息 -s输出调试信息
pytest -vs
# -n: 多线程运行(前提安装插件:pytest-xdist)
pytest -vs -n=2
# --reruns num: 失败重跑(前提安装插件:pytest-rerunfailres)
pytest -vs --reruns=2
# -x: 出现一个用例失败则停止测试
pytest -vs -x
# --maxfail: 出现几个失败才终止
pytest -vs --maxfail=2
# --html: 生成html的测试报告,后面 需要跟上所创建的文件位置及文件名称(前提安装插件:pytest-html)
pytest -vs --html ./reports/result.html
# -k: 运行测试用例名称中包含某个字符串的测试用例,我们可以采用or表示或者,采用and表示都
pytest -vs -k "qiuluo"
pytest -vs -k "qiuluo or weiliang"
pytest -vs -k "qiuluo and weiliang"
# -m:一组用例执行,后面需要跟一个组名称,执行user_manage这个分组
pytest -vs -m user_manage
除了在终端直接键入命令外,你也可以通过 main
方法来执行:
if __name__ == '__main__':
pytest.main()
# 带参数的main方法
if __name__ == '__main__':
pytest.main(["‐vs"])
2.4 对测试进行分组
Pytest 允许我们在测试函数上使用标记。标记用于设置各种特征/属性来测试功能。Pytest 提供了许多内置标记,例如 xfail、skip 和 parametrize。除此之外,用户可以创建自己的标记名称。使用下面给出的语法将标记应用于测试:
@pytest.mark.<markername>
给出代码示例:
# test_demo1.py
# author: ethan
class TestDemo:
# 我们在Case上采用@pytest.mark. + 分组名称,就相当于该方法被划分为该分组中
# 注意:一个分组可以有多个方法(case),一个方法也可以被划分到多个分组中
@pytest.mark.user_manage
def test_demo1(self):
print("user_manage_test1")
@pytest.mark.product_manage
def test_demo2(self):
print("product_manage_test1")
@pytest.mark.user_manage
@pytest.mark.product_manage
def test_demo3(self):
print("manage_test1")
尝试执行一下:
# 确保你已经按照本文方法配置了 pytest.int
pytest -vs -m user_manage
测试结果如下,成功执行了分组为 user_manage
的两条测试用例!
2.5 跳过某些不需要的 case
Pytest 的跳过方法其实和 unittest 是完全相同的,我们只需采用 skip
或 skipif
方法来指定参数并贴在方法上即可跳过,给出代码示例:
# @pytest.mark.skip(跳过原因)
# @pytest.mark.skipif(跳过条件,跳过原因)
# 示例
class TestDemo:
workage2 = 5
workage3 = 20
@pytest.mark.skip(reason="无理由跳过")
def test_demo1(self):
print("我被跳过了")
@pytest.mark.skipif(workage2<10,reason="工作经验少于10年跳过")
def test_demo2(self):
print("由于经验不足,我被跳过了")
@pytest.mark.skipif(workage3<10,reason="工作经验少于10年跳过")
def test_demo3(self):
print("由于经验过关,我被执行了")
def test_demo3(self):
print("我没有跳过条件,所以我被执行了")
2.6 断言
Pytest 使用 assert
表达式进行断言,基本语法如下:
assert [expression]
# 带 description 的 assert 表达式。
# 如果断言失败,description 作为 AssertionError的内容展示
assert [expression,description]
给出示例代码如下:
def test_case_01():
print("进入测试函数")
assert add(2, 3) == 5
print("断言成功")
def test_case_03():
print("进入测试函数")
# 带描述性错误消息的用法
assert add(2, 3) == 10, "结果与预期不符"
print("断言失败") # 不会被执行
至此,我们掌握了 pytest 的基本用法,更高级的用法(夹具、参数化测试等)可以到 Pytest教程中学习。接下来我们完成 12306 网站测试任务的第四步。
3. Selenium + Pytest
有了基础知识的铺垫,完成第四步就非常简单了。第一步,我们需要把上一篇文章中实现的返回历时最短车次的逻辑封装成一个函数:
#-----------------------------------------
# get_shortest_duration_train
# 输入 :无
# 输出 :字典(历时最短车辆信息)
# 描述 :
#-----------------------------------------
def get_shortest_duration_train():
# .... 具体逻辑
return shortest_train
然后编写测试用例:
#-------------------------------------
# test_shortestTrain.py
# Author : 周正
# Email :ethan.zheng.zhou@outlook.com
# Description :
# 测试用例
#--------------------------------------
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")))
from SeleniumPyTest import get_shortest_duration_train
class TestDemo:
@classmethod
def setup_class(cls):
"""在所有测试前执行一次,获取最短车次信息"""
cls.shortest_train = get_shortest_duration_train()
def test_number(self):
assert self.shortest_train['number'] == 'G9'
def test_startTime(self):
assert self.shortest_train['start_time'] == '14:34'
def test_endTime(self):
assert self.shortest_train['end_time'] == '15:37'
def test_duration(self):
assert self.shortest_train['duration'] == '01:03'
键入命令并查看测试结果:
pytest
至此,12306 网站测试任务成功完成!
写给看到最后的你
感谢你坚持阅读完这篇Selenium与Pytest的整合指南!通过12306 测试案例的完整实现,相信你已经掌握了从基础断言到复杂测试框架搭建的核心方法论。自动化测试的真正价值在于将质量保障转化为可复用的工程资产,而持续探索才是应对快速迭代的技术生态的不二法门。如果在实践过程中遇到任何挑战,欢迎在评论区评论或通过邮件与我探讨。
参考文章
Pytest – 教程 – 菜鸟教程
pytest 核心功能-调用pytest_w3cschool
完整的Pytest文档 — pytest documentation
Selenium WebDriver — Web自动化测试标准工具 – Ethan的博客