news 2026/10/12 11:21:28

Python自动化测试框架:unittest、pytest、Selenium、requests和Pytest-BDD

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python自动化测试框架:unittest、pytest、Selenium、requests和Pytest-BDD

前言


这个标题里有一个需要先纠正的说法:requests不是测试框架。requests是一个 HTTP 客户端库,它只负责发请求、收响应,跟「组织测试用例、收集测试结果、生成报告」这些测试框架的职责完全无关。它出现在自动化测试话题里,是因为写接口测试时常常用它来发请求——它是被测试代码调用的工具,不是承载测试的框架。


同理,Selenium严格说也不是「测试框架」,而是浏览器自动化驱动库:它把「打开浏览器、点按钮、读文本」这些动作暴露成 Python 方法,至于怎么组织用例、怎么断言,还是交给unittest或pytest。


把前提理清之后,标题里这五个名字其实分属四类,各司其职:




类别名字定位来源



测试框架unittest内核自带,类式用例,自带断言方法标准库

测试框架pytest第三方,函数式用例,裸assert需安装

浏览器驱动Selenium驱动真实浏览器做端到端操作需安装

BDD 工具pytest-bdd把 Gherkin 的场景文本映射到 pytest 用例需安装

HTTP 辅助requests在测试里发 HTTP 请求需安装



本文按这个分类逐个讲它们的定位与分工,并说明它们通常怎么组合。注意requests只作为「测试里的辅助工具」出现,本文不把它当框架讲。版本提示:按 PyPI 元数据,最新的pytest、selenium、pytest-bdd都声明requires_python为>=3.10;unittest属于标准库,随解释器走。Python 2.7 已于2020 年 1 月 1 日停止维护,下面一律用 Python 3 写法(例如断言用assertEqual而不是 2 时代的种种别名习惯,print用函数形式)。


一、unittest:标准库自带的测试框架


unittest直接import就能用,零依赖。它的组织方式是类 + 方法:测试类继承unittest.TestCase,方法名以test开头才会被收集。


# 适用于 Python 3.8+
import unittest


def add(a, b):
return a + b


class TestAdd(unittest.TestCase):
def setUp(self):
# 每个 test_ 方法执行前都会跑一次
self.samples = [1, 2, 3]

def tearDown(self):
# 每个 test_ 方法执行后都会跑一次,用来清理
self.samples = None

def test_add_positive(self):
self.assertEqual(add(1, 2), 3)

def test_add_negative(self):
self.assertEqual(add(-1, -2), -3)

def test_sum_of_samples(self):
self.assertEqual(sum(self.samples), 6)


if __name__ == "__main__":
unittest.main()

要点:



  • 断言是方法:assertEqual、assertTrue、assertIn、assertRaises等,名字写在TestCase上。写错名字不会报「断言失败」,而是直接AttributeError。

  • setUp/tearDown是每条用例前后各跑一次;如果希望整个类只准备一次,用setUpClass/tearDownClass(它们是类方法)。

  • 保存为test_*.py后,可以用python -m unittest discover自动发现并运行。


二、pytest:更轻的第三方测试框架


pytest需要pip install pytest。它最大的特点是不需要继承任何基类,普通函数就是用例,断言直接用 Python 的assert——失败时 pytest 会做「断言内省」,把表达式两边的实际值都打出来,所以不必去记几十个assertXxx方法名。


# 适用于 Python 3.10+(需要 pip install pytest)
import pytest


def add(a, b):
return a + b


def test_add_positive():
assert add(1, 2) == 3


@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(-1, -2, -3),
(0, 0, 0),
])
def test_add_many(a, b, expected):
assert add(a, b) == expected


@pytest.fixture
def samples():
"""fixture:把测试需要的数据/资源准备好,由参数名注入。"""
return [1, 2, 3]


def test_sum_of_samples(samples):
assert sum(samples) == 6

要点:



  • @pytest.mark.parametrize把一组数据展开成多条独立用例,失败时能精确指出是哪组数据挂了。

  • fixture用@pytest.fixture声明,用例只要在参数列表里写上同名参数就能拿到,不需要手工调用、不需要继承。fixture 还能声明作用域(函数级、类级、模块级等)来做复用与清理。

  • 运行方式是命令行pytest或python -m pytest;后者能保证用的是当前解释器对应的 pytest。


两者的分工可以这样理解:新项目优先pytest,不需要额外依赖或要交付到受限环境时用unittest。它们不是互斥的——pytest能直接收集并运行unittest.TestCase写成的用例。


三、Selenium:驱动浏览器的工具库


Selenium解决的问题是「用户界面行为」:点按钮、填表单、等待元素出现、读取渲染后的文本。这些是纯 HTTP 请求库做不到的,因为浏览器要执行 JavaScript 才能把页面渲染出来。


# 适用于 Python 3.10+(需要 pip install selenium,且本机装有 Chrome)
from selenium import webdriver
from selenium.webdriver.common.by import By


def test_page_title():
driver = webdriver.Chrome() # 启动一个浏览器会话
try:
driver.get("https://example.com/")
heading = driver.find_element(By.TAG_NAME, "h1")
assert heading.text
finally:
driver.quit() # 无论如何都要退出,否则留下僵尸进程

关键在于谁在启动时帮你把驱动准备好。Selenium 需要 chromedriver、geckodriver 这类浏览器驱动;从 Selenium 4.6 起,官方提供了Selenium Manager——官方文档称它是「Selenium 官方的驱动管理器」,随每个 Selenium 版本一起发布,Selenium的各个语言绑定默认会调用它,因此不需要你手工下载驱动或改代码。它在驱动不可用时才介入,会自动发现本机浏览器版本、下载匹配的驱动并缓存到本地缓存目录。


再强调一次:Selenium是驱动库,不是框架。真正的用例组织仍然由pytest或unittest完成——上面那个函数能被 pytest 直接收集运行,就是因为它写成了 pytest 认得的普通函数。


四、pytest-bdd:把场景文本映射到 pytest


BDD(行为驱动开发,Behavior-Driven Development)想让「业务人员也能读的用例描述」直接变成可执行的测试。pytest-bdd把 Gherkin 语法(Given/When/Then)的场景文件和方法绑定起来,而运行器仍然是 pytest。


先写一个.feature文件:


Feature: 购物车
Scenario: 加入一件商品
Given 购物车是空的
When 我加入一件价格为 10 的商品
Then 购物车总额应该是 10

再用装饰器把它接上:


# 适用于 Python 3.10+(需要 pip install pytest-bdd)
from pytest_bdd import scenario, given, when, then


@scenario("cart.feature", "加入一件商品")
def test_add_one_item():
"""这个空函数是一个占位用例,steps 会在它之前执行。"""


@given("购物车是空的", target_fixture="cart")
def empty_cart():
return []


@when("我加入一件价格为 10 的商品")
def add_item(cart):
cart.append(10)


@then("购物车总额应该是 10")
def check_total(cart):
assert sum(cart) == 10

要点:



  • @scenario("文件.feature", "场景名")把某个场景绑定到一个测试函数;这个函数体本身通常是空的,Given/When/Then步骤会在它之前按顺序执行。

  • 步骤函数可以用target_fixture把返回值变成下游步骤共享的 fixture(例子里的cart就是这样在三个步骤之间传递的)。

  • 步骤函数本身也是普通函数,可以用assert、可以抛异常,失败了 pytest 照常报告。


pytest-bdd的价值在于用例描述与实现分离:改业务描述不用动断言代码,改实现也不影响场景文本的可读性。代价是多了一层Given/When/Then的映射,小项目里可能显得啰嗦。


五、requests:它在测试里到底是什么角色


回到开头那个纠正:requests是 HTTP 客户端库,它的工作是构造请求、发送、解析响应。在自动化测试体系里,它扮演的是辅助工具——测试「接口」时,用它发请求,再用assert断言响应。


# 适用于 Python 3.10+(需要 pip install requests pytest)
import pytest
import requests


def test_public_api_shape():
"""对一个公开的只读接口做最基本的契约检查。"""
r = requests.get("https://example.com/api/status", timeout=10)
assert r.status_code == 200
assert r.headers["Content-Type"].startswith("application/json")

payload = r.json()
assert isinstance(payload, dict)
assert "version" in payload

把它放进这个分类表里,位置就清楚了:




名字是不是「测试框架」真正角色



unittest是标准库测试框架

pytest是第三方测试框架,也是前者的替代/超集

Selenium否浏览器自动化驱动库

pytest-bdd否(是插件)挂在 pytest 上的 BDD 插件

requests否HTTP 客户端库,测试里的辅助工具



它们常见的组合方式是:用pytest组织用例和 fixture,用requests测接口,用Selenium测界面,需要业务可读场景时加pytest-bdd。unittest则在不想引入第三方依赖时顶替pytest的位置。


顺便说一句合规与伦理:用requests或Selenium做测试时,目标应该是你自己负责的系统,或明确提供测试环境的公开接口。拿它们去批量打扰别人的站点,无论包装成「压测」还是「爬虫」,都不合适——限速、可识别 UA、遵守 robots.txt 这些规则在测试场景里同样适用。


常见坑点


1. 把requests当测试框架。


❌ 在介绍「自动化测试框架」时把requests和pytest并列,以为它负责收集和运行用例。 ✅requests只发 HTTP 请求;用例收集、断言报告由pytest/unittest负责。


2.unittest里把断言写成assert x == y却奇怪地「全过」。


❌ 在TestCase子类里用普通函数名(如check_foo)命名用例 —— 不会被收集,自然也不失败。 ✅ 方法名必须以test开头,且断言优先用self.assertEqual等方法,报告信息更全。


3.setUp当成「整个类只跑一次」。


❌ 把昂贵的初始化写在setUp里,以为只执行一次 —— 它每条用例前都跑。 ✅ 一次性准备用setUpClass(类方法);每条用例前的准备才用setUp。


4. Selenium 忘了driver.quit()。


❌ 测试抛异常后直接退出,浏览器进程残留,跑几十条用例后机器被拖垮。 ✅ 用try / finally保证driver.quit()一定执行;用 pytest 时可以写成 fixture 的清理段。


5. 以为 Selenium 要手工下载驱动。


❌ 到处找「chromedriver 与 Chrome 版本对应表」然后手动塞进 PATH。 ✅ Selenium 4.6 起自带的 Selenium Manager 会默认处理驱动的发现、下载与缓存;只有在它解决不了时才需要手工指定。


6.pytest-bdd的步骤文本与 feature 文件对不上。


❌ feature 里写When 我加入一件价格为 10 的商品,@when装饰器里写的是另一个字面量 —— 场景找不到步骤,报StepDefinitionNotFoundError。 ✅ 装饰器里的字符串必须与 feature 文件逐字一致;重复的步骤说明可以抽成公共步骤。


7. 测试直接打真实生产环境。


❌requests.get()和Selenium都指向线上地址,测试一跑就给线上刷流量。 ✅ 指向测试环境或本地服务;确需访问公开接口时也要限速、只做只读检查。


8. 认为Selenium就是「测试框架」,于是也拿它去跑接口测试。


❌ 用Selenium去调接口 —— 启动整个浏览器只为发一个 HTTP 请求,慢且脆弱。 ✅ 接口层面用requests,只有需要真实渲染和交互时才用Selenium。


总结




名字类别一句话职责安装方式



unittest测试框架类式用例、自带断言标准库,免装

pytest测试框架函数式用例、裸assert、fixture、参数化pip install pytest

Selenium浏览器驱动操作真实浏览器,做端到端测试pip install selenium

pytest-bddBDD 插件把 Gherkin 场景映射成 pytest 用例pip install pytest-bdd

requestsHTTP 客户端在测试里发请求,属于工具而非框架pip install requests



回到标题:把这五个名字并称「自动化测试框架」是一种笼统的说法。准确的理解是——两个框架(unittest、pytest)负责「怎么组织与运行测试」,一个插件(pytest-bdd)负责「怎么用业务语言描述测试」,两个库(Selenium、requests)负责「怎么与被测系统交互」。分清这四层,选型时就不会再拿浏览器驱动去做接口测试,也不会指望 HTTP 客户端帮你生成测试报告。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 11:15:57

SpringBoot邮件集成:从配置到高可用的全链路实践

1. 项目概述:为什么SpringBoot整合Mail不是“配个配置就完事”的小事“SpringBoot整合mail邮箱”这八个字,看起来平平无奇,像极了教程里一句轻描淡写的“三步接入邮件功能”。但我在某高校实验室带学生做毕业设计、在某公司支撑内部运维平台时…

作者头像 李华
网站建设 2026/10/12 11:13:56

AnyPS5:面向PC的PS5游戏兼容性技术解析

我无法基于当前输入生成符合要求的博文。 原因如下: 输入中仅提供了项目标题 "AnyPS5" ,但未提供任何实质性的【项目正文】、【关键词】或【摘要描述】; 所谓“相关热搜词”和“最新网络热词”字段为空,未给出具体…

作者头像 李华
网站建设 2026/10/12 11:13:40

日本优卡服装CAD全流程拆解:从DXF导入到排料输出的实用指南

简介:日本优卡服装CAD免狗版是一款源自日本的专业服装设计与制版软件,面向服装设计师、制版师及服装专业学习者,可在无需硬件加密狗的情况下直接出图、读图并编辑版型,显著降低使用门槛和硬件成本。资源包共含1656个文件&#xff…

作者头像 李华
网站建设 2026/10/12 11:12:41

孤岛式直流微电网分层控制复现:IEEE16节点建模与仿真实践

1. 系统框架与问题域拆解1.1 标题关键词背后的完整技术画像拿到“具有灵活结构的孤岛式直流微电网中的分层控制(IEEE16节点)”这个标题,很多人的第一反应是:这不就是直流微电网的下垂控制加个二级补偿嘛。但如果真按这个理解去复现…

作者头像 李华
网站建设 2026/10/12 11:11:58

校园网课程设计实战:从子网划分到VLAN规划的完整指南

简介:这是一套以真实校园为背景的计算机网络课程设计完整方案文档,主要面向高校网络工程、计算机等相关专业学生,适合在完成“校园网规划设计”类实践作业或课程设计时作为参考与模板。方案以淮海工学院东港学院为对象,将校园划分…

作者头像 李华