2、测试框架选型:Python单元测试框架对比(unittest vs pytest)
说实话,每次带新人做自动化测试项目,第一个被问的问题总是:「到底该用 unittest 还是 pytest?」
这个问题我回答过不下几十次了。今天咱们就掰开揉碎,把这两个框架的底裤都扒干净。
2.1 先聊聊 unittest —— 老牌正统,但有点「端着」
unittest 是 Python 标准库自带的测试框架。说白了,它就是 Java 里 JUnit 的 Python 翻版。我最早做自动化测试时用的就是它,那时候 pytest 还没这么火。
它的核心特点就四个字:规规矩矩。
- 必须继承
unittest.TestCase类 - 测试方法必须以
test_开头 - 用
assertEqual()、assertTrue()这类方法做断言 - setUp / tearDown 做前后置处理
给你看个例子,感受一下:
import unittest
class TestLogin(unittest.TestCase):
def setUp(self):
# 每个用例执行前都会跑
self.driver = WebDriver()
self.driver.get("https://example.com/login")
def tearDown(self):
self.driver.quit()
def test_login_success(self):
self.driver.find_element(By.ID, "username").send_keys("admin")
self.driver.find_element(By.ID, "password").send_keys("123456")
self.driver.find_element(By.ID, "loginBtn").click()
self.assertEqual(self.driver.current_url, "https://example.com/home")
def test_login_failed(self):
self.driver.find_element(By.ID, "username").send_keys("wrong")
self.driver.find_element(By.ID, "password").send_keys("wrong")
self.driver.find_element(By.ID, "loginBtn").click()
self.assertTrue(self.driver.find_element(By.CLASS_NAME, "error-msg").is_displayed())
嗯,看着还行对吧?但用久了你会发现一个问题——代码量太大了。每个测试类都要写 setUp、tearDown,断言还得记一堆方法名。我当年写一个登录模块的测试,光模板代码就占了三分之一。
2.2 再来看 pytest —— 简洁到「令人发指」
我第一次接触 pytest 时,心里就一个想法:「这才叫测试框架该有的样子!」
pytest 不需要继承任何类,不需要写 setUp/tearDown,断言直接用 Python 原生的 assert 关键字。你想想看,这得省多少事?
同样的登录测试,用 pytest 写出来是这样的:
import pytest
@pytest.fixture
def driver():
driver = WebDriver()
driver.get("https://example.com/login")
yield driver
driver.quit()
def test_login_success(driver):
driver.find_element(By.ID, "username").send_keys("admin")
driver.find_element(By.ID, "password").send_keys("123456")
driver.find_element(By.ID, "loginBtn").click()
assert driver.current_url == "https://example.com/home"
def test_login_failed(driver):
driver.find_element(By.ID, "username").send_keys("wrong")
driver.find_element(By.ID, "password").send_keys("wrong")
driver.find_element(By.ID, "loginBtn").click()
assert driver.find_element(By.CLASS_NAME, "error-msg").is_displayed()
看到了吗?代码量直接砍半。而且 @pytest.fixture 比 setUp/tearDown 灵活太多了——你可以控制它的作用域(function、class、module、session),还能在 fixture 之间互相调用。
2.3 核心特性对比:一张表说清楚
咱们直接上干货,我整理了一张对比表:
| 特性 | unittest | pytest |
|---|---|---|
| 安装方式 | 内置,无需安装 | 需 pip install pytest |
| 测试发现 | 需手动加载 TestSuite | 自动递归发现 test_*.py |
| 断言方式 | assertEqual / assertTrue 等 | 原生 assert |
| 前后置处理 | setUp / tearDown | @pytest.fixture |
| 参数化 | 需用 subTest 或第三方库 | @pytest.mark.parametrize |
| 插件生态 | 有限 | 极其丰富(1000+ 插件) |
| 报告生成 | 需额外配置 | pytest-html / allure 即插即用 |
| 并发执行 | 不支持 | pytest-xdist 轻松搞定 |
| 失败重跑 | 不支持 | pytest-rerunfailures |
为什么会这样?说白了,unittest 是 2000 年代初的设计思路,那时候测试还没这么复杂。而 pytest 诞生在 2010 年后,吸收了 unittest 的教训,加上 Python 社区的各种最佳实践,自然更「现代」。
2.4 如何选择适合项目的框架?
这个问题没有标准答案,但我可以给你一些「实战经验」:
什么时候选 unittest?
- 项目环境受限,不能安装第三方包(比如某些银行内网环境)
- 团队全是 Java 背景,对 JUnit 风格更熟悉
- 维护一个已经用 unittest 写了上万用例的老项目
什么时候选 pytest?
- 新项目,从零开始搭建
- 需要做数据驱动测试(参数化)
- 需要生成漂亮的测试报告(Allure 报告是真的香)
- 用例数量多,需要并发执行
- 需要做接口测试、UI 测试、单元测试混搭
我曾经接手过一个项目,团队用 unittest 写了 5000 多个用例。每个用例文件开头都是大段的 setUp 代码,维护起来简直要命。后来我们花了两个月迁移到 pytest,用 fixture 重构了公共逻辑,代码量减少了 40%,执行时间从 2 小时降到了 20 分钟(用了 xdist 并发)。
2.5 我的最终建议
如果你问我个人习惯,我现在所有项目都用 pytest。原因很简单:
- 写起来快,读起来爽
- 插件生态太丰富了,想要什么功能都有
- 社区活跃,遇到问题 StackOverflow 一搜就有答案
但话说回来,工具只是工具。真正重要的是你对测试的理解——用例怎么设计、覆盖率怎么保证、持续集成怎么落地。这些才是咱们这门课要讲的核心。
下一章,咱们就聊聊怎么用 pytest 搭建一个真正能用的自动化测试框架。到时候我会把我在项目中踩过的坑、总结的经验,一股脑全倒出来。