今日练习主题:测试与调试
今天我们将深入学习Python的测试和调试技术,包括单元测试、调试工具、性能分析和代码质量检查。
练习1:单元测试与测试框架
import unittest
from unittest.mock import Mock, patch, MagicMock
import pytest
import doctest
# 待测试的示例代码
class Calculator:
"""一个简单的计算器类"""
def add(self, a, b):
"""加法运算"""
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("参数必须是数字")
return a + b
def subtract(self, a, b):
"""减法运算"""
return a - b
def multiply(self, a, b):
"""乘法运算"""
return a * b
def divide(self, a, b):
"""除法运算"""
if b == 0:
raise ValueError("除数不能为零")
return a / b
def factorial(self, n):
"""计算阶乘"""
if n < 0:
raise ValueError("阶乘不能为负数")
if n == 0:
return 1
return n * self.factorial(n - 1)
def email_validator(email):
"""简单的邮箱验证函数"""
if not email or '@' not in email:
return False
local_part, domain = email.split('@', 1)
return bool(local_part) and bool(domain) and '.' in domain
# 使用doctest的示例
def fibonacci(n):
"""
计算斐波那契数列的第n项
Examples:
>>> fibonacci(0)
0
>>> fibonacci(1)
1
>>> fibonacci(5)
5
>>> fibonacci(10)
55
"""
if n < 0:
raise ValueError("n不能为负数")
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
class TestCalculator(unittest.TestCase):
"""Calculator类的单元测试"""
def setUp(self):
"""测试前置设置"""
self.calc = Calculator()
print("创建Calculator实例")
def tearDown(self):
"""测试后置清理"""
self.calc = None
print("清理Calculator实例")
def test_add(self):
"""测试加法"""
self.assertEqual(self.calc.add(2, 3), 5)
self.assertEqual(self.calc.add(-1, 1), 0)
self.assertEqual(self.calc.add(0, 0), 0)
self.assertAlmostEqual(self.calc.add(0.1, 0.2), 0.3, places=7)
def test_add_type_error(self):
"""测试加法类型错误"""
with self.assertRaises(TypeError):
self.calc.add("2", 3)
with self.assertRaises(TypeError):
self.calc.add(2, "3")
def test_subtract(self):
"""测试减法"""
self.assertEqual(self.calc.subtract(5, 3), 2)
self.assertEqual(self.calc.subtract(3, 5), -2)
def test_multiply(self):
"""测试乘法"""
self.assertEqual(self.calc.multiply(3, 4), 12)
self.assertEqual(self.calc.multiply(-2, 3), -6)
self.assertEqual(self.calc.multiply(0, 5), 0)
def test_divide(self):
"""测试除法"""
self.assertEqual(self.calc.divide(6, 3), 2)
self.assertEqual(self.calc.divide(5, 2), 2.5)
def test_divide_by_zero(self):
"""测试除零错误"""
with self.assertRaises(ValueError):
self.calc.divide(5, 0)
def test_factorial(self):
"""测试阶乘"""
self.assertEqual(self.calc.factorial(0), 1)
self.assertEqual(self.calc.factorial(1), 1)
self.assertEqual(self.calc.factorial(5), 120)
def test_factorial_negative(self):
"""测试负数阶乘错误"""
with self.assertRaises(ValueError):
self.calc.factorial(-1)
@unittest.skip("暂时跳过这个测试")
def test_skip_example(self):
"""跳过测试的示例"""
self.fail("这个测试应该被跳过")
@unittest.skipIf(not hasattr(Calculator, 'advanced_function'),
"需要高级功能支持")
def test_conditional_skip(self):
"""条件跳过测试"""
pass
class TestEmailValidator(unittest.TestCase):
"""邮箱验证函数的测试"""
def test_valid_emails(self):
"""测试有效邮箱"""
valid_emails = [
"test@example.com",
"user.name@domain.co.uk",
"user+tag@domain.org"
]
for email in valid_emails:
with self.subTest(email=email):
self.assertTrue(email_validator(email))
def test_invalid_emails(self):
"""测试无效邮箱"""
invalid_emails = [
"",
"invalid",
"invalid@",
"@domain.com",
"user@domain"
]
for email in invalid_emails:
with self.subTest(email=email):
self.assertFalse(email_validator(email))
class TestWithMocks(unittest.TestCase):
"""使用Mock对象的测试"""
def test_mock_example(self):
"""Mock对象示例"""
# 创建Mock对象
mock_obj = Mock()
mock_obj.some_method.return_value = "mocked result"
# 使用Mock对象
result = mock_obj.some_method()
self.assertEqual(result, "mocked result")
# 验证方法被调用
mock_obj.some_method.assert_called_once()
def test_patch_example(self):
"""使用patch的示例"""
with patch('builtins.print') as mock_print:
print("Hello, World!")
mock_print.assert_called_with("Hello, World!")
def test_mock_side_effect(self):
"""Mock的side_effect使用"""
mock_obj = Mock()
mock_obj.get_value.side_effect = [1, 2, 3]
self.assertEqual(mock_obj.get_value(), 1)
self.assertEqual(mock_obj.get_value(), 2)
self.assertEqual(mock_obj.get_value(), 3)
# 使用pytest风格的测试(需要安装pytest)
def test_fibonacci_pytest_style():
"""使用pytest风格的斐波那契测试"""
assert fibonacci(0) == 0
assert fibonacci(1) == 1
assert fibonacci(5) == 5
assert fibonacci(10) == 55
def test_fibonacci_negative():
"""测试斐波那契负数输入"""
with pytest.raises(ValueError):
fibonacci(-1)
def test_calculator_pytest_style():
"""pytest风格的计算器测试"""
calc = Calculator()
assert calc.add(2, 3) == 5
assert calc.multiply(4, 5) == 20
def run_tests():
"""运行所有测试"""
print("=== 单元测试与测试框架 ===")
# 1. 运行unittest测试
print("1. 运行unittest测试")
unittest_loader = unittest.TestLoader()
# 运行单个测试类
print("
运行Calculator测试:")
calculator_suite = unittest_loader.loadTestsFromTestCase(TestCalculator)
calculator_runner = unittest.TextTestRunner(verbosity=2)
calculator_result = calculator_runner.run(calculator_suite)
print("
运行EmailValidator测试:")
email_suite = unittest_loader.loadTestsFromTestCase(TestEmailValidator)
email_runner = unittest.TextTestRunner(verbosity=2)
email_result = email_runner.run(email_suite)
# 2. 运行doctest
print("
2. 运行doctest")
doctest_result = doctest.testmod()
print(f"Doctest结果: 运行{doctest_result.attempted}个测试,失败{doctest_result.failed}个")
# 3. 创建测试套件
print("
3. 创建测试套件")
all_tests = unittest.TestSuite()
all_tests.addTest(calculator_suite)
all_tests.addTest(email_suite)
print(f"测试套件包含 {all_tests.countTestCases()} 个测试用例")
# 4. 测试发现
print("
4. 测试发现示例")
# 在实际项目中可以使用:python -m unittest discover
return {
'calculator_tests': calculator_result,
'email_tests': email_result,
'doctest': doctest_result
}
# 运行测试
test_results = run_tests()
练习2:调试技术与性能分析
import pdb
import logging
import cProfile
import pstats
import timeit
from memory_profiler import profile
import traceback
import sys
def debugging_techniques():
"""调试技术与性能分析"""
print("
=== 调试技术与性能分析 ===")
# 设置日志记录
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('debug.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger('DebugDemo')
class DebugExample:
def __init__(self):
self.data = []
self.counter = 0
logger.info("DebugExample实例已创建")
def problematic_function(self, n):
"""一个有问题的函数,用于调试演示"""
logger.debug(f"开始执行problematic_function({n})")
result = 0
for i in range(n):
# 故意制造一个潜在问题
if i == 5:
logger.warning("遇到特殊值 i=5")
result += i * 2
# 模拟复杂计算
time.sleep(0.01)
self.counter += 1
logger.debug(f"problematic_function完成,结果: {result}")
return result
def divide_numbers(self, a, b):
"""除法函数,可能抛出异常"""
logger.info(f"执行除法: {a} / {b}")
if b == 0:
logger.error("除零错误!")
raise ValueError("除数不能为零")
result = a / b
logger.debug(f"除法结果: {result}")
return result
def process_data(self, data_list):
"""处理数据的函数"""
logger.info(f"开始处理数据,数量: {len(data_list)}")
processed = []
for i, item in enumerate(data_list):
try:
# 模拟数据处理
if isinstance(item, str):
processed.append(item.upper())
elif isinstance(item, (int, float)):
processed.append(item * 2)
else:
logger.warning(f"无法处理的数据类型: {type(item)}")
processed.append(None)
except Exception as e:
logger.error(f"处理数据时出错: {e}", exc_info=True)
processed.append(None)
logger.info("数据处理完成")
return processed
def demo_basic_debugging():
"""基础调试演示"""
print("1. 基础调试技术")
print("-" * 40)
example = DebugExample()
# 使用print调试(不推荐在生产环境使用)
print("=== Print调试 ===")
result = example.problematic_function(3)
print(f"结果: {result}")
# 使用日志调试
print("
=== 日志调试 ===")
example.problematic_function(5)
# 异常处理
print("
=== 异常处理 ===")
try:
example.divide_numbers(10, 0)
except ValueError as e:
print(f"捕获到异常: {e}")
# 打印完整的异常信息
traceback.print_exc()
# 正常情况
try:
result = example.divide_numbers(10, 2)
print(f"正常除法结果: {result}")
except ValueError as e:
print(f"异常: {e}")
def demo_pdb_debugging():
"""PDB调试演示"""
print("
2. PDB调试器")
print("-" * 40)
def buggy_function(numbers):
"""一个有bug的函数"""
total = 0
for i in range(len(numbers)):
# 这里有一个off-by-one错误
total += numbers[i] # 应该是numbers[i]
print(f"处理第 {i} 个数字: {numbers[i]}")
average = total / len(numbers) # 如果numbers为空会除零
return total, average
# 手动触发PDB(撤销注释来使用)
# print("撤销注释下面的代码来体验PDB调试")
# numbers = [1, 2, 3, 4, 5]
# 设置断点
# pdb.set_trace()
# result = buggy_function(numbers)
# print(f"结果: {result}")
print("PDB常用命令:")
print(" n(ext) - 执行下一行")
print(" s(tep) - 进入函数")
print(" c(ontinue) - 继续执行")
print(" l(ist) - 显示代码")
print(" p(rint) - 打印变量")
print(" q(uit) - 退出调试")
def demo_profiling():
"""性能分析演示"""
print("
3. 性能分析")
print("-" * 40)
def slow_function(n):
"""一个运行较慢的函数"""
result = 0
for i in range(n):
for j in range(1000):
result += i * j
return result
def fast_function(n):
"""一个优化后的函数"""
return sum(i * j for i in range(n) for j in range(1000))
# 使用timeit测试性能
print("=== 性能比较 ===")
n = 100
slow_time = timeit.timeit(lambda: slow_function(n), number=10)
fast_time = timeit.timeit(lambda: fast_function(n), number=10)
print(f"慢函数执行时间: {slow_time:.4f}秒")
print(f"快函数执行时间: {fast_time:.4f}秒")
print(f"性能提升: {slow_time/fast_time:.2f}倍")
# 使用cProfile进行详细分析
print("
=== cProfile分析 ===")
profiler = cProfile.Profile()
profiler.enable()
# 执行要分析的代码
slow_function(50)
fast_function(50)
profiler.disable()
# 输出分析结果
stats = pstats.Stats(profiler)
print("函数调用统计:")
stats.sort_stats('cumulative').print_stats(10)
@profile
def memory_intensive_function():
"""内存密集型函数(需要memory-profiler包)"""
print("
4. 内存分析")
print("-" * 40)
# 创建大量数据
big_list = [i for i in range(100000)]
big_dict = {i: str(i) for i in range(50000)}
# 处理数据
processed = [x * 2 for x in big_list]
result = {k: v.upper() for k, v in big_dict.items()}
return processed, result
def demo_advanced_debugging():
"""高级调试技术"""
print("
5. 高级调试技术")
print("-" * 40)
# 断言调试
def calculate_discount(price, discount_rate):
"""计算折扣价格"""
assert price > 0, "价格必须大于0"
assert 0 <= discount_rate <= 1, "折扣率必须在0-1之间"
discounted_price = price * (1 - discount_rate)
assert discounted_price >= 0, "折扣后价格不能为负"
return discounted_price
print("=== 断言调试 ===")
try:
result = calculate_discount(100, 0.2)
print(f"折扣价格: {result}")
# 这会触发断言错误
# result = calculate_discount(-100, 0.2)
except AssertionError as e:
print(f"断言错误: {e}")
# 使用sys.excepthook处理未捕获的异常
def global_exception_handler(exc_type, exc_value, exc_traceback):
"""全局异常处理"""
if issubclass(exc_type, KeyboardInterrupt):
# 忽略Ctrl+C
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.critical("未捕获的异常:",
exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = global_exception_handler
print("=== 自定义异常钩子已设置 ===")
def demo_debugging_tools():
"""调试工具演示"""
print("
6. 调试工具")
print("-" * 40)
# 使用inspect模块
import inspect
def example_function(x, y=10):
"""示例函数"""
return x + y
print("=== 代码检查 ===")
print(f"函数名: {example_function.__name__}")
print(f"文档字符串: {example_function.__doc__}")
print(f"源代码: {inspect.getsource(example_function)}")
# 获取函数签名
sig = inspect.signature(example_function)
print(f"参数: {list(sig.parameters.keys())}")
# 使用__debug__常量
print("
=== 调试常量 ===")
if __debug__:
print("调试模式已启用")
else:
print("调试模式未启用(使用-O优化选项时)")
# 运行所有调试演示
demo_basic_debugging()
demo_pdb_debugging()
demo_profiling()
# 内存分析需要单独运行:python -m memory_profiler script.py
print("
内存分析示例需要运行: python -m memory_profiler script.py")
demo_advanced_debugging()
demo_debugging_tools()
logger.info("调试演示完成")
def create_test_project():
"""创建完整的测试项目示例"""
print("
=== 完整测试项目示例 ===")
# 项目结构
project_structure = """
my_project/
├── src/
│ ├── __init__.py
│ ├── calculator.py
│ ├── validator.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_calculator.py
│ ├── test_validator.py
│ ├── test_utils.py
│ └── conftest.py
├── requirements.txt
├── setup.py
├── pytest.ini
└── .github/
└── workflows/
└── tests.yml
"""
print("项目结构:")
print(project_structure)
# 创建示例配置文件
config_examples = {
'pytest.ini': """
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --strict-markers
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
integration: integration tests
""",
'conftest.py': """
import pytest
from src.calculator import Calculator
from src.validator import EmailValidator
@pytest.fixture
def calculator():
'''提供Calculator实例'''
return Calculator()
@pytest.fixture
def email_validator():
'''提供EmailValidator实例'''
return EmailValidator()
@pytest.fixture(autouse=True)
def setup_teardown():
'''每个测试自动运行的设置和清理'''
print("测试开始")
yield
print("测试结束")
""",
'github_workflow': """
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
python -m pytest tests/ -v --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v2
"""
}
print("配置文件示例:")
for filename, content in config_examples.items():
print(f"
{filename}:")
print(content)
# 测试策略提议
testing_strategy = """
测试策略提议:
1. 单元测试 (Unit Tests)
- 测试单个函数/方法
- 快速执行,隔离依赖
- 使用mock对象
2. 集成测试 (Integration Tests)
- 测试模块间交互
- 验证数据流和接口
3. 功能测试 (Functional Tests)
- 测试完整功能
- 从用户角度验证
4. 性能测试 (Performance Tests)
- 测试系统性能
- 压力测试和负载测试
5. 端到端测试 (E2E Tests)
- 测试完整工作流
- 模拟真实用户场景
"""
print(testing_strategy)
# 最佳实践
best_practices = """
测试最佳实践:
✓ 测试名称要具有描述性
✓ 每个测试只测试一个功能
✓ 使用setup和teardown管理测试环境
✓ 测试要独立,不依赖执行顺序
✓ 使用适当的断言方法
✓ 测试边界情况和异常情况
✓ 保持测试代码的整洁
✓ 定期运行测试套件
✓ 使用持续集成
✓ 测量测试覆盖率
"""
print(best_practices)
def run_comprehensive_testing():
"""运行综合测试演示"""
print("=== 综合测试演示 ===")
# 运行单元测试
test_results = run_tests()
# 运行调试演示
debugging_techniques()
# 创建测试项目示例
create_test_project()
# 测试覆盖率报告(模拟)
print("
=== 测试覆盖率报告 ===")
coverage_report = {
'模块': ['calculator.py', 'validator.py', 'utils.py', '全部'],
'语句覆盖率': [95, 88, 92, 92],
'分支覆盖率': [90, 85, 88, 88],
'函数覆盖率': [100, 95, 96, 97]
}
print("测试覆盖率统计:")
for i, module in enumerate(coverage_report['模块']):
stmt = coverage_report['语句覆盖率'][i]
branch = coverage_report['分支覆盖率'][i]
func = coverage_report['函数覆盖率'][i]
print(f"{module:10} | 语句: {stmt:3}% | 分支: {branch:3}% | 函数: {func:3}%")
# 测试质量评估
print("
=== 测试质量评估 ===")
quality_metrics = {
'总测试用例数': 45,
'通过测试数': 43,
'失败测试数': 2,
'跳过测试数': 1,
'测试执行时间': '2.3秒',
'平均测试时间': '51毫秒',
'测试代码行数': 560,
'生产代码行数': 320,
'测试/代码比例': '1.75:1'
}
for metric, value in quality_metrics.items():
print(f"{metric}: {value}")
# 提议和改善
print("
=== 改善提议 ===")
improvements = [
"增加边界情况测试",
"提高异常情况测试覆盖率",
"添加性能测试",
"实现持续集成流水线",
"增加集成测试用例"
]
for i, improvement in enumerate(improvements, 1):
print(f"{i}. {improvement}")
return {
'test_results': test_results,
'coverage': coverage_report,
'quality_metrics': quality_metrics,
'improvements': improvements
}
# 运行综合测试演示
final_results = run_comprehensive_testing()
print("
" + "="*60)
print("第二十天学习完成!")
print("="*60)
print("今天学习了:")
print("✓ 单元测试框架(unittest, pytest)")
print("✓ 测试用例设计和组织")
print("✓ Mock对象和测试替身")
print("✓ 调试工具和技术")
print("✓ 性能分析和优化")
print("✓ 测试最佳实践")
print("✓ 持续集成和测试自动化")
学习总结:
测试方面:
- 掌握了unittest和pytest测试框架
- 学会了编写有效的测试用例
- 理解了Mock对象和测试隔离
- 学会了测试组织和执行策略
调试方面:
- 掌握了多种调试工具和技术
- 学会了性能分析和优化方法
- 理解了日志记录和异常处理
- 学会了使用专业调试器
最佳实践:
- 测试驱动开发(TDD)理念
- 持续集成和自动化测试
- 代码质量保证方法
- 测试覆盖率分析
这些技能对于编写高质量、可维护的Python代码至关重大。明天我们将开始学习更高级的主题!
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END














暂无评论内容