工程实践练习答案
project-structure
练习 1:创建标准项目
要求:创建一个名为 calculator 的 Python 项目,使用 src layout。
答案:
bash
# 1. 创建目录结构
mkdir calculator
cd calculator
mkdir -p src/calculator tests docs
# 2. 创建源代码
cat > src/calculator/__init__.py << 'EOF'
"""Simple calculator package"""
__version__ = "0.1.0"
from .operations import add, subtract
__all__ = ["add", "subtract"]
EOF
cat > src/calculator/operations.py << 'EOF'
def add(a: float, b: float) -> float:
"""Add two numbers"""
return a + b
def subtract(a: float, b: float) -> float:
"""Subtract b from a"""
return a - b
EOF
# 3. 创建配置
cat > pyproject.toml << 'EOF'
[project]
name = "calculator"
version = "0.1.0"
description = "A simple calculator package"
requires-python = ">=3.10"
[project.optional-dependencies]
dev = ["pytest>=7.0"]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
EOF
# 4. 创建测试
cat > tests/test_operations.py << 'EOF'
from calculator.operations import add, subtract
def test_add():
assert add(2, 3) == 5
def test_subtract():
assert subtract(5, 3) == 2
EOF
# 5. 创建虚拟环境并测试
python3 -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pytest关键点:
- src layout 将源代码放在
src/目录 __init__.py定义包的公共 API- pyproject.toml 包含项目元数据
- 虚拟环境隔离依赖
testing-basics
练习 1:编写 pytest 测试
要求:为字符串处理函数编写测试。
python
def capitalize_words(text):
return ' '.join(word.capitalize() for word in text.split())答案:
python
# string_utils.py
def capitalize_words(text: str) -> str:
"""Capitalize the first letter of each word"""
return ' '.join(word.capitalize() for word in text.split())
# test_string_utils.py
import pytest
from string_utils import capitalize_words
def test_capitalize_single_word():
assert capitalize_words("hello") == "Hello"
def test_capitalize_multiple_words():
assert capitalize_words("hello world") == "Hello World"
def test_capitalize_mixed_case():
assert capitalize_words("hELLo WoRLd") == "Hello World"
def test_capitalize_empty_string():
assert capitalize_words("") == ""
@pytest.mark.parametrize("input_text,expected", [
("python", "Python"),
("python programming", "Python Programming"),
("PYTHON", "Python"),
("", ""),
])
def test_capitalize_parametrized(input_text, expected):
assert capitalize_words(input_text) == expected运行测试:
bash
pytest test_string_utils.py -v关键点:
- 测试边界情况(空字符串)
- 使用 parametrize 减少重复代码
- 测试函数命名清晰
练习 2:使用 fixtures
要求:测试一个用户管理类。
答案:
python
# user_manager.py
class UserManager:
def __init__(self):
self.users = {}
def add_user(self, user_id, name):
self.users[user_id] = name
def get_user(self, user_id):
return self.users.get(user_id)
def delete_user(self, user_id):
if user_id in self.users:
del self.users[user_id]
return True
return False
# test_user_manager.py
import pytest
from user_manager import UserManager
@pytest.fixture
def manager():
"""创建 UserManager 实例"""
return UserManager()
@pytest.fixture
def populated_manager():
"""创建带有初始数据的 UserManager"""
manager = UserManager()
manager.add_user(1, "Alice")
manager.add_user(2, "Bob")
return manager
def test_add_user(manager):
manager.add_user(1, "Alice")
assert manager.get_user(1) == "Alice"
def test_get_nonexistent_user(manager):
assert manager.get_user(999) is None
def test_delete_user(populated_manager):
assert populated_manager.delete_user(1) is True
assert populated_manager.get_user(1) is None
def test_delete_nonexistent_user(manager):
assert manager.delete_user(999) is False关键点:
- fixtures 提供测试数据
- 每个测试独立运行,互不影响
- populated_manager 复用 manager 逻辑
mocking
练习 1:Mock HTTP 请求
要求:测试一个获取用户信息的函数,Mock HTTP 请求。
答案:
python
# user_service.py
import requests
def get_user_info(user_id):
"""从 API 获取用户信息"""
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()
# test_user_service.py
import pytest
from unittest.mock import patch, Mock
import requests
from user_service import get_user_info
@patch('user_service.requests.get')
def test_get_user_info_success(mock_get):
# 设置 Mock 返回值
mock_response = Mock()
mock_response.json.return_value = {
"id": 123,
"name": "Alice",
"email": "alice@example.com"
}
mock_get.return_value = mock_response
# 调用函数
result = get_user_info(123)
# 验证
assert result["name"] == "Alice"
mock_get.assert_called_once_with("https://api.example.com/users/123")
@patch('user_service.requests.get')
def test_get_user_info_not_found(mock_get):
# 模拟 404 错误
mock_get.return_value.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
# 验证异常
with pytest.raises(requests.HTTPError):
get_user_info(999)关键点:
- 使用
@patch替换requests.get - Mock 返回值和异常
- 验证函数被正确调用
练习 2:Mock 文件操作
要求:测试读取配置文件的函数。
答案:
python
# config_loader.py
import json
def load_config(filename):
"""加载 JSON 配置文件"""
with open(filename) as f:
return json.load(f)
def get_database_url(config_file):
"""从配置文件获取数据库 URL"""
config = load_config(config_file)
return config.get("database", {}).get("url")
# test_config_loader.py
import pytest
from unittest.mock import patch, mock_open
import json
from config_loader import load_config, get_database_url
def test_load_config():
mock_data = '{"database": {"url": "postgresql://localhost/mydb"}}'
with patch("builtins.open", mock_open(read_data=mock_data)):
config = load_config("config.json")
assert config["database"]["url"] == "postgresql://localhost/mydb"
def test_get_database_url():
mock_config = {"database": {"url": "postgresql://localhost/mydb"}}
with patch('config_loader.load_config', return_value=mock_config):
url = get_database_url("config.json")
assert url == "postgresql://localhost/mydb"
def test_get_database_url_missing():
mock_config = {}
with patch('config_loader.load_config', return_value=mock_config):
url = get_database_url("config.json")
assert url is None关键点:
mock_open模拟文件操作- 可以 Mock 自己的函数
- 测试错误情况
debugging
练习 1:使用 logging 调试
要求:为数据处理函数添加日志。
答案:
python
# data_processor.py
import logging
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s - %(message)s')
def process_data(items):
"""处理数据列表"""
logging.info(f"开始处理 {len(items)} 个项目")
result = []
for i, item in enumerate(items):
logging.debug(f"处理第 {i+1} 个项目: {item}")
if item < 0:
logging.warning(f"发现负数: {item}")
continue
processed = item * 2
result.append(processed)
logging.debug(f"结果: {processed}")
logging.info(f"完成,处理了 {len(result)} 个有效项目")
return result
# 使用
data = [1, -2, 3, 4, -5]
result = process_data(data)
print(f"最终结果: {result}")输出:
INFO - 开始处理 5 个项目
DEBUG - 处理第 1 个项目: 1
DEBUG - 结果: 2
DEBUG - 处理第 2 个项目: -2
WARNING - 发现负数: -2
DEBUG - 处理第 3 个项目: 3
DEBUG - 结果: 6
DEBUG - 处理第 4 个项目: 4
DEBUG - 结果: 8
DEBUG - 处理第 5 个项目: -5
WARNING - 发现负数: -5
INFO - 完成,处理了 3 个有效项目
最终结果: [2, 6, 8]关键点:
- INFO 记录主要流程
- DEBUG 记录详细信息
- WARNING 记录异常情况
packaging
练习 1:创建可安装的包
要求:创建一个简单的包并发布到 Test PyPI。
答案:
bash
# 1. 创建项目结构
mkdir greeting_package
cd greeting_package
mkdir -p src/greeting
# 2. 源代码
cat > src/greeting/__init__.py << 'EOF'
__version__ = "0.1.0"
def greet(name):
return f"Hello, {name}!"
EOF
# 3. 配置
cat > pyproject.toml << 'EOF'
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "greeting-yourname" # 替换为唯一名称
version = "0.1.0"
description = "A simple greeting package"
readme = "README.md"
requires-python = ">=3.10"
authors = [{name = "Your Name", email = "you@example.com"}]
EOF
# 4. README
cat > README.md << 'EOF'
# Greeting Package
A simple package that greets people.
## Installation
```bash
pip install greeting-yournameUsage
python
from greeting import greet
print(greet("Alice"))EOF
5. 构建
pip install build twine python -m build
6. 上传到 Test PyPI
twine upload --repository testpypi dist/*
7. 测试安装
pip install --index-url https://test.pypi.org/simple/ greeting-yourname
**关键点**:
- 包名必须唯一
- 先发布到 Test PyPI 测试
- README 提供使用说明
---
**总结**:这些练习覆盖了 Python 工程实践的核心技能:项目结构、测试、Mock、调试和打包。掌握这些技能可以构建专业、可维护的 Python 项目。