Python 测试 unittest/pytest
Python 测试 unittest/pytest 的完整教学讲解。
unittest 基础
基本写法:编写测试类
class <类>(unittest.TestCase):
# 继承 TestCase 编写单元测试
import unittest
class TestString(unittest.TestCase):
def test_upper(self):
self.assertEqual("abc".upper(), "ABC")
基本写法:运行测试
python -m unittest <模块>
# 命令行运行 unittest 测试
# 执行:python -m unittest test_my module
# 自动发现:python -m unittest discover
unittest 断言
基本写法:相等断言
self.assertEqual(<实际>, <期望>)
# 验证两值相等
self.assertEqual(sum([1, 2]), 3)
基本写法:不等与布尔
self.assertNotEqual / self.assertTrue / self.assertFalse
# 不等与布尔断言
self.assertNotEqual(1, 2)
self.assertTrue("a" in "abc")
self.assertFalse([])
基本写法:判断异常
self.assertRaises(<异常类>)
# 验证代码抛出指定异常
with self.assertRaises(ZeroDivisionError):
1 / 0
基本写法:异常匹配
self.assertRaisesRegex(<异常>, <正则>)
# 验证异常消息匹配
with self.assertRaisesRegex(ValueError, "invalid"):
int("abc")
基本写法:近似比较
self.assertAlmostEqual(<实际>, <期望>, places=<小数位>)
# 浮点数近似相等比较
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)
基本写法:包含判断
self.assertIn / self.assertNotIn
# 判断成员关系
self.assertIn(2, [1, 2, 3])
self.assertNotIn("x", "abc")
基本写法:None 判断
self.assertIsNone / self.assertIsNotNone
# 判断是否为 None
self.assertIsNone(None)
self.assertIsNotNone(0)
unittest 前后置
基本写法:每个用例前后置
def setUp(self) / def tearDown(self)
# 每个测试方法前后执行
class TestDB(unittest.TestCase):
def setUp(self):
self.conn = create_conn()
def tearDown(self):
self.conn.close()
def test_query(self):
self.assertTrue(self.conn.query())
基本写法:类级前后置
@classmethod def setUpClass / tearDownClass
# 整个测试类只执行一次
class TestSuite(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.data = load_fixture()
@classmethod
def tearDownClass(cls):
cls.data = None
pytest 基础
基本写法:函数式测试
def test_<函数名>():
# pytest 无需继承,直接写函数
def test_addition():
assert 1 + 1 == 2
基本写法:运行 pytest
pytest [<选项>]
# 常用命令行运行方式
# pytest 运行所有测试
# pytest test_x.py 运行指定文件
# pytest -k "add" 按名称匹配运行
# pytest -v 详细输出
# pytest --tb=short 简短回溯
基本写法:异常断言
pytest.raises(<异常类>)
# 验证抛出异常
import pytest
def test_div_zero():
with pytest.raises(ZeroDivisionError):
1 / 0
基本写法:近似断言
pytest.approx(<期望>)
# 浮点近似比较
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
pytest fixture
基本写法:定义 fixture
@pytest.fixture
# 通过 fixture 注入测试数据
@pytest.fixture
def sample_list():
return [1, 2, 3]
def test_len(sample_list):
assert len(sample_list) == 3
基本写法:yield 前后置
@pytest.fixture
def <名>(): yield <值>
# yield 前为准备,后为清理
@pytest.fixture
def db_conn():
conn = create_conn()
yield conn
conn.close()
基本写法:fixture 作用域
@pytest.fixture(scope="<作用域>")
# 控制 fixture 生命周期
@pytest.fixture(scope="session")
def config():
return load_config() # 整个会话只执行一次
基本写法:自动使用
@pytest.fixture(autouse=True)
# 自动应用到所有测试,无需参数
@pytest.fixture(autouse=True)
def reset_state():
yield
clear_cache()
pytest 参数化
基本写法:参数化测试
@pytest.mark.parametrize("<参数>", [(<值1>,), (<值2>,)])
# 一组数据生成多个用例
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(4, 5, 9),
(0, 0, 0),
])
def test_add(a, b, expected):
assert a + b == expected
pytest 标记
基本写法:自定义标记
@pytest.mark.<标记名>
# 标记测试分类运行
@pytest.mark.slow
def test_big_data():
...
# 运行:pytest -m slow
基本写法:跳过测试
@pytest.mark.skip(reason="<原因>")
# 无条件跳过
@pytest.mark.skip(reason="暂不支持")
def test_future():
pass
基本写法:条件跳过
@pytest.mark.skipif(<条件>, reason="<原因>")
# 满足条件时跳过
import sys
@pytest.mark.skipif(sys.platform == "win32", reason="仅 Linux")
def test_unix_only():
pass
基本写法:预期失败
@pytest.mark.xfail
# 标记为预期失败,失败不报错
@pytest.mark.xfail(reason="已知 bug")
def test_known_issue():
assert 1 == 2
mock 模拟
基本写法:patch 替换对象
unittest.mock.patch("<目标>")
# 临时替换函数或对象
from unittest.mock import patch
@patch("mymodule.requests.get")
def test_api(mock_get):
mock_get.return_value.status_code = 200
assert mymodule.fetch() == 200
基本写法:配置 mock 行为
mock.return_value / mock.side_effect
# 设置返回值或副作用
mock_get.return_value.json.return_value = {"ok": True}
mock_get.side_effect = ConnectionError("超时") # 抛异常
基本写法:断言调用
mock.assert_called_once_with(<参数>)
# 验证 mock 被调用情况
mock_get.assert_called_once_with("https://api.com")
mock_get.assert_not_called()
基本写法:MagicMock
MagicMock()
# 创建支持魔法方法的模拟对象
from unittest.mock import MagicMock
m = MagicMock()
m.__len__.return_value = 5
print(len(m)) # 5