Skip to content

工程实践练习答案 ​

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-yourname

Usage ​

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 项目。

用心记录每一步成长