30天学会Python编程:29.Python单元测试
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
|
test_addition() | ||
setUp() tearDown() | ||
assertEqual(result, 5) | ||
suite.addTest(TestMath) | ||
unittest.TextTestRunner() |
import unittest
classMathOperations:
defadd(self, a, b):
return a + b
classTestMathOperations(unittest.TestCase):
defsetUp(self):
"""每个测试方法前执行"""
self.math = MathOperations()
deftest_add_integers(self):
"""测试整数加法"""
result = self.math.add(5, 3)
self.assertEqual(result, 8)
deftest_add_floats(self):
"""测试浮点数加法"""
result = self.math.add(2.5, 3.1)
self.assertAlmostEqual(result, 5.6, places=1)
deftearDown(self):
"""每个测试方法后执行"""
delself.math
if __name__ == '__main__':
unittest.main()
assertEqual(a, b) | self.assertEqual(sum([1,2,3]), 6) | |
assertNotEqual(a, b) | self.assertNotEqual('a', 'b') | |
assertTrue(x) | self.assertTrue(10 > 5) | |
assertFalse(x) | self.assertFalse(5 > 10) | |
assertIs(a, b) | self.assertIs(obj1, obj2) | |
assertIsNone(x) | self.assertIsNone(result) | |
assertIn(a, b) | self.assertIn(3, [1,2,3]) | |
assertRaises(exc) | with self.assertRaises(ValueError): | |
assertAlmostEqual(a, b) | self.assertAlmostEqual(0.1+0.2, 0.3, places=7) |
class DatabaseTest(unittest.TestCase):
@classmethod
defsetUpClass(cls):
"""整个测试类执行前调用一次"""
cls.db = create_test_database()
cls.db.connect()
defsetUp(self):
"""每个测试方法前调用"""
self.cursor = self.db.create_cursor()
self.cursor.execute("BEGIN TRANSACTION")
deftest_insert(self):
self.cursor.execute("INSERT INTO users VALUES ('Alice')")
# 验证插入操作...
deftearDown(self):
"""每个测试方法后调用"""
self.cursor.execute("ROLLBACK")
self.cursor.close()
@classmethod
deftearDownClass(cls):
"""整个测试类执行后调用一次"""
cls.db.disconnect()
cls.db.drop()
# content of test_math.py
defadd(a, b):
return a + b
deftest_add_integers():
assert add(5, 3) == 8
deftest_add_floats():
"""测试浮点数加法"""
result = add(2.5, 3.1)
assertabs(result - 5.6) < 0.001
deftest_add_strings():
"""测试字符串拼接"""
assert add("Hello", "World") == "HelloWorld"
import pytest
@pytest.fixture(scope="module")
defdatabase():
"""模块级数据库固件"""
db = create_test_db()
yield db # 测试执行后继续执行清理
db.drop()
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-5, 5, 0),
(100, 200, 300),
])
deftest_addition(a, b, expected):
assert add(a, b) == expected
@pytest.mark.slow
deftest_large_number_addition():
"""标记为慢测试"""
assert add(10**18, 1) == 10**18 + 1
@pytest.mark.skipif(sys.version_info < (3, 8),
reason="需要Python 3.8或更高版本")
deftest_walrus_operator():
"""跳过特定Python版本的测试"""
assert (result := add(3, 4)) == 7
Mock | ||
Stub | ||
Spy | ||
Fake |
from unittest.mock import MagicMock, patch, PropertyMock
classPaymentProcessor:
defprocess_payment(self, amount, card_number):
# 实际实现调用外部支付网关
pass
classTestOrder(unittest.TestCase):
@patch('payment.PaymentProcessor.process_payment')
deftest_order_payment(self, mock_process):
"""测试订单支付流程"""
order = Order(total=100)
order.pay("4111111111111111")
# 验证支付方法被调用
mock_process.assert_called_once_with(100, "4111111111111111")
deftest_inventory_update(self):
"""测试库存更新"""
with patch('inventory.Inventory.decrease') as mock_decrease:
order = Order(items=[{"id": 1, "quantity": 2}])
order.process()
# 验证库存更新方法被调用
mock_decrease.assert_called_once_with(1, 2)
@patch.object(Product, 'price', new_callable=PropertyMock)
deftest_discount_calculation(self, mock_price):
"""测试折扣计算逻辑"""
mock_price.return_value = 100
product = Product(id=1)
# 测试折扣逻辑
discount = calculate_discount(product, 20)
self.assertEqual(discount, 20)
import unittest
from parameterized import parameterized
class TestMath(unittest.TestCase):
@parameterized.expand([
("positive", 5, 3, 8),
("zero", 0, 0, 0),
("negative", -5, 5, 0),
("large_numbers", 10**6, 10**6, 2*10**6),
])
def test_add(self, name, a, b, expected):
result = add(a, b)
self.assertEqual(result, expected)
import pytest
import csv
defload_test_data():
"""从CSV文件加载测试数据"""
withopen('test_data.csv', 'r') as f:
reader = csv.DictReader(f)
return [tuple(row.values()) for row in reader]
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
pytest.param(-5, 5, 0, id="negative_and_positive"),
pytest.param(100, 200, 300, marks=pytest.mark.slow),
])
deftest_addition(a, b, expected):
assert add(a, b) == expected
@pytest.mark.parametrize("a,b,expected", load_test_data())
deftest_from_csv(a, b, expected):
"""从外部数据源加载测试用例"""
a = int(a)
b = int(b)
expected = int(expected)
assert add(a, b) == expected
语句覆盖率 | ||
分支覆盖率 | ||
函数覆盖率 | ||
行覆盖率 | ||
条件覆盖率 |
# 安装插件
pip install pytest-cov
# 运行测试并生成报告
pytest --cov=my_project --cov-report=html
# 检查最小覆盖率
pytest --cov=my_project --cov-fail-under=90
# .coveragerc 配置文件
[run]
source = my_project
branch = True# 启用分支覆盖
omit =
*/__init__.py
*/tests/*
[report]
show_missing = True# 显示未覆盖行
exclude_lines =
pragma: no cover
def __repr__
if __name__ == .__main__.:
# 步骤1:编写失败测试
deftest_fibonacci():
assert fibonacci(0) == 0
assert fibonacci(1) == 1
assert fibonacci(2) == 1
assert fibonacci(5) == 5
# 步骤2:实现最小功能
deffibonacci(n):
if n == 0:
return0
elif n == 1:
return1
else:
return fibonacci(n-1) + fibonacci(n-2)
# 步骤3:重构改进(添加缓存优化)
from functools import lru_cache
@lru_cache(maxsize=None)
deffibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
pytest-xdist
并行执行
pytest -n auto # 自动检测CPU核心数
@pytest.mark.slow
def test_large_data_processing():
# 慢测试...
def user_factory(**kwargs):
defaults = {"name": "Test", "email": "test@example.com"}
return {**defaults, **kwargs}
上帝测试 | ||
测试耦合 | ||
实现细节测试 | ||
不稳定测试 | ||
慢测试 |
from fastapi.testclient import TestClient
from myapp.main import app
client = TestClient(app)
deftest_create_user():
response = client.post(
"/users/",
json={"name": "Alice", "email": "alice@example.com"}
)
assert response.status_code == 201
assert response.json()["name"] == "Alice"
# 验证用户是否创建成功
user_id = response.json()["id"]
get_response = client.get(f"/users/{user_id}")
assert get_response.status_code == 200
assert get_response.json()["email"] == "alice@example.com"
@patch("myapp.services.send_welcome_email")
deftest_user_creation_sends_email(mock_send):
client.post("/users/", json={"name": "Bob", "email": "bob@example.com"})
mock_send.assert_called_once_with("bob@example.com")
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture(scope="module")
deftest_db():
# 内存数据库用于测试
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
yield Session()
# 测试后自动清理
Base.metadata.drop_all(engine)
deftest_create_user(test_db):
user = User(name="Alice", email="alice@example.com")
test_db.add(user)
test_db.commit()
saved_user = test_db.query(User).filter_by(email="alice@example.com").first()
assert saved_user isnotNone
assert saved_user.name == "Alice"
# 测试唯一约束
duplicate = User(name="Alice2", email="alice@example.com")
test_db.add(duplicate)
with pytest.raises(IntegrityError):
test_db.commit()
test_<功能>_<条件>_<预期结果>
def test_add_positive_numbers_returns_sum()
def test_login_with_invalid_credentials_raises_error()
Test<模块名>
class TestUserModel(unittest.TestCase):
project/
├── src/
│ ├── __init__.py
│ ├── module1.py
│ └── module2.py
└── tests/
├── unit/
│ ├── test_module1.py
│ └── test_module2.py
├── integration/
│ └── test_integration.py
└── e2e/
└── test_e2e.py
单元测试是Python开发中的核心实践,提供以下关键价值:
高级进阶方向:
tox
进行多环境测试"没有测试的代码就是坏代码,不管它写得多么优雅" - Martin Fowler
通过持续实践单元测试,我们能构建更健壮、可维护的Python应用程序,显著提高开发效率和代码质量。
阅读原文:原文链接