# 单元测试实践 ## 🎯 学习目标 通过本章学习,您将能够: - 理解单元测试的核心概念和设计原则 - 掌握pytest框架的单元测试编写技巧 - 学会为Chat-Room项目编写高质量的单元测试 - 实现测试覆盖率分析和质量度量 ## 🧪 单元测试设计 ### 单元测试架构 ```mermaid graph TB subgraph "单元测试架构" A[测试用例
Test Cases] --> A1[测试方法
Test Methods] A --> A2[测试类
Test Classes] A --> A3[测试模块
Test Modules] B[测试数据
Test Data] --> B1[夹具数据
Fixture Data] B --> B2[参数化数据
Parametrized Data] B --> B3[模拟数据
Mock Data] C[断言验证
Assertions] --> C1[状态断言
State Assertions] C --> C2[行为断言
Behavior Assertions] C --> C3[异常断言
Exception Assertions] D[测试隔离
Test Isolation] --> D1[独立性
Independence] D --> D2[可重复性
Repeatability] D --> D3[确定性
Determinism] end A --> B B --> C C --> D style A fill:#e8f5e8 style D fill:#f8d7da ``` ### 测试金字塔 ```mermaid graph TB subgraph "测试金字塔" A[UI测试
UI Tests
少量、慢速、昂贵] --> A1[端到端测试
E2E Tests] B[集成测试
Integration Tests
中等数量、中等速度] --> B1[API测试
API Tests] B --> B2[数据库测试
Database Tests] B --> B3[服务集成
Service Integration] C[单元测试
Unit Tests
大量、快速、便宜] --> C1[函数测试
Function Tests] C --> C2[类测试
Class Tests] C --> C3[模块测试
Module Tests] end A --> B B --> C style C fill:#e8f5e8 style B fill:#fff3cd style A fill:#f8d7da ``` ### 单元测试设计原则 单元测试应该遵循以下设计原则: 1. **FIRST原则** - **Fast(快速)**:单元测试应该运行得很快 - **Independent(独立)**:测试之间不应该相互依赖 - **Repeatable(可重复)**:在任何环境中都能重复运行 - **Self-Validating(自验证)**:测试应该有明确的通过/失败结果 - **Timely(及时)**:测试应该在产品代码之前编写 2. **AAA模式** - **Arrange(准备)**:设置测试数据和环境 - **Act(执行)**:执行被测试的操作 - **Assert(断言)**:验证结果是否符合预期 ## 🔧 单元测试实现 ### Chat-Room单元测试示例 ```python # tests/unit/test_user_model.py - 用户模型单元测试 import pytest from datetime import datetime from unittest.mock import Mock, patch import hashlib class TestUser: """用户模型单元测试""" def test_user_creation_valid_data(self): """测试用户创建 - 有效数据""" # 模拟用户类 class User: def __init__(self, username, email, password): self.username = username self.email = email self.password_hash = self._hash_password(password) self.created_at = datetime.now() self.is_active = True def _hash_password(self, password): return hashlib.sha256(password.encode()).hexdigest() # 测试用户创建 user = User("testuser", "test@example.com", "password123") assert user.username == "testuser" assert user.email == "test@example.com" assert user.password_hash is not None assert user.is_active is True assert isinstance(user.created_at, datetime) def test_user_creation_invalid_username(self): """测试用户创建 - 无效用户名""" class ValidationError(Exception): pass class User: def __init__(self, username, email, password): if len(username) < 3: raise ValidationError("用户名长度至少3个字符") self.username = username # 测试无效用户名 with pytest.raises(ValidationError, match="用户名长度至少3个字符"): User("ab", "test@example.com", "password123") class TestUserManager: """用户管理器单元测试""" def test_create_user_success(self): """测试创建用户成功""" # 模拟用户管理器 class UserManager: def __init__(self): self.users = {} self.next_id = 1 def create_user(self, username, email, password): if username in self.users: raise ValueError("用户名已存在") user_id = self.next_id self.next_id += 1 self.users[username] = { 'id': user_id, 'username': username, 'email': email, 'password_hash': hashlib.sha256(password.encode()).hexdigest() } return user_id manager = UserManager() user_id = manager.create_user("testuser", "test@example.com", "password123") assert user_id == 1 assert "testuser" in manager.users assert manager.users["testuser"]["email"] == "test@example.com" class TestMessage: """消息模型单元测试""" def test_message_creation_valid_data(self): """测试消息创建 - 有效数据""" class Message: def __init__(self, content, user_id, group_id=None): if not content.strip(): raise ValueError("消息内容不能为空") self.content = content self.user_id = user_id self.group_id = group_id self.created_at = datetime.now() self.message_type = "text" message = Message("Hello World", 1, 1) assert message.content == "Hello World" assert message.user_id == 1 assert message.group_id == 1 assert message.message_type == "text" assert isinstance(message.created_at, datetime) ## 🎭 Mock和Stub技术 ### 使用Mock对象 ```python # tests/unit/test_message_service.py - 使用Mock的消息服务测试 from unittest.mock import Mock, patch class TestMessageServiceWithMock: """使用Mock技术的消息服务测试""" def test_send_message_with_database_mock(self): """测试发送消息 - 使用数据库Mock""" # 模拟数据库连接 mock_db = Mock() mock_cursor = Mock() mock_db.cursor.return_value = mock_cursor mock_cursor.lastrowid = 123 # 模拟消息服务 class MessageService: def __init__(self, db): self.db = db def send_message(self, content, user_id, group_id): cursor = self.db.cursor() cursor.execute( "INSERT INTO messages (content, user_id, group_id) VALUES (?, ?, ?)", (content, user_id, group_id) ) self.db.commit() return cursor.lastrowid service = MessageService(mock_db) message_id = service.send_message("测试消息", 1, 1) # 验证Mock调用 mock_db.cursor.assert_called_once() mock_cursor.execute.assert_called_once_with( "INSERT INTO messages (content, user_id, group_id) VALUES (?, ?, ?)", ("测试消息", 1, 1) ) mock_db.commit.assert_called_once() assert message_id == 123 ## 📊 参数化测试 ```python class TestParametrizedValidation: """参数化验证测试""" @pytest.mark.parametrize("username,email,password,expected", [ ("validuser", "valid@example.com", "validpass123", True), ("ab", "valid@example.com", "validpass123", False), # 用户名太短 ("validuser", "invalid-email", "validpass123", False), # 邮箱无效 ("validuser", "valid@example.com", "123", False), # 密码太短 ]) def test_user_validation(self, username, email, password, expected): """参数化测试用户验证""" def validate_user(username, email, password): if not username or len(username) < 3: return False if not email or "@" not in email: return False if not password or len(password) < 6: return False return True result = validate_user(username, email, password) assert result == expected ``` ## 📋 学习检查清单 完成本节学习后,请确认您能够: - [ ] 理解单元测试的设计原则和架构 - [ ] 编写独立、快速、可重复的单元测试 - [ ] 使用AAA模式组织测试代码 - [ ] 应用Mock和Stub技术隔离依赖 - [ ] 编写参数化测试提高测试覆盖率 - [ ] 正确测试异常处理逻辑 - [ ] 为Chat-Room项目编写高质量单元测试 - [ ] 分析和改进测试代码质量 ## 🚀 下一步 掌握单元测试后,请继续学习: - [集成测试实践](integration-testing.md) - 组件间协作测试 - [TDD实践](tdd-practices.md) - 测试驱动开发 - [Pytest框架](pytest-framework.md) - 高级测试技巧 --- ## 📖 导航 ⬅️ **上一节:** [Pytest Framework](pytest-framework.md) ➡️ **下一节:** [Integration Testing](integration-testing.md) 📚 **返回:** [第15章:测试开发](README.md) 🏠 **主页:** [学习路径总览](../README.md) **单元测试是代码质量的基石,每个函数和类都应该有对应的单元测试!** 🧪