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,断言还得记一堆方法名。我当年写一个登录模块的测试,光模板代码就占了三分之一。

注意:unittest 的断言方法有二十多种,比如 assertEqual、assertTrue、assertIn、assertIsNone…… 记不住?那就等着翻文档吧。

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 之间互相调用。

个人经验:我曾在项目中用 pytest 的 fixture 机制,把 2000 多行的测试基类压缩到了 300 行。说白了,fixture 就是 pytest 的灵魂。

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 如何选择适合项目的框架?

这个问题没有标准答案,但我可以给你一些「实战经验」:

我的建议:新项目无脑选 pytest。除非你遇到下面这些特殊情况。

什么时候选 unittest?

  • 项目环境受限,不能安装第三方包(比如某些银行内网环境)
  • 团队全是 Java 背景,对 JUnit 风格更熟悉
  • 维护一个已经用 unittest 写了上万用例的老项目

什么时候选 pytest?

  • 新项目,从零开始搭建
  • 需要做数据驱动测试(参数化)
  • 需要生成漂亮的测试报告(Allure 报告是真的香)
  • 用例数量多,需要并发执行
  • 需要做接口测试、UI 测试、单元测试混搭

我曾经接手过一个项目,团队用 unittest 写了 5000 多个用例。每个用例文件开头都是大段的 setUp 代码,维护起来简直要命。后来我们花了两个月迁移到 pytest,用 fixture 重构了公共逻辑,代码量减少了 40%,执行时间从 2 小时降到了 20 分钟(用了 xdist 并发)。

避坑指南:我曾经犯过一个错误——在 pytest 里混用 unittest 的断言方法。比如用 assertEqual 而不是 assert。结果报错信息极其不友好,排查了半天才发现是断言方式不统一。记住:选了 pytest,就彻底拥抱 assert

2.5 我的最终建议

如果你问我个人习惯,我现在所有项目都用 pytest。原因很简单:

  • 写起来快,读起来爽
  • 插件生态太丰富了,想要什么功能都有
  • 社区活跃,遇到问题 StackOverflow 一搜就有答案

但话说回来,工具只是工具。真正重要的是你对测试的理解——用例怎么设计、覆盖率怎么保证、持续集成怎么落地。这些才是咱们这门课要讲的核心。

下一章,咱们就聊聊怎么用 pytest 搭建一个真正能用的自动化测试框架。到时候我会把我在项目中踩过的坑、总结的经验,一股脑全倒出来。