1. 项目概述:为什么是unittest?
如果你刚接触Python,或者从手动测试转向自动化,面对一堆测试框架(pytest, nose, unittest)可能会有点懵。我刚开始那会儿也一样,总觉得要学就学最“流行”的。但干了十几年,带过不少新人项目后,我发现一个扎心的事实:对于绝大多数从零开始的团队和个人, unittest 往往是那个最稳妥、最能让你“先跑起来”的起点。它可能不是功能最炫的,但绝对是地基最稳的。
为什么这么说?首先,unittest是Python标准库的一部分,这意味着你不需要
pip install
任何额外的东西,只要装了Python就能用。这对于环境管控严格的公司或者快速验证想法来说,是巨大的优势。其次,它的设计理念源自Java的JUnit,非常经典,结构清晰(TestCase, TestSuite, TestRunner, TestFixture),你学的不只是一套工具,更是一套单元测试的
方法论
。把这套方法论吃透了,以后切换到pytest等更灵活的框架,你会觉得轻而易举,因为核心的“断言”、“用例组织”、“前置后置”概念都是相通的。
这个系列的目标,就是帮你绕开我当年踩过的坑,用最直白的方式,把unittest从“Hello World”讲到能在实际项目中搭建起一个可维护的自动化测试框架。我们不玩虚的,所有代码你都能直接抄走,用到自己的项目里。收藏这一篇,足够你从零基础到能独立负责一个模块的自动化测试任务。
2. unittest核心四要素深度拆解
理解unittest,关键在于吃透它的四个核心概念。很多人看了官方文档还是云里雾里,就是因为没把这些概念和实际代码对应起来。咱们一个一个掰开揉碎了说。
2.1 TestCase:你的每一个测试剧本
你可以把
unittest.TestCase
类理解成一个
测试剧本的模板
。每一个测试方法(以
test_
开头的方法)就是这个剧本里的一个独立场景。
import unittest
class TestMathOperations(unittest.TestCase):
# 这是一个测试场景:测试加法
def test_addition(self):
result = 1 + 2
self.assertEqual(result, 3) # 断言:结果应该等于3
# 另一个测试场景:测试除法,包括异常情况
def test_division_by_zero(self):
with self.assertRaises(ZeroDivisionError):
_ = 1 / 0
关键点解析:
-
命名必须规范
:测试方法名
必须
以
test_开头。这是TestRunner识别它的唯一标志。你写成check_addition()是没用的。 -
self.assertXxx是灵魂 :这是你验证结果的地方。unittest提供了一整套断言方法,这是你最主要的工具。上面用的assertEqual(a, b)和assertRaises(异常)是最常用的两个。
实操心得 :初期不要怕断言用得多。一个测试方法里包含多个断言是常见的,用于验证一个函数的多个侧面。比如测试一个用户注册函数,你可能需要断言返回值、断言数据库记录、断言发送的消息等。
2.2 TestFixture:场景的搭建与拆除
拍戏要有布景和清场,测试也一样。
TestFixture
指的就是测试前后的准备和清理工作,对应
setUp()
和
tearDown()
方法。
import unittest
import tempfile
import os
class TestFileOperations(unittest.TestCase):
def setUp(self):
"""每个测试方法执行前都会运行。"""
# 创建一个临时文件,并写入测试数据
self.test_file = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
self.test_file.write('Initial content\n')
self.test_file.flush() # 确保内容写入磁盘
self.file_path = self.test_file.name
print(f"SetUp: 创建了临时文件 {self.file_path}")
def tearDown(self):
"""每个测试方法执行后都会运行。"""
# 关闭并删除临时文件
self.test_file.close()
os.unlink(self.file_path)
print(f"TearDown: 删除了临时文件 {self.file_path}")
def test_file_read(self):
with open(self.file_path, 'r') as f:
content = f.read()
self.assertIn('Initial', content)
def test_file_write(self):
with open(self.file_path, 'a') as f:
f.write('Appended content\n')
# 验证文件大小变化等...
为什么这很重要? 它保证了测试的 独立性 。每个测试方法都从一个干净、已知的状态开始,不会受到前一个测试残留数据的影响。这是编写可靠自动化测试的黄金法则。
2.3 TestSuite:测试剧组的调度
单个TestCase好比一集电视剧,TestSuite就是整个季度的播放列表。你可以自由组织测试执行的顺序和范围。
import unittest
# 假设这是你写好的其他测试类
from test_math import TestMathOperations
from test_string import TestStringMethods
def create_suite():
# 创建一个测试套件
suite = unittest.TestSuite()
# 方法1:添加单个测试方法
suite.addTest(TestMathOperations('test_addition'))
# 方法2:添加一个测试类的所有方法
suite.addTest(unittest.makeSuite(TestStringMethods))
# 方法3:通过加载器从模块添加所有测试
loader = unittest.TestLoader()
suite.addTests(loader.loadTestsFromModule(test_math))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2) # verbosity=2 输出详细信息
runner.run(create_suite())
应用场景 :
- 冒烟测试 :创建一个只包含核心功能测试的Suite,每次提交代码后快速运行。
- 按模块测试 :为不同的模块(如API测试、数据库测试、UI测试)创建不同的Suite,方便针对性执行。
- 集成测试 :按特定业务流程,将不同模块的测试用例组合成一个Suite来执行。
2.4 TestRunner:测试的执行导演
TestRunner负责执行TestSuite并输出结果。最常用的是
unittest.TextTestRunner
,它会将结果输出到控制台。但它的能力不止于此,你可以通过继承它来生成HTML报告、发送邮件通知、与CI/CD工具(如Jenkins)集成等。
import unittest
import xmlrunner
import sys
class TestExample(unittest.TestCase):
def test_pass(self):
self.assertTrue(True)
def test_fail(self):
self.assertTrue(False) # 这个会失败
if __name__ == '__main__':
# 基础运行器
# unittest.main()
# 使用更强大的第三方运行器,如生成JUnit XML格式报告(CI工具最爱)
with open('test-results.xml', 'wb') as output:
runner = xmlrunner.XMLTestRunner(output=output, verbosity=2)
suite = unittest.TestLoader().loadTestsFromTestCase(TestExample)
result = runner.run(suite)
# 根据测试结果控制流程(例如:测试失败则阻止后续部署)
sys.exit(0 if result.wasSuccessful() else 1)
选择建议
:项目初期用
unittest.main()
或
python -m unittest discover
就够。当需要集成到CI/CD流水线时,务必使用能生成标准格式(如JUnit XML)报告的Runner,这样Jenkins、GitLab CI等工具才能解析测试结果并展示趋势图。
3. 从零搭建一个可维护的测试项目结构
学了招式,还得有练功房。混乱的测试代码结构是项目后期的噩梦。下面是我在多个项目中总结出的、最适合新手入门且易于扩展的目录结构。
your_project/
├── src/ # 你的主要源代码
│ └── calculator.py # 示例:一个计算器模块
├── tests/ # 测试代码根目录
│ ├── __init__.py # 让Python将tests视为一个包
│ ├── unit/ # 单元测试
│ │ ├── __init__.py
│ │ ├── test_calculator.py # 对应src/calculator.py的单元测试
│ │ └── test_utils.py
│ ├── integration/ # 集成测试
│ │ ├── __init__.py
│ │ └── test_api_integration.py
│ ├── data/ # 测试数据(JSON, CSV, SQL文件等)
│ │ └── test_users.json
│ ├── conftest.py # (可选) 未来兼容pytest的全局配置
│ └── reports/ # 测试报告输出目录(.gitignore忽略)
│ └── .gitkeep
├── requirements.txt # 项目依赖
├── requirements-test.txt # 测试环境额外依赖(如html-testRunner)
└── run_tests.py # 统一的测试入口脚本
关键文件解析:
-
run_tests.py(测试入口脚本) : 这是项目的总开关。避免让团队成员记忆复杂的discover命令。#!/usr/bin/env python3 # run_tests.py import sys import unittest from xmlrunner import XMLTestRunner def run_all_tests(): # 自动发现tests目录下所有以`test_`开头的文件 test_suite = unittest.defaultTestLoader.discover(start_dir='./tests', pattern='test_*.py') # 配置运行器 runner = XMLTestRunner( output='./tests/reports', verbosity=2, descriptions=True, failfast=False, # 设为True则一个用例失败就停止 ) # 执行测试 result = runner.run(test_suite) # 返回退出码,非0表示失败(用于CI/CD) return 0 if result.wasSuccessful() else 1 if __name__ == '__main__': sys.exit(run_all_tests())以后只需要执行
python run_tests.py。 -
tests/__init__.py: 一个空文件即可,它的存在标志着tests是一个Python包,这样在测试用例中才能正确地从上级目录(src)导入被测试模块。这是新手常遇到的ImportError的根源。 -
tests/data/目录 : 测试数据与代码分离是基本原则。不要把测试用的JSON字符串、用户密码硬编码在测试用例里。放在单独的文件中,用例里读取它。# test_user.py import json import os import unittest class TestUserAPI(unittest.TestCase): def setUp(self): data_path = os.path.join(os.path.dirname(__file__), 'data', 'test_users.json') with open(data_path, 'r') as f: self.test_users = json.load(f) def test_valid_user_login(self): user = self.test_users['valid_user'] # 调用你的登录函数,传入user['username'], user['password'] # ... 进行断言
4. 断言的艺术:不仅仅是assertEqual
断言是你的“检查官”。unittest提供了丰富的断言方法,用对方法能让测试报告更清晰。
4.1 基础断言方法速查表
| 断言方法 | 检查条件 | 实用场景示例 |
|---|---|---|
assertEqual(a, b)
|
a == b
| 验证函数返回值是否等于期望值 |
assertNotEqual(a, b)
|
a != b
| 验证两个对象不相等 |
assertTrue(x)
|
bool(x) is True
| 验证条件为真 |
assertFalse(x)
|
bool(x) is False
| 验证条件为假 |
assertIs(a, b)
|
a is b
| 验证是同一个对象(内存地址) |
assertIsNot(a, b)
|
a is not b
| 验证不是同一个对象 |
assertIsNone(x)
|
x is None
| 验证变量为None |
assertIsNotNone(x)
|
x is not None
| 验证变量不为None |
assertIn(a, b)
|
a in b
| 验证a在容器b中 |
assertNotIn(a, b)
|
a not in b
| 验证a不在容器b中 |
assertIsInstance(a, b)
|
isinstance(a, b)
| 验证a是b类型(或子类) |
assertNotIsInstance(a, b)
|
not isinstance(a, b)
| 验证a不是b类型 |
assertRaises(Exc, func, *args, **kw)
|
func(*args, **kw)
抛出Exc异常
| 验证函数在特定输入下会抛出预期异常 |
assertAlmostEqual(a, b, places=7)
|
round(a-b, places) == 0
| 验证浮点数近似相等,避免精度问题 |
assertGreater(a, b)
|
a > b
| 验证a大于b |
assertLess(a, b)
|
a < b
| 验证a小于b |
4.2 断言的最佳实践与常见坑
1. 使用最精确的断言
:
不要什么都用
assertTrue
。
assertEqual
失败时会打印出
a
和
b
的实际值,而
assertTrue(a == b)
失败只告诉你
False is not True
,毫无信息量。
2. 处理浮点数比较
:
永远不要
用
assertEqual
比较浮点数!因为计算机的浮点精度问题。
# 错误示范
self.assertEqual(0.1 + 0.2, 0.3) # 很可能失败!因为 0.1+0.2 = 0.30000000000000004
# 正确示范
self.assertAlmostEqual(0.1 + 0.2, 0.3) # 默认比较小数点后7位
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=2) # 比较小数点后2位
3. 异常断言的正确姿势
:
assertRaises
有两种用法,推荐使用上下文管理器(第二种),更清晰。
import unittest
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
class TestDivide(unittest.TestCase):
# 方法1:传统参数式(容易写错,不直观)
def test_divide_by_zero_old(self):
self.assertRaises(ValueError, divide, 10, 0)
# 方法2:上下文管理器式(推荐!清晰直观)
def test_divide_by_zero_new(self):
with self.assertRaises(ValueError) as cm:
divide(10, 0)
# 你还可以进一步断言异常信息
self.assertEqual(str(cm.exception), "除数不能为零")
# 方法3:断言异常类型,但不关心具体信息
def test_divide_by_zero_simple(self):
with self.assertRaises(ValueError):
divide(10, 0)
5. 高级技巧与实战模式
掌握了基础,我们来看看如何用unittest应对更复杂的真实场景。
5.1 跳过测试与条件执行
不是所有测试在任何时候都需要运行。比如,某些测试依赖外部服务(如数据库、第三方API),当这些服务不可用时,我们希望跳过而非失败。
import unittest
import sys
class TestAdvancedFeatures(unittest.TestCase):
# 无条件跳过这个测试用例,并说明原因
@unittest.skip("这个功能在v2.0中才会实现,暂时跳过")
def test_future_feature(self):
self.fail("这个测试不应该被执行")
# 根据条件跳过(例如:操作系统)
@unittest.skipIf(sys.platform != "win32", "仅需在Windows环境下运行")
def test_windows_specific_feature(self):
# 测试Windows特有的路径处理等
pass
# 根据条件跳过,除非条件为真
@unittest.skipUnless(hasattr(os, 'symlink'), "需要操作系统支持符号链接")
def test_symlink_function(self):
# 测试符号链接相关功能
pass
# 预期失败:你知道这个测试目前会失败,但不想它影响整体通过率
@unittest.expectedFailure
def test_buggy_feature(self):
# 这里有一个已知的Bug,我们标记它为预期失败
self.assertEqual(1, 2) # 这行会失败,但测试结果标记为“预期失败”
def test_normal_feature(self):
self.assertEqual(1, 1)
运行测试时,跳过的测试会显示
S
,预期失败的测试如果真失败了显示
x
,如果意外通过了则显示
u
(unexpected success)。
5.2 使用子测试进行参数化
这是unittest一个非常强大但常被忽略的功能。当你需要用多组不同输入数据测试同一个逻辑时,与其写多个几乎重复的测试方法,不如用
subTest
。
import unittest
def is_positive_number(n):
return isinstance(n, (int, float)) and n > 0
class TestParameterized(unittest.TestCase):
# 传统方法:写多个测试方法,代码重复
def test_positive_int(self):
self.assertTrue(is_positive_number(5))
def test_positive_float(self):
self.assertTrue(is_positive_number(3.14))
def test_zero(self):
self.assertFalse(is_positive_number(0))
# ... 更多重复方法
# 现代方法:使用subTest进行参数化测试(推荐!)
def test_is_positive_number_with_subtest(self):
test_cases = [
(5, True, "正整数应该返回True"),
(3.14, True, "正浮点数应该返回True"),
(0, False, "零应该返回False"),
(-1, False, "负整数应该返回False"),
("abc", False, "字符串应该返回False"),
(None, False, "None应该返回False"),
]
for input_val, expected, msg in test_cases:
with self.subTest(input=input_val, msg=msg):
# 每个subTest都是一个独立的测试上下文
# 如果其中一个失败,不会影响其他subTest的执行,并且会精确报告是哪个数据组失败了
result = is_positive_number(input_val)
self.assertEqual(result, expected, msg)
使用subTest的巨大优势
:当第三组数据(
0
)测试失败时,测试报告会明确指出是
subTest
中的
input=0
这一组失败了,而其他组(如
input=5
)依然会继续执行并报告成功。这比一个用例失败就全部停止,或者自己写循环但无法精确报错要好得多。
5.3 模拟(Mock)外部依赖
单元测试的核心是“隔离”。你的函数可能调用了数据库、网络请求、文件系统。测试时,我们需要用“假对象”替换这些真实依赖,这就是Mock。
unittest.mock
是Python标准库中的模拟框架,功能强大。
import unittest
from unittest.mock import Mock, patch, MagicMock
import requests
# 假设我们有一个函数,它调用外部API获取天气
def get_weather(city):
# 这是一个真实的外部调用,我们不想在单元测试中真的发起网络请求
response = requests.get(f"https://api.weather.com/v1/{city}")
if response.status_code == 200:
return response.json()['temperature']
return None
class TestWeatherFunction(unittest.TestCase):
# 方法1:使用patch装饰器,临时替换`requests.get`函数
@patch('__main__.requests.get') # 注意替换路径,这里因为函数定义在同一文件,用`__main__`
def test_get_weather_success(self, mock_get):
# 1. 准备模拟的响应对象
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'temperature': 22}
# 2. 配置mock_get,让它被调用时返回我们准备好的模拟响应
mock_get.return_value = mock_response
# 3. 执行被测试函数
result = get_weather('Beijing')
# 4. 断言函数返回了正确的结果
self.assertEqual(result, 22)
# 5. (可选) 断言requests.get被以正确的参数调用了一次
mock_get.assert_called_once_with("https://api.weather.com/v1/Beijing")
@patch('__main__.requests.get')
def test_get_weather_failure(self, mock_get):
mock_response = Mock()
mock_response.status_code = 404
mock_get.return_value = mock_response
result = get_weather('InvalidCity')
self.assertIsNone(result)
# 更复杂的场景:模拟对象的方法链式调用
class OrderProcessor:
def __init__(self, payment_gateway):
self.payment_gateway = payment_gateway
def process(self, order_id):
# 模拟一个复杂的方法链调用
result = self.payment_gateway.charge(order_id).get_status()
return result == "SUCCESS"
class TestOrderProcessor(unittest.TestCase):
def test_process_order_success(self):
# 创建一个MagicMock(它比Mock更“魔法”,可以模拟任何属性和方法)
mock_gateway = MagicMock()
# 配置方法链的返回值
mock_charge_result = MagicMock()
mock_charge_result.get_status.return_value = "SUCCESS"
mock_gateway.charge.return_value = mock_charge_result
processor = OrderProcessor(mock_gateway)
self.assertTrue(processor.process(123))
# 验证调用链
mock_gateway.charge.assert_called_once_with(123)
mock_charge_result.get_status.assert_called_once()
踩坑实录 :Mock最常见的坑是 打补丁(patch)的路径不对 。
@patch(‘a.b.c’)中的字符串必须是 被测代码中导入对象的位置 ,而不是它定义的位置。如果your_module.py里import requests,然后在测试中from your_module import get_weather,那么patch路径应该是@patch(‘your_module.requests.get’)。搞不清的时候,一个笨办法是直接在测试文件里print(requests.get.__module__)看看。
6. 集成与持续测试:让自动化跑起来
写好的测试不能只躺在你电脑里。我们需要把它集成到开发流程中,实现持续测试。
6.1 使用
unittest discover
自动发现用例
这是unittest最强大的命令行工具。你不需要手动导入所有测试文件。
# 最基本用法:发现当前目录及子目录下所有test_*.py文件中的测试
python -m unittest discover
# 指定起始目录和文件名模式
python -m unittest discover -s ./tests -p '*_test.py'
# 指定一个具体的测试模块
python -m unittest tests.unit.test_calculator
# 指定一个测试类
python -m unittest tests.unit.test_calculator.TestMathOperations
# 指定一个测试方法
python -m unittest tests.unit.test_calculator.TestMathOperations.test_addition
# 提高输出详细程度(看到每个用例的执行结果)
python -m unittest discover -v
# 遇到第一个失败就停止(快速失败模式)
python -m unittest discover -f
6.2 生成HTML测试报告
控制台输出对开发者友好,但对项目经理或需要存档的情况就不够直观。我们可以用第三方库生成漂亮的HTML报告。
首先安装:
pip install html-testRunner
# run_tests_with_html.py
import unittest
import HtmlTestRunner
import os
# 创建报告目录
report_dir = './test_reports'
os.makedirs(report_dir, exist_ok=True)
# 发现测试
test_suite = unittest.defaultTestLoader.discover(start_dir='./tests', pattern='test_*.py')
# 使用HTMLTestRunner运行
runner = HtmlTestRunner.HTMLTestRunner(
output=report_dir,
report_title='项目自动化测试报告',
report_name='Test_Report',
combine_reports=True, # 合并多个测试类的报告
add_timestamp=True,
)
runner.run(test_suite)
运行后,会在
test_reports
目录下生成一个
Test_Report.html
文件,用浏览器打开,可以看到清晰的通过/失败统计、每个用例的执行详情和日志,非常专业。
6.3 与CI/CD工具集成(以GitHub Actions为例)
现代项目离不开持续集成。这里给出一个最简单的GitHub Actions工作流配置,让你每次推送代码都能自动运行测试。
在你的项目根目录创建
.github/workflows/python-test.yml
:
name: Python Automated Tests
on: [push, pull_request] # 在推送代码或创建Pull Request时触发
jobs:
test:
runs-on: ubuntu-latest # 在最新的Ubuntu系统上运行
strategy:
matrix:
python-version: [‘3.8’, ‘3.9’, ‘3.10’] # 支持多个Python版本测试
steps:
- uses: actions/checkout@v2 # 1. 检出你的代码
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }} # 2. 安装指定Python版本
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # 3. 安装项目依赖
if [ -f requirements-test.txt ]; then pip install -r requirements-test.txt; fi # 安装测试依赖
- name: Run tests with unittest
run: |
python -m pytest tests/ --junitxml=./test-results/junit.xml --tb=short || true # 也可以用pytest运行unittest用例,并生成JUnit报告
# 或者直接用unittest discover生成报告
python -m xmlrunner discover -t . -s tests -p "test_*.py" --output-file ./test-results/junit.xml
- name: Upload test results
if: always() # 即使测试失败也上传报告
uses: actions/upload-artifact@v2
with:
name: test-results-${{ matrix.python-version }}
path: ./test-results/
retention-days: 7
配置好后,每次你的代码有变动,GitHub都会自动在一个干净的环境中安装依赖并运行所有测试。如果测试失败,你会收到通知,并且可以下载详细的测试报告进行分析。
7. 常见问题排查与性能优化
在实际项目中跑测试,你肯定会遇到各种奇怪的问题。这里列几个我印象最深的“坑”。
7.1 测试用例执行顺序之谜
unittest默认会按照 测试方法名的字母顺序 来执行,而不是你在文件中编写的顺序。这有时会导致一些微妙的依赖问题(虽然测试应该完全独立)。
解决方案
:如果你真的需要控制顺序(通常不推荐,这违反了测试独立性原则),可以使用
unittest.TestLoader
的
sortTestMethodsUsing
参数,或者更简单地,使用
unittest.TestSuite
来手动添加测试。
import unittest
import types
class TestOrder(unittest.TestCase):
def test_b(self):
print(“test_b”)
def test_a(self):
print(“test_a”)
def test_c(self):
print(“test_c”)
# 方法1:通过TestSuite控制顺序
def custom_suite():
suite = unittest.TestSuite()
suite.addTest(TestOrder(‘test_c’))
suite.addTest(TestOrder(‘test_a’))
suite.addTest(TestOrder(‘test_b’))
return suite
if __name__ == ‘__main__’:
runner = unittest.TextTestRunner()
runner.run(custom_suite())
# 输出: test_c, test_a, test_b
7.2 测试数据库的清理与隔离
测试涉及数据库时,最大的挑战是数据污染。每个测试必须在一个干净的数据环境中运行。
最佳实践 :
-
使用内存数据库
:如SQLite
:memory:。每个测试连接都是全新的数据库。 -
使用事务回滚
:在
setUp中开始事务,在tearDown中回滚。 - 使用独立的测试数据库 :并每次测试前清空关键表。
import unittest
import sqlite3
class TestWithDatabase(unittest.TestCase):
def setUp(self):
# 每次测试都连接到全新的内存数据库
self.conn = sqlite3.connect(‘:memory:’)
self.cursor = self.conn.cursor()
self.cursor.execute(‘CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)’)
self.conn.commit()
def tearDown(self):
self.conn.close() # 关闭连接,内存数据库随之销毁
def test_insert_user(self):
self.cursor.execute(“INSERT INTO users (name) VALUES (‘Alice’)”)
self.conn.commit()
self.cursor.execute(“SELECT COUNT(*) FROM users”)
count = self.cursor.fetchone()[0]
self.assertEqual(count, 1)
7.3 测试性能优化:跳过慢测试与并行执行
当测试套件有成百上千个用例时,执行时间可能很长。优化方法:
-
标记慢测试 :用
@unittest.skipIf配合一个环境变量,在快速验证时跳过耗时测试(如端到端UI测试)。import os @unittest.skipIf(os.getenv(‘SKIP_SLOW_TESTS’) == ‘1’, “跳过慢测试”) def test_slow_integration(self): # 这个测试可能要跑1分钟 pass快速验证时:
SKIP_SLOW_TESTS=1 python -m unittest discover -
使用
unittest的并行执行(Python 3.8+) :python -m unittest discover --parallel -
考虑升级到pytest :对于大型项目,pytest的插件生态(如
pytest-xdist)提供了更强大、更灵活的并行测试能力。这也是为什么很多人最终会从unittest迁移到pytest的原因之一。但记住,学好unittest是理解pytest的基础。
走到这里,你已经不是unittest的新手了。从理解核心概念,到搭建项目结构,再到使用高级特性和Mock应对复杂场景,最后集成到自动化流程中,这套组合拳足以让你应对工作中80%以上的Python自动化测试需求。剩下的20%,是随着项目复杂度的提升,去探索更专业的测试框架(如pytest)、持续集成流水线的深度定制、以及测试策略(单元测试、集成测试、端到端测试)的宏观设计。但有了unittest打下的坚实基础,那些都将是你自然而然会去探索的下一站。记住,最好的学习方式就是动手,找一个你自己的小项目,从为它写第一个
test_
方法开始吧。

9366

被折叠的 条评论
为什么被折叠?



