# Chat-Room 测试文档
## 📋 概述
Chat-Room项目采用全面的测试策略,包括单元测试、集成测试、功能测试和性能测试。测试框架基于pytest,提供自动化测试、持续集成和测试覆盖率分析等功能。
## 🏗️ 测试架构
### 测试架构图
```mermaid
graph TB
subgraph "测试层次"
UNIT[单元测试
Unit Tests]
INTEGRATION[集成测试
Integration Tests]
FUNCTIONAL[功能测试
Functional Tests]
PERFORMANCE[性能测试
Performance Tests]
E2E[端到端测试
E2E Tests]
end
subgraph "测试工具"
PYTEST[pytest
测试框架]
MOCK[unittest.mock
模拟工具]
FIXTURES[pytest fixtures
测试夹具]
COVERAGE[pytest-cov
覆盖率工具]
end
subgraph "测试环境"
LOCAL[本地测试环境]
CI[CI/CD环境]
STAGING[预发布环境]
end
UNIT --> PYTEST
INTEGRATION --> PYTEST
FUNCTIONAL --> PYTEST
PERFORMANCE --> PYTEST
E2E --> PYTEST
PYTEST --> MOCK
PYTEST --> FIXTURES
PYTEST --> COVERAGE
PYTEST --> LOCAL
PYTEST --> CI
PYTEST --> STAGING
```
### 测试执行流程
```mermaid
flowchart TD
START([开始测试]) --> SETUP[测试环境准备]
SETUP --> UNIT_TEST[运行单元测试]
UNIT_TEST --> UNIT_PASS{单元测试通过?}
UNIT_PASS -->|是| INTEGRATION_TEST[运行集成测试]
UNIT_PASS -->|否| FIX_UNIT[修复单元测试]
FIX_UNIT --> UNIT_TEST
INTEGRATION_TEST --> INT_PASS{集成测试通过?}
INT_PASS -->|是| FUNCTIONAL_TEST[运行功能测试]
INT_PASS -->|否| FIX_INT[修复集成测试]
FIX_INT --> INTEGRATION_TEST
FUNCTIONAL_TEST --> FUNC_PASS{功能测试通过?}
FUNC_PASS -->|是| PERFORMANCE_TEST[运行性能测试]
FUNC_PASS -->|否| FIX_FUNC[修复功能测试]
FIX_FUNC --> FUNCTIONAL_TEST
PERFORMANCE_TEST --> PERF_PASS{性能测试通过?}
PERF_PASS -->|是| GENERATE_REPORT[生成测试报告]
PERF_PASS -->|否| FIX_PERF[优化性能]
FIX_PERF --> PERFORMANCE_TEST
GENERATE_REPORT --> CLEANUP[清理测试环境]
CLEANUP --> END([测试完成])
```
## 🧪 单元测试
### 测试目录结构
```
test/
├── unit/ # 单元测试
│ ├── server/ # 服务器端单元测试
│ │ ├── test_user_manager.py
│ │ ├── test_chat_manager.py
│ │ ├── test_file_handler.py
│ │ └── test_ai_handler.py
│ ├── client/ # 客户端单元测试
│ │ ├── test_chat_client.py
│ │ ├── test_command_parser.py
│ │ └── test_ui_components.py
│ └── shared/ # 共享模块单元测试
│ ├── test_protocol.py
│ ├── test_messages.py
│ └── test_config_manager.py
├── integration/ # 集成测试
│ ├── test_client_server.py
│ ├── test_database_integration.py
│ └── test_ai_integration.py
├── functional/ # 功能测试
│ ├── test_user_registration.py
│ ├── test_chat_functionality.py
│ └── test_file_transfer.py
├── performance/ # 性能测试
│ ├── test_concurrent_users.py
│ ├── test_message_throughput.py
│ └── test_file_transfer_speed.py
├── fixtures/ # 测试夹具
│ ├── database_fixtures.py
│ ├── user_fixtures.py
│ └── message_fixtures.py
└── utils/ # 测试工具
├── test_helpers.py
├── mock_server.py
└── test_data_generator.py
```
### 单元测试示例
#### 用户管理器测试 (test/unit/server/test_user_manager.py)
```python
import pytest
from unittest.mock import Mock, patch
from server.core.user_manager import UserManager
from shared.exceptions import UserException
class TestUserManager:
"""用户管理器单元测试"""
@pytest.fixture
def user_manager(self):
"""用户管理器测试夹具"""
with patch('server.database.connection.DatabaseManager'):
return UserManager()
@pytest.fixture
def mock_db(self, user_manager):
"""模拟数据库"""
return user_manager.db
def test_register_user_success(self, user_manager, mock_db):
"""测试用户注册成功"""
# 准备测试数据
username = "testuser"
password = "password123"
# 模拟数据库返回
mock_db.get_user_by_username.return_value = None
mock_db.create_user.return_value = True
# 执行测试
success, message = user_manager.register_user(username, password)
# 验证结果
assert success is True
assert "注册成功" in message
mock_db.create_user.assert_called_once()
def test_register_user_duplicate_username(self, user_manager, mock_db):
"""测试重复用户名注册"""
# 准备测试数据
username = "existinguser"
password = "password123"
# 模拟数据库返回已存在用户
mock_db.get_user_by_username.return_value = {"id": 1, "username": username}
# 执行测试
success, message = user_manager.register_user(username, password)
# 验证结果
assert success is False
assert "用户名已存在" in message
mock_db.create_user.assert_not_called()
def test_login_user_success(self, user_manager, mock_db):
"""测试用户登录成功"""
# 准备测试数据
username = "testuser"
password = "password123"
hashed_password = "$2b$12$hashedpassword"
# 模拟数据库返回
mock_db.get_user_by_username.return_value = {
"id": 1,
"username": username,
"password_hash": hashed_password
}
# 模拟密码验证
with patch('bcrypt.checkpw', return_value=True):
success, message, user_info = user_manager.login_user(username, password)
# 验证结果
assert success is True
assert user_info is not None
assert user_info["username"] == username
def test_login_user_invalid_password(self, user_manager, mock_db):
"""测试无效密码登录"""
# 准备测试数据
username = "testuser"
password = "wrongpassword"
hashed_password = "$2b$12$hashedpassword"
# 模拟数据库返回
mock_db.get_user_by_username.return_value = {
"id": 1,
"username": username,
"password_hash": hashed_password
}
# 模拟密码验证失败
with patch('bcrypt.checkpw', return_value=False):
success, message, user_info = user_manager.login_user(username, password)
# 验证结果
assert success is False
assert "密码错误" in message
assert user_info is None
@pytest.mark.parametrize("username,expected", [
("ab", False), # 太短
("validuser", True), # 有效
("a" * 25, False), # 太长
("user@name", False), # 包含非法字符
])
def test_validate_username(self, user_manager, username, expected):
"""测试用户名验证"""
is_valid, _ = user_manager._validate_username(username)
assert is_valid == expected
```
#### 聊天客户端测试 (test/unit/client/test_chat_client.py)
```python
import pytest
from unittest.mock import Mock, patch, MagicMock
from client.core.client import ChatClient
from shared.exceptions import NetworkException
class TestChatClient:
"""聊天客户端单元测试"""
@pytest.fixture
def chat_client(self):
"""聊天客户端测试夹具"""
return ChatClient("localhost", 8888)
@pytest.fixture
def mock_socket(self):
"""模拟Socket"""
with patch('socket.socket') as mock:
yield mock.return_value
def test_connect_success(self, chat_client, mock_socket):
"""测试连接成功"""
# 模拟连接成功
mock_socket.connect.return_value = None
# 执行测试
result = chat_client.connect()
# 验证结果
assert result is True
assert chat_client.connected is True
mock_socket.connect.assert_called_once_with(("localhost", 8888))
def test_connect_failure(self, chat_client, mock_socket):
"""测试连接失败"""
# 模拟连接失败
mock_socket.connect.side_effect = ConnectionRefusedError("Connection refused")
# 执行测试
result = chat_client.connect()
# 验证结果
assert result is False
assert chat_client.connected is False
def test_send_message_success(self, chat_client, mock_socket):
"""测试发送消息成功"""
# 准备测试环境
chat_client.connected = True
chat_client.socket = mock_socket
# 执行测试
result = chat_client.send_message("chat_message", {"content": "Hello"})
# 验证结果
assert result is True
mock_socket.send.assert_called_once()
def test_send_message_not_connected(self, chat_client):
"""测试未连接时发送消息"""
# 执行测试
result = chat_client.send_message("chat_message", {"content": "Hello"})
# 验证结果
assert result is False
```
## 🔗 集成测试
### 客户端-服务器集成测试 (test/integration/test_client_server.py)
```python
import pytest
import threading
import time
from server.core.server import ChatRoomServer
from client.core.client import ChatClient
class TestClientServerIntegration:
"""客户端-服务器集成测试"""
@pytest.fixture(scope="class")
def test_server(self):
"""测试服务器夹具"""
server = ChatRoomServer("localhost", 8889)
server_thread = threading.Thread(target=server.start)
server_thread.daemon = True
server_thread.start()
# 等待服务器启动
time.sleep(1)
yield server
# 清理
server.stop()
@pytest.fixture
def test_client(self, test_server):
"""测试客户端夹具"""
client = ChatClient("localhost", 8889)
yield client
client.disconnect()
def test_client_server_connection(self, test_client):
"""测试客户端服务器连接"""
# 连接服务器
success = test_client.connect()
assert success is True
# 验证连接状态
assert test_client.connected is True
def test_user_registration_and_login(self, test_client):
"""测试用户注册和登录流程"""
# 连接服务器
test_client.connect()
# 注册用户
success, message = test_client.register_user("testuser", "password123")
assert success is True
# 登录用户
success, message = test_client.login("testuser", "password123")
assert success is True
def test_chat_message_flow(self, test_client):
"""测试聊天消息流程"""
# 连接并登录
test_client.connect()
test_client.register_user("chatuser", "password123")
test_client.login("chatuser", "password123")
# 发送聊天消息
success = test_client.send_chat_message("Hello, World!")
assert success is True
```
## 🎯 功能测试
### 文件传输功能测试 (test/functional/test_file_transfer.py)
```python
import pytest
import os
import tempfile
from test.utils.test_helpers import create_test_file, start_test_server
class TestFileTransferFunctionality:
"""文件传输功能测试"""
@pytest.fixture(scope="class")
def test_environment(self):
"""测试环境夹具"""
server = start_test_server()
yield server
server.cleanup()
def test_file_upload_download_cycle(self, test_environment):
"""测试完整的文件上传下载周期"""
# 创建测试文件
test_file_path = create_test_file("test_document.txt", "Hello, World!")
# 连接并登录
client = test_environment.create_client()
client.connect()
client.register_user("fileuser", "password123")
client.login("fileuser", "password123")
# 上传文件
success, message = client.upload_file(test_file_path)
assert success is True
# 列出文件
success, message, files = client.list_files()
assert success is True
assert len(files) > 0
# 下载文件
file_id = files[0]['file_id']
success, message = client.download_file(file_id)
assert success is True
# 验证下载的文件
downloaded_path = f"client/Downloads/fileuser/test_document.txt"
assert os.path.exists(downloaded_path)
with open(downloaded_path, 'r') as f:
content = f.read()
assert content == "Hello, World!"
# 清理
os.remove(test_file_path)
os.remove(downloaded_path)
```
## ⚡ 性能测试
### 并发用户测试 (test/performance/test_concurrent_users.py)
```python
import pytest
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from test.utils.test_helpers import start_test_server, create_test_client
class TestConcurrentUsers:
"""并发用户性能测试"""
@pytest.fixture(scope="class")
def performance_server(self):
"""性能测试服务器"""
server = start_test_server(port=8890)
yield server
server.cleanup()
def test_concurrent_connections(self, performance_server):
"""测试并发连接"""
num_clients = 50
connection_results = []
def connect_client(client_id):
"""客户端连接函数"""
client = create_test_client(port=8890)
success = client.connect()
connection_results.append(success)
if success:
client.disconnect()
return success
# 并发连接测试
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_clients) as executor:
futures = [executor.submit(connect_client, i) for i in range(num_clients)]
results = [future.result() for future in futures]
end_time = time.time()
# 验证结果
success_count = sum(results)
success_rate = success_count / num_clients
connection_time = end_time - start_time
assert success_rate >= 0.95 # 95%成功率
assert connection_time < 10 # 10秒内完成
print(f"并发连接测试: {success_count}/{num_clients} 成功, 耗时: {connection_time:.2f}秒")
def test_message_throughput(self, performance_server):
"""测试消息吞吐量"""
num_messages = 1000
num_clients = 10
def send_messages(client_id):
"""发送消息函数"""
client = create_test_client(port=8890)
client.connect()
client.register_user(f"user{client_id}", "password123")
client.login(f"user{client_id}", "password123")
messages_sent = 0
for i in range(num_messages // num_clients):
success = client.send_chat_message(f"Message {i} from user {client_id}")
if success:
messages_sent += 1
client.disconnect()
return messages_sent
# 消息吞吐量测试
start_time = time.time()
with ThreadPoolExecutor(max_workers=num_clients) as executor:
futures = [executor.submit(send_messages, i) for i in range(num_clients)]
results = [future.result() for future in futures]
end_time = time.time()
# 计算结果
total_sent = sum(results)
test_duration = end_time - start_time
throughput = total_sent / test_duration
assert throughput >= 100 # 每秒至少100条消息
print(f"消息吞吐量测试: {total_sent}条消息, 耗时: {test_duration:.2f}秒, 吞吐量: {throughput:.2f}msg/s")
```
## 🔧 测试工具和夹具
### 测试夹具 (test/fixtures/database_fixtures.py)
```python
import pytest
import tempfile
import os
from server.database.connection import DatabaseManager
@pytest.fixture
def temp_database():
"""临时数据库夹具"""
# 创建临时数据库文件
db_fd, db_path = tempfile.mkstemp(suffix='.db')
os.close(db_fd)
# 初始化数据库
db_manager = DatabaseManager(db_path)
db_manager.init_database()
yield db_manager
# 清理
os.unlink(db_path)
@pytest.fixture
def sample_users():
"""示例用户数据"""
return [
{"username": "alice", "password": "password123"},
{"username": "bob", "password": "password456"},
{"username": "charlie", "password": "password789"}
]
@pytest.fixture
def sample_messages():
"""示例消息数据"""
return [
{"content": "Hello, everyone!", "user_id": 1, "group_id": 1},
{"content": "How are you?", "user_id": 2, "group_id": 1},
{"content": "I'm fine, thanks!", "user_id": 3, "group_id": 1}
]
```
### 测试辅助工具 (test/utils/test_helpers.py)
```python
import tempfile
import os
import threading
import time
from server.core.server import ChatRoomServer
from client.core.client import ChatClient
def create_test_file(filename: str, content: str) -> str:
"""创建测试文件"""
temp_dir = tempfile.gettempdir()
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
return file_path
def start_test_server(host: str = "localhost", port: int = 8889) -> ChatRoomServer:
"""启动测试服务器"""
server = ChatRoomServer(host, port)
server_thread = threading.Thread(target=server.start)
server_thread.daemon = True
server_thread.start()
# 等待服务器启动
time.sleep(1)
return server
def create_test_client(host: str = "localhost", port: int = 8889) -> ChatClient:
"""创建测试客户端"""
return ChatClient(host, port)
class TestDataGenerator:
"""测试数据生成器"""
@staticmethod
def generate_users(count: int) -> list:
"""生成测试用户"""
return [
{
"username": f"user{i}",
"password": f"password{i}",
"email": f"user{i}@test.com"
}
for i in range(count)
]
@staticmethod
def generate_messages(count: int, user_ids: list, group_id: int) -> list:
"""生成测试消息"""
import random
return [
{
"content": f"Test message {i}",
"user_id": random.choice(user_ids),
"group_id": group_id
}
for i in range(count)
]
```
## 🚀 运行测试
### 测试命令
```bash
# 运行所有测试
pytest
# 运行特定类型的测试
pytest test/unit/ # 单元测试
pytest test/integration/ # 集成测试
pytest test/functional/ # 功能测试
pytest test/performance/ # 性能测试
# 运行特定模块测试
pytest test/unit/server/test_user_manager.py
# 运行特定测试函数
pytest test/unit/server/test_user_manager.py::TestUserManager::test_register_user_success
# 生成覆盖率报告
pytest --cov=server --cov=client --cov=shared --cov-report=html
# 并行运行测试
pytest -n auto
# 详细输出
pytest -v
# 只运行失败的测试
pytest --lf
# 运行性能测试(需要特殊标记)
pytest -m performance
```
### 持续集成配置 (.github/workflows/test.yml)
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov pytest-xdist
- name: Run tests
run: |
pytest --cov=server --cov=client --cov=shared --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
```
这个测试文档提供了完整的测试策略、测试架构、测试用例和运行指南,帮助开发者理解和执行Chat-Room项目的测试工作。