
前言这个标题里有一个需要先纠正的说法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.10unittest属于标准库随解释器走。Python 2.7 已于2020 年 1 月 1 日停止维护下面一律用 Python 3 写法例如断言用assertEqual而不是 2 时代的种种别名习惯print用函数形式。一、unittest标准库自带的测试框架unittest直接import就能用零依赖。它的组织方式是类 方法测试类继承unittest.TestCase方法名以test开头才会被收集。# 适用于 Python 3.8import unittestdef add(a, b):return a bclass TestAdd(unittest.TestCase):def setUp(self):# 每个 test_ 方法执行前都会跑一次self.samples [1, 2, 3]def tearDown(self):# 每个 test_ 方法执行后都会跑一次用来清理self.samples Nonedef 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 pytestimport pytestdef add(a, b):return a bdef test_add_positive():assert add(1, 2) 3pytest.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) expectedpytest.fixturedef 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且本机装有 Chromefrom selenium import webdriverfrom selenium.webdriver.common.by import Bydef test_page_title():driver webdriver.Chrome() # 启动一个浏览器会话try:driver.get(https://example.com/)heading driver.find_element(By.TAG_NAME, h1)assert heading.textfinally:driver.quit() # 无论如何都要退出否则留下僵尸进程关键在于谁在启动时帮你把驱动准备好。Selenium 需要 chromedriver、geckodriver 这类浏览器驱动从 Selenium 4.6 起官方提供了Selenium Manager——官方文档称它是「Selenium 官方的驱动管理器」随每个 Selenium 版本一起发布Selenium的各个语言绑定默认会调用它因此不需要你手工下载驱动或改代码。它在驱动不可用时才介入会自动发现本机浏览器版本、下载匹配的驱动并缓存到本地缓存目录。再强调一次Selenium是驱动库不是框架。真正的用例组织仍然由pytest或unittest完成——上面那个函数能被 pytest 直接收集运行就是因为它写成了 pytest 认得的普通函数。四、pytest-bdd把场景文本映射到 pytestBDD行为驱动开发Behavior-Driven Development想让「业务人员也能读的用例描述」直接变成可执行的测试。pytest-bdd把 Gherkin 语法Given/When/Then的场景文件和方法绑定起来而运行器仍然是 pytest。先写一个.feature文件Feature: 购物车Scenario: 加入一件商品Given 购物车是空的When 我加入一件价格为 10 的商品Then 购物车总额应该是 10再用装饰器把它接上# 适用于 Python 3.10需要 pip install pytest-bddfrom pytest_bdd import scenario, given, when, thenscenario(cart.feature, 加入一件商品)def test_add_one_item():这个空函数是一个占位用例steps 会在它之前执行。given(购物车是空的, target_fixturecart)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 pytestimport pytestimport requestsdef test_public_api_shape():对一个公开的只读接口做最基本的契约检查。r requests.get(https://example.com/api/status, timeout10)assert r.status_code 200assert 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 pytestSelenium浏览器驱动操作真实浏览器做端到端测试pip install seleniumpytest-bddBDD 插件把 Gherkin 场景映射成 pytest 用例pip install pytest-bddrequestsHTTP 客户端在测试里发请求属于工具而非框架pip install requests回到标题把这五个名字并称「自动化测试框架」是一种笼统的说法。准确的理解是——两个框架unittest、pytest负责「怎么组织与运行测试」一个插件pytest-bdd负责「怎么用业务语言描述测试」两个库Selenium、requests负责「怎么与被测系统交互」。分清这四层选型时就不会再拿浏览器驱动去做接口测试也不会指望 HTTP 客户端帮你生成测试报告。