feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,245 @@
"""测试API客户端"""
import pytest
import time
from unittest.mock import Mock, patch, MagicMock
import requests
from apitest.client.api_client import APIClient
from apitest.models.exceptions import RequestException
from apitest.models.test_models import HTTPMethod, PerformanceMetrics
class TestAPIClient:
"""测试APIClient类"""
def test_init(self):
"""测试初始化"""
client = APIClient("http://localhost:8080", timeout=5000, max_retries=3)
assert client.base_url == "http://localhost:8080"
assert client.timeout == 5.0
assert client.max_retries == 3
assert "Content-Type" in client._default_headers
assert "Accept" in client._default_headers
def test_init_with_logger(self):
"""测试带日志记录器的初始化"""
logger = Mock()
client = APIClient("http://localhost:8080", logger=logger)
assert client.logger == logger
def test_set_default_headers(self):
"""测试设置默认请求头"""
client = APIClient("http://localhost:8080")
client.set_default_headers({"X-Custom-Header": "value"})
assert "X-Custom-Header" in client._default_headers
assert client._default_headers["X-Custom-Header"] == "value"
def test_set_auth_token(self):
"""测试设置认证token"""
client = APIClient("http://localhost:8080")
client.set_auth_token("test-token")
assert "Authorization" in client._default_headers
assert client._default_headers["Authorization"] == "Bearer test-token"
def test_build_url(self):
"""测试构建URL"""
client = APIClient("http://localhost:8080")
url = client._build_url("/api/test")
assert url == "http://localhost:8080/api/test"
def test_build_url_with_leading_slash(self):
"""测试构建URL(带前导斜杠)"""
client = APIClient("http://localhost:8080")
url = client._build_url("api/test")
assert url == "http://localhost:8080/api/test"
def test_merge_headers(self):
"""测试合并请求头"""
client = APIClient("http://localhost:8080")
client.set_default_headers({"X-Default": "default"})
merged = client._merge_headers({"X-Custom": "custom"})
assert merged["X-Default"] == "default"
assert merged["X-Custom"] == "custom"
def test_merge_headers_override(self):
"""测试合并请求头(覆盖)"""
client = APIClient("http://localhost:8080")
client.set_default_headers({"Content-Type": "application/xml"})
merged = client._merge_headers({"Content-Type": "application/json"})
assert merged["Content-Type"] == "application/json"
@patch('requests.Session.get')
def test_request_success(self, mock_get):
"""测试成功请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_get.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.request(HTTPMethod.GET, "/api/test")
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
assert "performance" in result
@patch('requests.Session.get')
def test_request_with_params(self, mock_get):
"""测试带参数的请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_get.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.request(HTTPMethod.GET, "/api/test", params={"page": 1, "size": 10})
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
mock_get.assert_called_once()
@patch('requests.Session.post')
def test_request_with_body(self, mock_post):
"""测试带请求体的请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_post.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.request(HTTPMethod.POST, "/api/test", body={"name": "test"})
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
mock_post.assert_called_once()
@patch('requests.Session.get')
def test_request_with_headers(self, mock_get):
"""测试带自定义请求头的请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_get.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.request(HTTPMethod.GET, "/api/test", headers={"X-Custom": "value"})
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
mock_get.assert_called_once()
@patch('requests.Session.get')
def test_request_retry_on_timeout(self, mock_get):
"""测试超时重试"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_get.side_effect = [
requests.Timeout(),
requests.Timeout(),
mock_response
]
client = APIClient("http://localhost:8080", max_retries=3)
result = client.request(HTTPMethod.GET, "/api/test")
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
assert mock_get.call_count == 3
@patch('requests.Session.get')
def test_request_failure_after_retries(self, mock_get):
"""测试重试后仍然失败"""
mock_get.side_effect = requests.Timeout()
client = APIClient("http://localhost:8080", max_retries=2)
with pytest.raises(RequestException):
client.request(HTTPMethod.GET, "/api/test")
@patch('requests.Session.get')
def test_request_invalid_json(self, mock_get):
"""测试无效JSON响应"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.side_effect = ValueError("Invalid JSON")
mock_response.text = "plain text"
mock_response.headers = {"Content-Type": "text/plain"}
mock_get.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.request(HTTPMethod.GET, "/api/test")
assert result["status_code"] == 200
assert result["response_body"] == "plain text"
@patch('requests.Session.get')
def test_get(self, mock_get):
"""测试GET请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_get.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.get("/api/test")
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
@patch('requests.Session.post')
def test_post(self, mock_post):
"""测试POST请求"""
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_post.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.post("/api/test", {"name": "test"})
assert result["status_code"] == 201
assert result["response_body"] == {"success": True}
@patch('requests.Session.put')
def test_put(self, mock_put):
"""测试PUT请求"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"success": True}
mock_response.headers = {"Content-Type": "application/json"}
mock_put.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.put("/api/test/1", {"name": "updated"})
assert result["status_code"] == 200
assert result["response_body"] == {"success": True}
@patch('requests.Session.delete')
def test_delete(self, mock_delete):
"""测试DELETE请求"""
mock_response = Mock()
mock_response.status_code = 204
mock_response.headers = {"Content-Type": "application/json"}
mock_delete.return_value = mock_response
client = APIClient("http://localhost:8080")
result = client.delete("/api/test/1")
assert result["status_code"] == 204
def test_close(self):
"""测试关闭客户端"""
client = APIClient("http://localhost:8080")
session = client._session
client.close()
assert client._session == session
@@ -0,0 +1,370 @@
"""测试认证管理器"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from apitest.client.auth_manager import AuthManager
from apitest.models.exceptions import AuthException
import time
class TestAuthManager:
"""测试AuthManager类"""
def test_init(self):
"""测试初始化"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
assert auth_manager.base_url == "http://localhost:8080"
assert auth_manager.credentials == credentials
assert auth_manager.logger == logger
assert auth_manager._token is None
assert auth_manager._refresh_token is None
assert auth_manager._token_expiry is None
assert auth_manager._login_endpoint == "/sys/auth/login"
def test_set_login_endpoint(self):
"""测试设置登录端点"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_login_endpoint("/api/custom/login")
assert auth_manager._login_endpoint == "/api/custom/login"
def test_set_credentials(self):
"""测试设置认证凭据"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_credentials("newuser", "newpassword")
assert auth_manager.credentials == {"username": "newuser", "password": "newpassword"}
def test_set_token(self):
"""测试设置token"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_token("test-token", 3600)
assert auth_manager._token == "test-token"
assert auth_manager._token_expiry is not None
def test_get_token(self):
"""测试获取token"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._token = "test-token"
assert auth_manager.get_token() == "test-token"
def test_get_token_none(self):
"""测试获取token(未设置)"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
assert auth_manager.get_token() is None
def test_is_token_valid(self):
"""测试token有效性检查"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_token("test-token", 3600)
assert auth_manager.is_token_valid() is True
def test_is_token_valid_expired(self):
"""测试token有效性检查(已过期)"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_token("test-token", -1)
assert auth_manager.is_token_valid() is False
def test_is_token_valid_none(self):
"""测试token有效性检查(未设置)"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
assert auth_manager.is_token_valid() is False
@patch('requests.post')
def test_login_success(self, mock_post):
"""测试成功登录"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {
"token": "test-token",
"refreshToken": "refresh-token",
"expiresIn": 3600
}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
result = auth_manager.login()
assert result["data"]["token"] == "test-token"
assert auth_manager._token == "test-token"
assert auth_manager._refresh_token == "refresh-token"
assert auth_manager._token_expiry is not None
@patch('requests.post')
def test_login_failure(self, mock_post):
"""测试登录失败"""
mock_response = Mock()
mock_response.status_code = 401
mock_response.json.return_value = {
"data": {"error": "Invalid credentials"}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "wrong-password"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
with pytest.raises(AuthException):
auth_manager.login()
@patch('requests.post')
def test_login_no_token_in_response(self, mock_post):
"""测试登录(响应中无token"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {"message": "success"}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
with pytest.raises(AuthException):
auth_manager.login()
@patch('requests.post')
def test_login_http_error(self, mock_post):
"""测试登录(HTTP错误)"""
import requests
mock_post.side_effect = requests.RequestException("Network error")
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
with pytest.raises(AuthException):
auth_manager.login()
@patch('requests.post')
def test_refresh_token_success(self, mock_post):
"""测试成功刷新token"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {
"token": "new-token",
"refreshToken": "new-refresh-token",
"expiresIn": 3600
}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._refresh_token = "old-refresh-token"
result = auth_manager.refresh_token()
assert result is True
assert auth_manager._token == "new-token"
assert auth_manager._refresh_token == "new-refresh-token"
@patch('requests.post')
def test_refresh_token_failure(self, mock_post):
"""测试刷新token失败"""
mock_response = Mock()
mock_response.status_code = 401
mock_response.json.return_value = {
"data": {"error": "Invalid refresh token"}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._refresh_token = "old-refresh-token"
result = auth_manager.refresh_token()
assert result is False
def test_refresh_token_no_refresh_token(self):
"""测试刷新token(无refresh token"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
result = auth_manager.refresh_token()
assert result is False
@patch('requests.post')
def test_refresh_token_exception(self, mock_post):
"""测试刷新token异常"""
mock_post.side_effect = Exception("Network error")
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._refresh_token = "old-refresh-token"
result = auth_manager.refresh_token()
assert result is False
def test_logout(self):
"""测试登出"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._token = "test-token"
auth_manager._refresh_token = "refresh-token"
auth_manager._token_expiry = time.time() + 3600
auth_manager.logout()
assert auth_manager._token is None
assert auth_manager._refresh_token is None
assert auth_manager._token_expiry is None
@patch('requests.post')
def test_ensure_authenticated_with_valid_token(self, mock_post):
"""测试确保已认证(有效token"""
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager.set_token("test-token", 3600)
token = auth_manager.ensure_authenticated()
assert token == "test-token"
assert mock_post.call_count == 0
@patch('requests.post')
def test_ensure_authenticated_with_expired_token(self, mock_post):
"""测试确保已认证(过期token,刷新成功)"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {
"token": "new-token",
"refreshToken": "new-refresh-token",
"expiresIn": 3600
}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._token = "old-token"
auth_manager._refresh_token = "old-refresh-token"
from datetime import datetime, timedelta
auth_manager._token_expiry = datetime.now() - timedelta(seconds=100)
token = auth_manager.ensure_authenticated()
assert token == "new-token"
assert auth_manager._token == "new-token"
assert auth_manager._refresh_token == "new-refresh-token"
@patch('requests.post')
def test_ensure_authenticated_no_token(self, mock_post):
"""测试确保已认证(无token,登录成功)"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {
"token": "test-token",
"refreshToken": "refresh-token",
"expiresIn": 3600
}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
token = auth_manager.ensure_authenticated()
assert token == "test-token"
@patch('requests.post')
def test_ensure_authenticated_refresh_failed_login(self, mock_post):
"""测试确保已认证(刷新失败,重新登录)"""
refresh_response = Mock()
refresh_response.status_code = 401
refresh_response.json.return_value = {
"data": {"error": "Invalid refresh token"}
}
login_response = Mock()
login_response.status_code = 200
login_response.json.return_value = {
"data": {
"token": "test-token",
"refreshToken": "refresh-token",
"expiresIn": 3600
}
}
mock_post.side_effect = [refresh_response, login_response]
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
auth_manager._token = "old-token"
auth_manager._refresh_token = "old-refresh-token"
from datetime import datetime, timedelta
auth_manager._token_expiry = datetime.now() - timedelta(seconds=100)
token = auth_manager.ensure_authenticated()
assert token == "test-token"
assert auth_manager._token == "test-token"
@patch('requests.post')
def test_ensure_authenticated_all_failed(self, mock_post):
"""测试确保已认证(全部失败)"""
mock_response = Mock()
mock_response.status_code = 401
mock_response.json.return_value = {
"data": {"error": "Authentication failed"}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
with pytest.raises(AuthException):
auth_manager.ensure_authenticated()
@patch('requests.post')
def test_get_auth_headers(self, mock_post):
"""测试获取认证请求头"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"data": {
"token": "test-token",
"refreshToken": "refresh-token",
"expiresIn": 3600
}
}
mock_post.return_value = mock_response
logger = Mock()
credentials = {"username": "admin", "password": "password123"}
auth_manager = AuthManager("http://localhost:8080", credentials, logger)
headers = auth_manager.get_auth_headers()
assert headers["Authorization"] == "Bearer test-token"
assert headers["Content-Type"] == "application/json"
@@ -0,0 +1,383 @@
"""CLI接口单元测试"""
import pytest
from click.testing import CliRunner
from pathlib import Path
import json
import tempfile
from apitest.cli_module import cli
@pytest.fixture
def runner():
"""创建CLI测试运行器"""
return CliRunner()
@pytest.fixture
def sample_test_cases(tmp_path):
"""创建示例测试用例文件"""
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "GET",
"headers": {},
"enabled": True
},
{
"id": "TC002",
"name": "测试用例2",
"description": "测试用例2",
"module": "user",
"endpoint": "/api/user",
"method": "POST",
"headers": {},
"enabled": True,
"tags": ["smoke", "regression"],
"priority": 1
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
return test_file
def test_cli_version(runner):
"""测试CLI版本命令"""
result = runner.invoke(cli, ["--version"])
assert result.exit_code == 0
assert "1.0.0" in result.output
def test_cli_help(runner):
"""测试CLI帮助命令"""
result = runner.invoke(cli, ["--help"])
assert result.exit_code == 0
assert "黑盒API测试工具" in result.output
def test_list_command(runner, sample_test_cases):
"""测试列出测试用例命令"""
result = runner.invoke(cli, ["list", str(sample_test_cases)])
assert result.exit_code == 0
assert "测试用例总数: 2" in result.output
assert "TC001" in result.output
assert "TC002" in result.output
def test_list_command_with_module_filter(runner, sample_test_cases):
"""测试列出测试用例命令(模块过滤)"""
result = runner.invoke(cli, ["list", str(sample_test_cases), "--module", "test"])
assert result.exit_code == 0
assert "TC001" in result.output
assert "TC002" not in result.output
def test_list_command_with_tag_filter(runner, sample_test_cases):
"""测试列出测试用例命令(标签过滤)"""
result = runner.invoke(cli, ["list", str(sample_test_cases), "--tag", "smoke"])
assert result.exit_code == 0
assert "TC001" not in result.output
assert "TC002" in result.output
def test_list_command_with_priority_filter(runner, sample_test_cases):
"""测试列出测试用例命令(优先级过滤)"""
result = runner.invoke(cli, ["list", str(sample_test_cases), "--priority", "1"])
assert result.exit_code == 0
assert "TC001" not in result.output
assert "TC002" in result.output
def test_validate_command(runner, sample_test_cases):
"""测试验证测试用例命令"""
result = runner.invoke(cli, ["validate", str(sample_test_cases)])
assert result.exit_code == 0
assert "验证通过" in result.output
def test_validate_command_invalid_file(runner, tmp_path):
"""测试验证测试用例命令(无效文件)"""
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code != 0
def test_config_command(runner):
"""测试配置命令"""
result = runner.invoke(cli, ["config"])
assert result.exit_code == 0
assert "当前配置" in result.output
def test_config_command_with_key(runner):
"""测试配置命令(指定键)"""
result = runner.invoke(cli, ["config", "--key", "target.base_url"])
assert result.exit_code == 0
def test_run_command_no_test_cases(runner):
"""测试运行命令(无测试用例)"""
result = runner.invoke(cli, ["run"])
assert result.exit_code != 0
assert "请指定测试用例文件路径" in result.output or "错误:" in result.output
def test_run_command_help(runner):
"""测试运行命令帮助"""
result = runner.invoke(cli, ["run", "--help"])
assert result.exit_code == 0
assert "运行测试用例" in result.output
def test_list_command_help(runner):
"""测试列出命令帮助"""
result = runner.invoke(cli, ["list", "--help"])
assert result.exit_code == 0
assert "列出测试用例" in result.output
def test_validate_command_help(runner):
"""测试验证命令帮助"""
result = runner.invoke(cli, ["validate", "--help"])
assert result.exit_code == 0
assert "验证测试用例文件" in result.output
def test_config_command_help(runner):
"""测试配置命令帮助"""
result = runner.invoke(cli, ["config", "--help"])
assert result.exit_code == 0
assert "查看配置" in result.output
def test_run_command_with_verbose(runner, sample_test_cases):
"""测试运行命令(详细模式)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--verbose"])
assert result.exit_code in [0, 1]
def test_run_command_with_stop_on_failure(runner, sample_test_cases):
"""测试运行命令(失败时停止)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--stop-on-failure"])
assert result.exit_code in [0, 1]
def test_run_command_with_no_report(runner, sample_test_cases):
"""测试运行命令(不生成报告)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--no-report"])
assert result.exit_code in [0, 1]
def test_run_command_with_report_format_json(runner, sample_test_cases):
"""测试运行命令(JSON报告格式)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--report-format", "json"])
assert result.exit_code in [0, 1]
def test_run_command_with_report_path(runner, sample_test_cases, tmp_path):
"""测试运行命令(指定报告路径)"""
report_path = tmp_path / "report.html"
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--report-path", str(report_path)])
assert result.exit_code in [0, 1]
def test_run_command_with_module_filter(runner, sample_test_cases):
"""测试运行命令(模块过滤)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--module", "test"])
assert result.exit_code in [0, 1]
def test_run_command_with_tag_filter(runner, sample_test_cases):
"""测试运行命令(标签过滤)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--tag", "smoke"])
assert result.exit_code in [0, 1]
def test_run_command_with_priority_filter(runner, sample_test_cases):
"""测试运行命令(优先级过滤)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--priority", "1"])
assert result.exit_code in [0, 1]
def test_run_command_with_no_matching_cases(runner, sample_test_cases):
"""测试运行命令(无匹配测试用例)"""
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--module", "nonexistent"])
assert result.exit_code == 0
assert "没有匹配的测试用例" in result.output or "警告" in result.output
def test_run_command_with_test_data(runner, sample_test_cases, tmp_path):
"""测试运行命令(带测试数据)"""
test_data = [
{"username": "test1", "password": "pass1"},
{"username": "test2", "password": "pass2"}
]
test_data_file = tmp_path / "test_data.csv"
with open(test_data_file, "w", encoding="utf-8") as f:
f.write("username,password\n")
f.write("test1,pass1\n")
f.write("test2,pass2\n")
result = runner.invoke(cli, ["run", "--test-cases", str(sample_test_cases), "--test-data", str(test_data_file)])
assert result.exit_code in [0, 1]
def test_run_command_with_exception(runner, tmp_path):
"""测试运行命令(异常处理)"""
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
result = runner.invoke(cli, ["run", "--test-cases", str(invalid_file)])
assert result.exit_code == 1
assert "执行测试时出错" in result.output or "错误:" in result.output
def test_run_command_with_verbose_exception(runner, tmp_path):
"""测试运行命令(详细模式异常)"""
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
result = runner.invoke(cli, ["run", "--test-cases", str(invalid_file), "--verbose"])
assert result.exit_code == 1
def test_list_command_with_exception(runner, tmp_path):
"""测试列出命令(异常处理)"""
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
result = runner.invoke(cli, ["list", str(invalid_file)])
assert result.exit_code == 1
assert "列出测试用例时出错" in result.output or "错误:" in result.output
def test_validate_command_with_missing_fields(runner, tmp_path):
"""测试验证命令(缺少字段)"""
invalid_test_data = [
{
"id": "",
"name": "测试用例",
"endpoint": "/api/test",
"method": "GET"
}
]
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
json.dump(invalid_test_data, f)
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code == 1
assert "验证失败" in result.output
def test_validate_command_with_missing_id(runner, tmp_path):
"""测试验证命令(缺少ID"""
invalid_test_data = [
{
"name": "测试用例",
"endpoint": "/api/test",
"method": "GET"
}
]
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
json.dump(invalid_test_data, f)
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code == 1
def test_validate_command_with_missing_name(runner, tmp_path):
"""测试验证命令(缺少名称)"""
invalid_test_data = [
{
"id": "TC001",
"endpoint": "/api/test",
"method": "GET"
}
]
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
json.dump(invalid_test_data, f)
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code == 1
def test_validate_command_with_missing_endpoint(runner, tmp_path):
"""测试验证命令(缺少端点)"""
invalid_test_data = [
{
"id": "TC001",
"name": "测试用例",
"method": "GET"
}
]
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
json.dump(invalid_test_data, f)
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code == 1
def test_validate_command_with_exception(runner, tmp_path):
"""测试验证命令(异常处理)"""
invalid_file = tmp_path / "invalid.json"
with open(invalid_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
result = runner.invoke(cli, ["validate", str(invalid_file)])
assert result.exit_code == 1
assert "验证测试用例时出错" in result.output or "错误:" in result.output
def test_config_command_with_exception(runner):
"""测试配置命令(异常处理)"""
result = runner.invoke(cli, ["config", "--key", "nonexistent.key"])
assert result.exit_code in [0, 1]
def test_list_command_with_tags_and_dependencies(runner, tmp_path):
"""测试列出命令(显示标签和依赖)"""
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "GET",
"headers": {},
"enabled": True,
"tags": ["smoke", "regression"],
"dependencies": ["TC000"]
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
result = runner.invoke(cli, ["list", str(test_file)])
assert result.exit_code == 0
assert "标签" in result.output
assert "依赖" in result.output
@@ -0,0 +1,224 @@
import pytest
import yaml
import os
from pathlib import Path
from apitest.config.config_manager import ConfigManager
from apitest.models.exceptions import ConfigException
class TestConfigManager:
"""测试ConfigManager配置管理器"""
@pytest.fixture
def temp_config_file(self, tmp_path):
"""创建临时配置文件"""
config_data = {
"target": {
"base_url": "http://localhost:8080",
"timeout": 5000,
"max_retries": 3
},
"auth": {
"username": "test_user",
"password": "test_pass",
"login_endpoint": "/sys/auth/login"
},
"test": {
"data_dir": "data",
"test_cases_dir": "test_cases",
"parallel": True,
"parallel_threads": 4,
"retry_count": 2,
"stop_on_failure": False,
"max_response_time": 5000
},
"report": {
"output_dir": "reports",
"format": "html"
},
"logging": {
"level": "INFO",
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
"file": "logs/api_test.log"
}
}
config_file = tmp_path / "config.yaml"
with open(config_file, "w", encoding="utf-8") as f:
yaml.dump(config_data, f)
return config_file
def test_config_manager_initialization(self, temp_config_file):
"""测试配置管理器初始化"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.config_path == temp_config_file
assert config_manager._config is not None
def test_config_manager_nonexistent_file(self):
"""测试配置文件不存在"""
with pytest.raises(ConfigException, match="配置文件不存在"):
ConfigManager("/nonexistent/config.yaml")
def test_get_config_value(self, temp_config_file):
"""测试获取配置值"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get("target.base_url") == "http://localhost:8080"
assert config_manager.get("target.timeout") == 5000
assert config_manager.get("target.max_retries") == 3
def test_get_nested_config_value(self, temp_config_file):
"""测试获取嵌套配置值"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get("auth.username") == "test_user"
assert config_manager.get("auth.password") == "test_pass"
assert config_manager.get("auth.login_endpoint") == "/sys/auth/login"
def test_get_config_value_with_default(self, temp_config_file):
"""测试获取配置值(带默认值)"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get("nonexistent.key", "default_value") == "default_value"
assert config_manager.get("target.nonexistent", 0) == 0
def test_get_target_config(self, temp_config_file):
"""测试获取目标系统配置"""
config_manager = ConfigManager(str(temp_config_file))
target_config = config_manager.get_target_config()
assert target_config["base_url"] == "http://localhost:8080"
assert target_config["timeout"] == 5000
assert target_config["max_retries"] == 3
def test_get_auth_config(self, temp_config_file):
"""测试获取认证配置"""
config_manager = ConfigManager(str(temp_config_file))
auth_config = config_manager.get_auth_config()
assert auth_config["username"] == "test_user"
assert auth_config["password"] == "test_pass"
assert auth_config["login_endpoint"] == "/sys/auth/login"
def test_get_test_config(self, temp_config_file):
"""测试获取测试配置"""
config_manager = ConfigManager(str(temp_config_file))
test_config = config_manager.get_test_config()
assert test_config["data_dir"] == "data"
assert test_config["test_cases_dir"] == "test_cases"
assert test_config["parallel"] == True
assert test_config["parallel_threads"] == 4
def test_get_report_config(self, temp_config_file):
"""测试获取报告配置"""
config_manager = ConfigManager(str(temp_config_file))
report_config = config_manager.get_report_config()
assert report_config["output_dir"] == "reports"
assert report_config["format"] == "html"
def test_get_logging_config(self, temp_config_file):
"""测试获取日志配置"""
config_manager = ConfigManager(str(temp_config_file))
logging_config = config_manager.get_logging_config()
assert logging_config["level"] == "INFO"
assert logging_config["file"] == "logs/api_test.log"
def test_get_base_url(self, temp_config_file):
"""测试获取基础URL"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_base_url() == "http://localhost:8080"
def test_get_timeout(self, temp_config_file):
"""测试获取超时时间"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_timeout() == 5000
def test_get_max_retries(self, temp_config_file):
"""测试获取最大重试次数"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_max_retries() == 3
def test_get_login_endpoint(self, temp_config_file):
"""测试获取登录端点"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_login_endpoint() == "/sys/auth/login"
def test_is_parallel_enabled(self, temp_config_file):
"""测试是否启用并行执行"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.is_parallel_enabled() == True
def test_get_parallel_threads(self, temp_config_file):
"""测试获取并行线程数"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_parallel_threads() == 4
def test_get_retry_count(self, temp_config_file):
"""测试获取重试次数"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_retry_count() == 2
def test_should_stop_on_failure(self, temp_config_file):
"""测试是否在失败时停止"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.should_stop_on_failure() == False
def test_get_max_response_time(self, temp_config_file):
"""测试获取最大响应时间"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_max_response_time() == 5000
def test_get_report_format(self, temp_config_file):
"""测试获取报告格式"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_report_format() == "html"
def test_get_log_level(self, temp_config_file):
"""测试获取日志级别"""
config_manager = ConfigManager(str(temp_config_file))
assert config_manager.get_log_level() == "INFO"
def test_get_log_format(self, temp_config_file):
"""测试获取日志格式"""
config_manager = ConfigManager(str(temp_config_file))
expected_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
assert config_manager.get_log_format() == expected_format
def test_get_log_file(self, temp_config_file):
"""测试获取日志文件路径"""
config_manager = ConfigManager(str(temp_config_file))
log_file = config_manager.get_log_file()
assert log_file is not None
assert log_file.name == "api_test.log"
def test_get_auth_credentials_with_env(self, temp_config_file, monkeypatch):
"""测试获取认证凭据(环境变量)"""
monkeypatch.setenv("TEST_USERNAME", "env_user")
monkeypatch.setenv("TEST_PASSWORD", "env_pass")
config_manager = ConfigManager(str(temp_config_file))
credentials = config_manager.get_auth_credentials()
assert credentials["username"] == "env_user"
assert credentials["password"] == "env_pass"
def test_reload_config(self, temp_config_file):
"""测试重新加载配置"""
config_manager = ConfigManager(str(temp_config_file))
original_url = config_manager.get_base_url()
assert original_url == "http://localhost:8080"
with open(temp_config_file, "r+", encoding="utf-8") as f:
config_data = yaml.safe_load(f)
config_data["target"]["base_url"] = "http://new-url:8080"
f.seek(0)
yaml.dump(config_data, f)
f.truncate()
config_manager.reload()
assert config_manager.get_base_url() == "http://new-url:8080"
@@ -0,0 +1,137 @@
import pytest
import logging
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock
from apitest.config.config_manager import ConfigManager
from apitest.config.logger_manager import LoggerManager
class TestLoggerManager:
"""测试LoggerManager日志管理器"""
@pytest.fixture
def mock_config_manager(self):
"""模拟配置管理器"""
config_manager = MagicMock(spec=ConfigManager)
config_manager.get_log_level.return_value = "INFO"
config_manager.get_log_format.return_value = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
config_manager.get_log_file.return_value = None
return config_manager
@pytest.fixture
def logger_manager(self, mock_config_manager):
"""创建日志管理器实例"""
return LoggerManager(mock_config_manager)
def test_logger_manager_initialization(self, mock_config_manager):
"""测试日志管理器初始化"""
logger_manager = LoggerManager(mock_config_manager)
assert logger_manager.config_manager == mock_config_manager
assert isinstance(logger_manager._loggers, dict)
@pytest.fixture
def mock_config_manager_with_file(self, tmp_path):
"""模拟带日志文件的配置管理器"""
config_manager = MagicMock(spec=ConfigManager)
config_manager.get_log_level.return_value = "DEBUG"
config_manager.get_log_format.return_value = "%(name)s - %(levelname)s - %(message)s"
config_manager.get_log_file.return_value = tmp_path / "test.log"
return config_manager
def test_logger_manager_with_file(self, mock_config_manager_with_file):
"""测试带日志文件的日志管理器初始化"""
logger_manager = LoggerManager(mock_config_manager_with_file)
assert logger_manager.config_manager == mock_config_manager_with_file
def test_get_logger(self, logger_manager):
"""测试获取日志记录器"""
logger = logger_manager.get_logger("test_logger")
assert isinstance(logger, logging.Logger)
assert logger.name == "test_logger"
assert "test_logger" in logger_manager._loggers
def test_get_logger_cached(self, logger_manager):
"""测试获取缓存的日志记录器"""
logger1 = logger_manager.get_logger("test_logger")
logger2 = logger_manager.get_logger("test_logger")
assert logger1 is logger2
def test_set_level(self, logger_manager):
"""测试设置日志级别"""
logger_manager.set_level("DEBUG")
root_logger = logging.getLogger()
assert root_logger.level == logging.DEBUG
def test_add_file_handler(self, logger_manager, tmp_path):
"""测试添加文件处理器"""
log_file = tmp_path / "test_add_handler.log"
initial_handler_count = len([h for h in logging.getLogger().handlers if isinstance(h, logging.FileHandler)])
logger_manager.add_file_handler(log_file)
root_logger = logging.getLogger()
file_handlers = [h for h in root_logger.handlers if isinstance(h, logging.FileHandler)]
assert len(file_handlers) > initial_handler_count
new_handlers = file_handlers[initial_handler_count:]
assert len(new_handlers) > 0
assert str(log_file) in new_handlers[0].baseFilename
def test_add_file_handler_with_level(self, logger_manager, tmp_path):
"""测试添加带级别的文件处理器"""
log_file = tmp_path / "test_add_handler_level.log"
initial_handler_count = len([h for h in logging.getLogger().handlers if isinstance(h, logging.FileHandler)])
logger_manager.add_file_handler(log_file, "ERROR")
root_logger = logging.getLogger()
file_handlers = [h for h in root_logger.handlers if isinstance(h, logging.FileHandler)]
assert len(file_handlers) > initial_handler_count
new_handlers = file_handlers[initial_handler_count:]
assert len(new_handlers) > 0
assert new_handlers[0].level == logging.ERROR
def test_remove_all_handlers(self, logger_manager):
"""测试移除所有处理器"""
original_handler_count = len(logging.getLogger().handlers)
logger_manager.remove_all_handlers()
assert len(logging.getLogger().handlers) == 0
@patch('apitest.config.logger_manager.setup_logger')
def test_setup_logger_function(self, mock_setup):
"""测试setup_logger函数"""
from apitest.config.logger_manager import setup_logger
mock_config = MagicMock(spec=ConfigManager)
setup_logger(mock_config)
mock_setup.assert_called_once_with(mock_config)
class TestSetupLoggerIntegration:
"""测试setup_logger集成"""
def test_setup_logger_creates_logger_manager(self):
"""测试setup_logger创建日志管理器"""
config_data = {
"logging": {
"level": "INFO",
"format": "%(name)s - %(levelname)s - %(message)s",
"file": None
}
}
with patch('apitest.config.config_manager.ConfigManager') as mock_config_class:
mock_config = MagicMock(spec=ConfigManager)
mock_config.get_log_level.return_value = "INFO"
mock_config.get_log_format.return_value = "%(name)s - %(levelname)s - %(message)s"
mock_config.get_log_file.return_value = None
mock_config_class.return_value = mock_config
from apitest.config.logger_manager import setup_logger
logger_manager = setup_logger(mock_config)
assert isinstance(logger_manager, LoggerManager)
assert logger_manager.config_manager == mock_config
@@ -0,0 +1,306 @@
import pytest
from click.testing import CliRunner
from unittest.mock import patch, MagicMock
from apitest.main import cli
import sys
def test_cli_version():
"""测试版本信息"""
runner = CliRunner()
result = runner.invoke(cli, ['--version'])
assert result.exit_code == 0
assert '1.0.0' in result.output
def test_cli_with_no_command():
"""测试无命令时显示帮助信息"""
runner = CliRunner()
result = runner.invoke(cli, [])
assert result.exit_code == 0
assert '黑盒API测试工具' in result.output
def test_cli_run_with_valid_options():
"""测试有效命令执行"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_report.return_value = MagicMock()
mock_results = MagicMock()
mock_results.passed = 10
mock_results.failed = 0
mock_results.skipped = 0
mock_orchestrator.return_value.run_suite.return_value = mock_results
result = runner.invoke(cli, ['run', '--suite', 'test-suite'])
assert result.exit_code == 0
assert mock_orchestrator.return_value.run_suite.called
def test_cli_run_with_filter():
"""测试带过滤器的运行命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_report.return_value = MagicMock()
mock_results = MagicMock()
mock_results.passed = 5
mock_results.failed = 0
mock_results.skipped = 0
mock_orchestrator.return_value.run_suite.return_value = mock_results
result = runner.invoke(cli, ['run', '--filter', 'priority=high'])
assert result.exit_code == 0
def test_cli_run_with_parallel():
"""测试并发执行"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_report.return_value = MagicMock()
mock_results = MagicMock()
mock_results.passed = 10
mock_results.failed = 0
mock_results.skipped = 0
mock_orchestrator.return_value.run_suite.return_value = mock_results
result = runner.invoke(cli, ['run', '--parallel', '--threads', '8'])
assert result.exit_code == 0
def test_cli_run_with_failed_tests():
"""测试有失败测试的情况"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_report.return_value = MagicMock()
mock_results = MagicMock()
mock_results.passed = 5
mock_results.failed = 2
mock_results.skipped = 0
mock_orchestrator.return_value.run_suite.return_value = mock_results
result = runner.invoke(cli, ['run'])
assert result.exit_code == 1
def test_cli_run_with_exception():
"""测试异常处理"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.side_effect = Exception("Config error")
result = runner.invoke(cli, ['run'])
assert result.exit_code == 1
assert '执行测试时出错' in result.output
def test_cli_list_command():
"""测试list命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_test_case = MagicMock()
mock_test_case.id = 'test-001'
mock_test_case.name = 'Test Case 1'
mock_test_case.module = 'user'
mock_test_case.method.value = 'GET'
mock_test_case.endpoint = '/api/user'
mock_test_case.priority = 'high'
mock_test_case.enabled = True
mock_orchestrator.return_value.list_test_cases.return_value = [mock_test_case]
result = runner.invoke(cli, ['list'])
assert result.exit_code == 0
assert '测试套件' in result.output
assert 'test-001' in result.output
def test_cli_list_with_filter():
"""测试带过滤器的list命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.TestOrchestrator') as mock_orchestrator:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_orchestrator.return_value = MagicMock()
mock_orchestrator.return_value.list_test_cases.return_value = []
result = runner.invoke(cli, ['list', '--filter', 'priority=high'])
assert result.exit_code == 0
def test_cli_list_with_exception():
"""测试list命令异常处理"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.side_effect = Exception("List error")
result = runner.invoke(cli, ['list'])
assert result.exit_code == 1
assert '列出测试用例时出错' in result.output
def test_cli_report_command():
"""测试report命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_report.return_value = MagicMock()
result = runner.invoke(cli, ['report'])
assert result.exit_code == 0
assert '报告已生成' in result.output
def test_cli_report_with_output():
"""测试带输出路径的report命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
with patch('apitest.main.LoggerManager') as mock_logger:
with patch('apitest.main.ReportManager') as mock_report:
mock_config.return_value = MagicMock()
mock_logger.return_value = MagicMock()
mock_report.return_value = MagicMock()
result = runner.invoke(cli, ['report', '--output', '/tmp/report.html'])
assert result.exit_code == 0
def test_cli_report_with_exception():
"""测试report命令异常处理"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.side_effect = Exception("Report error")
result = runner.invoke(cli, ['report'])
assert result.exit_code == 1
assert '生成报告时出错' in result.output
def test_cli_config_get():
"""测试config get命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.return_value = MagicMock()
mock_config.return_value.get.return_value = 'test-value'
result = runner.invoke(cli, ['config', '--get', 'test-key'])
assert result.exit_code == 0
assert 'test-key = test-value' in result.output
def test_cli_config_set():
"""测试config set命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.return_value = MagicMock()
result = runner.invoke(cli, ['config', '--set', 'test.key=test-value'])
assert result.exit_code == 0
assert '配置已设置' in result.output
mock_config.return_value.set.assert_called_once_with('test.key', 'test-value')
def test_cli_config_validate():
"""测试config validate命令"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.return_value = MagicMock()
mock_config.return_value.validate.return_value = (True, [])
result = runner.invoke(cli, ['config', '--validate'])
assert result.exit_code == 0
assert '配置验证通过' in result.output
def test_cli_config_validate_with_errors():
"""测试config validate命令带错误"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.return_value = MagicMock()
mock_config.return_value.validate.return_value = (False, ['Error 1', 'Error 2'])
result = runner.invoke(cli, ['config', '--validate'])
assert result.exit_code == 1
assert '配置验证失败' in result.output
def test_cli_config_with_exception():
"""测试config命令异常处理"""
runner = CliRunner()
with patch('apitest.main.ConfigManager') as mock_config:
mock_config.side_effect = Exception("Config error")
result = runner.invoke(cli, ['config'])
assert result.exit_code == 1
assert '配置管理时出错' in result.output
@@ -0,0 +1,378 @@
import pytest
from datetime import datetime
from apitest.models.test_models import (
HTTPMethod,
ValidationRule,
TestCase,
PerformanceMetrics,
TestResult,
TestSuiteResult
)
from apitest.models.exceptions import (
APITestException,
ConfigException,
DataException,
AuthException,
RequestException,
ValidationException,
TestRunException,
ReportException
)
class TestHTTPMethod:
"""测试HTTPMethod枚举"""
def test_http_methods(self):
"""测试所有HTTP方法"""
assert HTTPMethod.GET.value == "GET"
assert HTTPMethod.POST.value == "POST"
assert HTTPMethod.PUT.value == "PUT"
assert HTTPMethod.DELETE.value == "DELETE"
assert HTTPMethod.PATCH.value == "PATCH"
assert HTTPMethod.HEAD.value == "HEAD"
assert HTTPMethod.OPTIONS.value == "OPTIONS"
class TestValidationRule:
"""测试ValidationRule数据类"""
def test_validation_rule_creation(self):
"""测试创建验证规则"""
rule = ValidationRule(
type="status_code",
expected=200,
message="状态码应为200"
)
assert rule.type == "status_code"
assert rule.expected == 200
assert rule.message == "状态码应为200"
assert rule.json_path is None
def test_validation_rule_with_json_path(self):
"""测试带JSON路径的验证规则"""
rule = ValidationRule(
type="json_path",
expected="success",
json_path="$.status",
message="状态应为success"
)
assert rule.json_path == "$.status"
def test_validation_rule_immutability(self):
"""测试验证规则的不可变性"""
rule = ValidationRule(type="status_code", expected=200)
with pytest.raises(Exception):
rule.expected = 201
class TestTestCaseModel:
"""测试TestCase数据类"""
def test_test_case_creation(self):
"""测试创建测试用例"""
test_case = TestCase(
id="TC001",
name="测试用户登录",
description="验证用户登录功能",
module="user",
endpoint="/api/auth/login",
method=HTTPMethod.POST,
headers={"Content-Type": "application/json"},
body={"username": "admin", "password": "admin123"}
)
assert test_case.id == "TC001"
assert test_case.name == "测试用户登录"
assert test_case.method == HTTPMethod.POST
assert test_case.auth_required == True
assert test_case.enabled == True
assert test_case.dependencies == []
assert test_case.validations == []
assert test_case.tags == []
def test_test_case_with_dependencies(self):
"""测试带依赖的测试用例"""
test_case = TestCase(
id="TC002",
name="测试获取用户信息",
description="验证获取用户信息功能",
module="user",
endpoint="/api/user/info",
method=HTTPMethod.GET,
headers={"Content-Type": "application/json"},
dependencies=["TC001"]
)
assert len(test_case.dependencies) == 1
assert "TC001" in test_case.dependencies
def test_test_case_with_validations(self):
"""测试带验证规则的测试用例"""
test_case = TestCase(
id="TC003",
name="测试创建用户",
description="验证创建用户功能",
module="user",
endpoint="/api/user",
method=HTTPMethod.POST,
headers={"Content-Type": "application/json"},
body={"username": "test", "password": "test123"},
validations=[
{"type": "status_code", "expected": 201},
{"type": "json_path", "json_path": "$.success", "expected": True}
]
)
assert len(test_case.validations) == 2
assert test_case.validations[0]["type"] == "status_code"
def test_test_case_immutability(self):
"""测试测试用例的不可变性"""
test_case = TestCase(
id="TC004",
name="测试用例",
description="测试",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={}
)
with pytest.raises(Exception):
test_case.name = "修改后的名称"
class TestPerformanceMetrics:
"""测试PerformanceMetrics数据类"""
def test_performance_metrics_creation(self):
"""测试创建性能指标"""
metrics = PerformanceMetrics(
response_time=500,
request_size=100,
response_size=200,
timestamp=datetime.now()
)
assert metrics.response_time == 500
assert metrics.request_size == 100
assert metrics.response_size == 200
def test_performance_metrics_to_dict(self):
"""测试性能指标转换为字典"""
timestamp = datetime.now()
metrics = PerformanceMetrics(
response_time=500,
request_size=100,
response_size=200,
timestamp=timestamp
)
result = metrics.to_dict()
assert result["response_time"] == 500
assert result["request_size"] == 100
assert result["response_size"] == 200
assert result["timestamp"] == timestamp.isoformat()
class TestTestResultModel:
"""测试TestResult数据类"""
def test_test_result_creation(self):
"""测试创建测试结果"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={}
)
result = TestResult(
test_case=test_case,
passed=True,
status_code=200,
response_body={"success": True},
response_headers={"Content-Type": "application/json"}
)
assert result.passed == True
assert result.status_code == 200
assert result.execution_time == 0.0
assert result.retry_count == 0
assert result.timestamp is not None
def test_test_result_with_performance(self):
"""测试带性能指标的测试结果"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={}
)
performance = PerformanceMetrics(
response_time=500,
request_size=100,
response_size=200,
timestamp=datetime.now()
)
result = TestResult(
test_case=test_case,
passed=True,
status_code=200,
response_body={"success": True},
response_headers={},
performance=performance,
execution_time=0.5
)
assert result.performance == performance
assert result.execution_time == 0.5
def test_test_result_to_dict(self):
"""测试测试结果转换为字典"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={}
)
result = TestResult(
test_case=test_case,
passed=True,
status_code=200,
response_body={"success": True},
response_headers={}
)
result_dict = result.to_dict()
assert result_dict["test_case_id"] == "TC001"
assert result_dict["test_case_name"] == "测试用例"
assert result_dict["passed"] == True
assert result_dict["status_code"] == 200
class TestTestSuiteResultModel:
"""测试TestSuiteResult数据类"""
def test_test_suite_result_creation(self):
"""测试创建测试套件结果"""
suite_result = TestSuiteResult(
suite_name="user",
total=10,
passed=8,
failed=1,
skipped=1,
results=[],
start_time=datetime.now()
)
assert suite_result.suite_name == "user"
assert suite_result.total == 10
assert suite_result.passed == 8
assert suite_result.failed == 1
assert suite_result.skipped == 1
assert suite_result.end_time is None
def test_test_suite_result_duration(self):
"""测试测试套件执行时长"""
start_time = datetime.now()
end_time = datetime.now()
suite_result = TestSuiteResult(
suite_name="user",
total=10,
passed=8,
failed=1,
skipped=1,
results=[],
start_time=start_time,
end_time=end_time
)
assert suite_result.duration >= 0
def test_test_suite_result_pass_rate(self):
"""测试测试套件通过率"""
suite_result = TestSuiteResult(
suite_name="user",
total=10,
passed=8,
failed=1,
skipped=1,
results=[],
start_time=datetime.now()
)
assert suite_result.pass_rate == 80.0
def test_test_suite_result_pass_rate_zero_total(self):
"""测试总数为0时的通过率"""
suite_result = TestSuiteResult(
suite_name="user",
total=0,
passed=0,
failed=0,
skipped=0,
results=[],
start_time=datetime.now()
)
assert suite_result.pass_rate == 0.0
def test_test_suite_result_to_dict(self):
"""测试测试套件结果转换为字典"""
suite_result = TestSuiteResult(
suite_name="user",
total=10,
passed=8,
failed=1,
skipped=1,
results=[],
start_time=datetime.now()
)
result_dict = suite_result.to_dict()
assert result_dict["suite_name"] == "user"
assert result_dict["total"] == 10
assert result_dict["passed"] == 8
assert result_dict["failed"] == 1
assert result_dict["skipped"] == 1
assert result_dict["pass_rate"] == 80.0
class TestExceptions:
"""测试异常类"""
def test_api_test_exception(self):
"""测试API测试基础异常"""
with pytest.raises(APITestException):
raise APITestException("测试异常")
def test_config_exception(self):
"""测试配置异常"""
with pytest.raises(ConfigException):
raise ConfigException("配置错误")
def test_data_exception(self):
"""测试数据异常"""
with pytest.raises(DataException):
raise DataException("数据错误")
def test_auth_exception(self):
"""测试认证异常"""
with pytest.raises(AuthException):
raise AuthException("认证失败")
def test_request_exception(self):
"""测试请求异常"""
with pytest.raises(RequestException):
raise RequestException("请求失败")
def test_validation_exception(self):
"""测试验证异常"""
with pytest.raises(ValidationException):
raise ValidationException("验证失败")
def test_test_execution_exception(self):
"""测试测试执行异常"""
with pytest.raises(TestRunException):
raise TestRunException("执行失败")
def test_report_exception(self):
"""测试报告生成异常"""
with pytest.raises(ReportException):
raise ReportException("报告生成失败")
@@ -0,0 +1,355 @@
"""报告管理器单元测试"""
import pytest
from pathlib import Path
from datetime import datetime
from unittest.mock import Mock, patch
from apitest.report.report_manager import ReportManager
from apitest.models.test_models import TestCase, TestResult, TestSuiteResult, HTTPMethod, PerformanceMetrics
from apitest.models.exceptions import ReportException
@pytest.fixture
def report_manager():
"""创建报告管理器实例"""
logger = Mock()
return ReportManager(logger)
@pytest.fixture
def test_suite_result():
"""创建测试套件结果"""
test_cases = [
TestCase(
id="TC001",
name="测试用例1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=True
),
TestCase(
id="TC002",
name="测试用例2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.POST,
headers={},
enabled=True
)
]
results = [
TestResult(
test_case=test_cases[0],
passed=True,
status_code=200,
response_body={"message": "success"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=123,
request_size=0,
response_size=0
)
),
TestResult(
test_case=test_cases[1],
passed=False,
status_code=500,
response_body={"error": "internal error"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=456,
request_size=0,
response_size=0
),
error_message="服务器内部错误"
)
]
return TestSuiteResult(
suite_name="Test Suite",
total=2,
passed=1,
failed=1,
skipped=0,
results=results,
start_time=datetime.now()
)
def test_generate_html_report_success(report_manager, test_suite_result, tmp_path):
"""测试生成HTML报告(成功)"""
report_path = tmp_path / "test_report.html"
result_path = report_manager.generate_html_report(
test_suite_result,
report_path,
title="测试报告"
)
assert result_path == str(report_path)
assert report_path.exists()
content = report_path.read_text(encoding="utf-8")
assert "测试报告" in content
assert "Test Suite" in content
assert "TC001" in content
assert "TC002" in content
assert "测试用例1" in content
assert "测试用例2" in content
assert "50.0%" in content or "50.0" in content
def test_generate_html_report_create_directory(report_manager, test_suite_result, tmp_path):
"""测试生成HTML报告(创建目录)"""
report_path = tmp_path / "reports" / "nested" / "test_report.html"
result_path = report_manager.generate_html_report(
test_suite_result,
report_path
)
assert result_path == str(report_path)
assert report_path.exists()
assert report_path.parent.exists()
def test_generate_json_report_success(report_manager, test_suite_result, tmp_path):
"""测试生成JSON报告(成功)"""
report_path = tmp_path / "test_report.json"
result_path = report_manager.generate_json_report(
test_suite_result,
report_path
)
assert result_path == str(report_path)
assert report_path.exists()
import json
content = report_path.read_text(encoding="utf-8")
data = json.loads(content)
assert data["suite_name"] == "Test Suite"
assert data["total"] == 2
assert data["passed"] == 1
assert data["failed"] == 1
assert data["skipped"] == 0
assert len(data["results"]) == 2
def test_generate_json_report_create_directory(report_manager, test_suite_result, tmp_path):
"""测试生成JSON报告(创建目录)"""
report_path = tmp_path / "reports" / "nested" / "test_report.json"
result_path = report_manager.generate_json_report(
test_suite_result,
report_path
)
assert result_path == str(report_path)
assert report_path.exists()
assert report_path.parent.exists()
def test_generate_html_report_logger_call(report_manager, test_suite_result, tmp_path):
"""测试生成HTML报告时调用日志记录器"""
report_path = tmp_path / "test_report.html"
report_manager.generate_html_report(
test_suite_result,
report_path
)
report_manager.logger.info.assert_called()
def test_generate_json_report_logger_call(report_manager, test_suite_result, tmp_path):
"""测试生成JSON报告时调用日志记录器"""
report_path = tmp_path / "test_report.json"
report_manager.generate_json_report(
test_suite_result,
report_path
)
report_manager.logger.info.assert_called()
def test_generate_html_report_content_structure(report_manager, test_suite_result, tmp_path):
"""测试HTML报告内容结构"""
report_path = tmp_path / "test_report.html"
report_manager.generate_html_report(
test_suite_result,
report_path,
title="API测试报告"
)
content = report_path.read_text(encoding="utf-8")
assert "<!DOCTYPE html>" in content
assert "<html" in content
assert "<head>" in content
assert "<body>" in content
assert "API测试报告" in content
assert "总用例数" in content
assert "通过" in content
assert "失败" in content
assert "跳过" in content
assert "通过率" in content
assert "执行时长" in content
assert "测试结果详情" in content
assert "</html>" in content
def test_generate_html_report_with_all_passed(report_manager, tmp_path):
"""测试生成HTML报告(全部通过)"""
test_cases = [
TestCase(
id="TC001",
name="测试用例1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=True
),
TestCase(
id="TC002",
name="测试用例2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.POST,
headers={},
enabled=True
)
]
results = [
TestResult(
test_case=test_cases[0],
passed=True,
status_code=200,
response_body={"message": "success"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=123,
request_size=0,
response_size=0
)
),
TestResult(
test_case=test_cases[1],
passed=True,
status_code=200,
response_body={"message": "success"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=456,
request_size=0,
response_size=0
)
)
]
test_suite_result = TestSuiteResult(
suite_name="Test Suite",
total=2,
passed=2,
failed=0,
skipped=0,
results=results,
start_time=datetime.now()
)
report_path = tmp_path / "test_report.html"
report_manager.generate_html_report(test_suite_result, report_path)
content = report_path.read_text(encoding="utf-8")
assert "100.0%" in content or "100.0" in content
def test_generate_html_report_with_all_failed(report_manager, tmp_path):
"""测试生成HTML报告(全部失败)"""
test_cases = [
TestCase(
id="TC001",
name="测试用例1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=True
),
TestCase(
id="TC002",
name="测试用例2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.POST,
headers={},
enabled=True
)
]
results = [
TestResult(
test_case=test_cases[0],
passed=False,
status_code=500,
response_body={"error": "error"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=123,
request_size=0,
response_size=0
),
error_message="错误1"
),
TestResult(
test_case=test_cases[1],
passed=False,
status_code=404,
response_body={"error": "not found"},
response_headers={"Content-Type": "application/json"},
performance=PerformanceMetrics(
timestamp=datetime.now(),
response_time=456,
request_size=0,
response_size=0
),
error_message="错误2"
)
]
test_suite_result = TestSuiteResult(
suite_name="Test Suite",
total=2,
passed=0,
failed=2,
skipped=0,
results=results,
start_time=datetime.now()
)
report_path = tmp_path / "test_report.html"
report_manager.generate_html_report(test_suite_result, report_path)
content = report_path.read_text(encoding="utf-8")
assert "0.0%" in content or "0.0" in content
assert "错误1" in content
assert "错误2" in content
@@ -0,0 +1,297 @@
"""测试数据管理器单元测试"""
import pytest
from pathlib import Path
from unittest.mock import Mock
from apitest.data.test_data_manager import TestDataManager
from apitest.models.test_models import TestCase, HTTPMethod
from apitest.models.exceptions import TestRunException
@pytest.fixture
def mock_logger():
"""创建模拟日志记录器"""
return Mock()
@pytest.fixture
def data_manager(mock_logger):
"""创建测试数据管理器实例"""
return TestDataManager(logger=mock_logger)
@pytest.fixture
def sample_test_cases():
"""创建示例测试用例"""
return [
TestCase(
id="TC001",
name="测试用例1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=True
),
TestCase(
id="TC002",
name="测试用例2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.POST,
headers={},
enabled=True
)
]
def test_load_test_cases_from_json_success(data_manager, tmp_path):
"""测试从JSON文件加载测试用例(成功)"""
import json
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "GET",
"headers": {},
"enabled": True
},
{
"id": "TC002",
"name": "测试用例2",
"description": "测试用例2",
"module": "test",
"endpoint": "/api/test2",
"method": "POST",
"headers": {},
"enabled": True
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
test_cases = data_manager.load_test_cases_from_json(test_file)
assert len(test_cases) == 2
assert test_cases[0].id == "TC001"
assert test_cases[1].id == "TC002"
assert test_cases[0].method == HTTPMethod.GET
assert test_cases[1].method == HTTPMethod.POST
def test_load_test_cases_from_json_file_not_found(data_manager, tmp_path):
"""测试从JSON文件加载测试用例(文件不存在)"""
test_file = tmp_path / "nonexistent.json"
with pytest.raises(TestRunException) as exc_info:
data_manager.load_test_cases_from_json(test_file)
assert "测试用例文件不存在" in str(exc_info.value)
def test_load_test_cases_from_json_invalid_json(data_manager, tmp_path):
"""测试从JSON文件加载测试用例(无效JSON)"""
test_file = tmp_path / "invalid.json"
with open(test_file, "w", encoding="utf-8") as f:
f.write("{ invalid json }")
with pytest.raises(TestRunException) as exc_info:
data_manager.load_test_cases_from_json(test_file)
assert "JSON文件解析失败" in str(exc_info.value)
def test_load_test_cases_from_json_invalid_method(data_manager, tmp_path):
"""测试从JSON文件加载测试用例(无效HTTP方法)"""
import json
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "INVALID",
"headers": {},
"enabled": True
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
test_cases = data_manager.load_test_cases_from_json(test_file)
assert len(test_cases) == 1
assert test_cases[0].method == HTTPMethod.GET
def test_load_test_data_from_csv_success(data_manager, tmp_path):
"""测试从CSV文件加载测试数据(成功)"""
test_file = tmp_path / "test_data.csv"
with open(test_file, "w", encoding="utf-8", newline="") as f:
f.write("param1,param2,param3\n")
f.write("value1,value2,value3\n")
f.write("value4,value5,value6\n")
test_data = data_manager.load_test_data_from_csv(test_file)
assert len(test_data) == 2
assert test_data[0] == {"param1": "value1", "param2": "value2", "param3": "value3"}
assert test_data[1] == {"param1": "value4", "param2": "value5", "param3": "value6"}
def test_load_test_data_from_csv_file_not_found(data_manager, tmp_path):
"""测试从CSV文件加载测试数据(文件不存在)"""
test_file = tmp_path / "nonexistent.csv"
with pytest.raises(TestRunException) as exc_info:
data_manager.load_test_data_from_csv(test_file)
assert "测试数据文件不存在" in str(exc_info.value)
def test_parameterize_test_case_success(data_manager, sample_test_cases):
"""测试参数化测试用例(成功)"""
test_data = [
{"params": {"id": "1"}, "body": {"name": "test1"}},
{"params": {"id": "2"}, "body": {"name": "test2"}}
]
parameterized_cases = data_manager.parameterize_test_case(
sample_test_cases[0],
test_data
)
assert len(parameterized_cases) == 2
assert parameterized_cases[0].id == "TC001_1"
assert parameterized_cases[0].name == "测试用例1 (数据集 1)"
assert parameterized_cases[0].params == {"id": "1"}
assert parameterized_cases[0].body == {"name": "test1"}
assert parameterized_cases[1].id == "TC001_2"
assert parameterized_cases[1].name == "测试用例1 (数据集 2)"
assert parameterized_cases[1].params == {"id": "2"}
assert parameterized_cases[1].body == {"name": "test2"}
def test_parameterize_test_case_empty_data(data_manager, sample_test_cases):
"""测试参数化测试用例(空数据)"""
test_data = []
parameterized_cases = data_manager.parameterize_test_case(
sample_test_cases[0],
test_data
)
assert len(parameterized_cases) == 0
def test_save_test_cases_to_json_success(data_manager, sample_test_cases, tmp_path):
"""测试保存测试用例到JSON文件(成功)"""
output_file = tmp_path / "output.json"
data_manager.save_test_cases_to_json(sample_test_cases, output_file)
assert output_file.exists()
import json
with open(output_file, "r", encoding="utf-8") as f:
data = json.load(f)
assert len(data) == 2
assert data[0]["id"] == "TC001"
assert data[1]["id"] == "TC002"
def test_save_test_cases_to_json_create_directory(data_manager, sample_test_cases, tmp_path):
"""测试保存测试用例到JSON文件(创建目录)"""
output_file = tmp_path / "nested" / "dir" / "output.json"
data_manager.save_test_cases_to_json(sample_test_cases, output_file)
assert output_file.exists()
assert output_file.parent.exists()
def test_save_test_data_to_csv_success(data_manager, tmp_path):
"""测试保存测试数据到CSV文件(成功)"""
test_data = [
{"param1": "value1", "param2": "value2"},
{"param1": "value3", "param2": "value4"}
]
output_file = tmp_path / "output.csv"
data_manager.save_test_data_to_csv(test_data, output_file)
assert output_file.exists()
import csv
with open(output_file, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
rows = list(reader)
assert len(rows) == 2
assert rows[0]["param1"] == "value1"
assert rows[1]["param1"] == "value3"
def test_save_test_data_to_csv_with_fieldnames(data_manager, tmp_path):
"""测试保存测试数据到CSV文件(指定字段名)"""
test_data = [
{"param1": "value1", "param2": "value2", "param3": "value3"},
{"param1": "value4", "param2": "value5", "param3": "value6"}
]
output_file = tmp_path / "output.csv"
data_manager.save_test_data_to_csv(
test_data,
output_file,
fieldnames=["param1", "param2"]
)
assert output_file.exists()
import csv
with open(output_file, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
rows = list(reader)
fieldnames = reader.fieldnames
assert len(rows) == 2
assert fieldnames == ["param1", "param2"]
def test_save_test_data_to_csv_empty_data(data_manager, tmp_path):
"""测试保存测试数据到CSV文件(空数据)"""
test_data = []
output_file = tmp_path / "output.csv"
with pytest.raises(TestRunException) as exc_info:
data_manager.save_test_data_to_csv(test_data, output_file)
assert "测试数据为空" in str(exc_info.value)
def test_save_test_data_to_csv_create_directory(data_manager, tmp_path):
"""测试保存测试数据到CSV文件(创建目录)"""
test_data = [{"param1": "value1"}]
output_file = tmp_path / "nested" / "dir" / "output.csv"
data_manager.save_test_data_to_csv(test_data, output_file)
assert output_file.exists()
assert output_file.parent.exists()
@@ -0,0 +1,505 @@
import pytest
from unittest.mock import MagicMock, patch
from datetime import datetime
from apitest.models.test_models import (
TestCase, TestResult, TestSuiteResult, HTTPMethod, PerformanceMetrics
)
from apitest.core.test_engine import TestEngine
from apitest.core.validation_engine import ValidationEngine
from apitest.models.exceptions import TestRunException
class TestTestEngine:
"""测试TestEngine测试引擎"""
@pytest.fixture
def mock_api_client(self):
"""模拟API客户端"""
api_client = MagicMock()
api_client.request.return_value = {
"status_code": 200,
"response_body": {"message": "success"},
"response_headers": {"Content-Type": "application/json"},
"performance": PerformanceMetrics(
timestamp=datetime.now(),
response_time=1000,
request_size=100,
response_size=200
)
}
return api_client
@pytest.fixture
def mock_auth_manager(self):
"""模拟认证管理器"""
auth_manager = MagicMock()
auth_manager.get_auth_headers.return_value = {"Authorization": "Bearer token"}
return auth_manager
@pytest.fixture
def mock_validation_engine(self):
"""模拟验证引擎"""
validation_engine = MagicMock()
validation_engine.validate_response.return_value = (True, "")
return validation_engine
@pytest.fixture
def test_engine(self, mock_api_client, mock_auth_manager, mock_validation_engine):
"""创建测试引擎实例"""
return TestEngine(
api_client=mock_api_client,
auth_manager=mock_auth_manager,
validation_engine=mock_validation_engine
)
def test_test_engine_initialization(self, test_engine):
"""测试测试引擎初始化"""
assert test_engine.api_client is not None
assert test_engine.auth_manager is not None
assert test_engine.validation_engine is not None
assert test_engine._context == {}
def test_set_context(self, test_engine):
"""测试设置上下文变量"""
test_engine.set_context("user_id", "12345")
assert test_engine.get_context("user_id") == "12345"
def test_get_context_with_default(self, test_engine):
"""测试获取上下文变量(带默认值)"""
assert test_engine.get_context("nonexistent", "default") == "default"
def test_topological_sort_no_dependencies(self, test_engine):
"""测试拓扑排序(无依赖)"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={}
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={}
)
]
sorted_cases = test_engine._topological_sort(test_cases)
assert len(sorted_cases) == 2
def test_topological_sort_with_dependencies(self, test_engine):
"""测试拓扑排序(有依赖)"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
dependencies=[]
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
dependencies=["TC001"]
),
TestCase(
id="TC003",
name="测试3",
description="测试用例3",
module="test",
endpoint="/api/test3",
method=HTTPMethod.GET,
headers={},
dependencies=["TC002"]
)
]
sorted_cases = test_engine._topological_sort(test_cases)
assert len(sorted_cases) == 3
assert sorted_cases[0].id == "TC001"
assert sorted_cases[1].id == "TC002"
assert sorted_cases[2].id == "TC003"
def test_topological_sort_circular_dependency(self, test_engine):
"""测试拓扑排序(循环依赖)"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
dependencies=["TC002"]
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
dependencies=["TC001"]
)
]
with pytest.raises(TestRunException, match="存在循环依赖"):
test_engine._topological_sort(test_cases)
def test_resolve_context_variables_string(self, test_engine):
"""测试解析上下文变量(字符串)"""
test_engine.set_context("user_id", "12345")
result = test_engine._resolve_context_variables("${user_id}")
assert result == "12345"
def test_resolve_context_variables_dict(self, test_engine):
"""测试解析上下文变量(字典)"""
test_engine.set_context("user_id", "12345")
data = {"user_id": "${user_id}", "name": "test"}
result = test_engine._resolve_context_variables(data)
assert result == {"user_id": "12345", "name": "test"}
def test_resolve_context_variables_list(self, test_engine):
"""测试解析上下文变量(列表)"""
test_engine.set_context("user_id", "12345")
data = ["${user_id}", "test"]
result = test_engine._resolve_context_variables(data)
assert result == ["12345", "test"]
def test_execute_test_case_success(self, test_engine):
"""测试执行测试用例(成功)"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "status_code", "value": 200}]
)
result = test_engine._execute_test_case(test_case)
assert isinstance(result, TestResult)
assert result.passed == True
assert result.status_code == 200
assert result.test_case == test_case
def test_execute_test_case_with_auth(self, test_engine):
"""测试执行测试用例(带认证)"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
auth_required=True
)
result = test_engine._execute_test_case(test_case)
assert result.passed == True
test_engine.auth_manager.get_auth_headers.assert_called_once()
def test_execute_test_case_failure(self, test_engine, mock_validation_engine):
"""测试执行测试用例(失败)"""
mock_validation_engine.validate_response.return_value = (False, "验证失败")
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "status_code", "value": 200}]
)
result = test_engine._execute_test_case(test_case)
assert result.passed == False
assert result.error_message == "验证失败"
def test_execute_test_case_with_setup(self, test_engine):
"""测试执行测试用例(带前置操作)"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
setup={"type": "set_context", "key": "test_key", "value": "test_value"}
)
result = test_engine._execute_test_case(test_case)
assert test_engine.get_context("test_key") == "test_value"
def test_execute_test_case_with_teardown(self, test_engine):
"""测试执行测试用例(带后置操作)"""
test_engine.set_context("test_key", "test_value")
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
teardown={"type": "clear_context", "key": "test_key"}
)
result = test_engine._execute_test_case(test_case)
assert test_engine.get_context("test_key") is None
def test_execute_test_suite(self, test_engine):
"""测试执行测试套件"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={}
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={}
)
]
result = test_engine.execute_test_suite(test_cases)
assert isinstance(result, TestSuiteResult)
assert len(result.results) == 2
assert result.passed == 2
assert result.failed == 0
def test_execute_test_suite_with_dependencies(self, test_engine):
"""测试执行测试套件(有依赖)"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
dependencies=[]
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
dependencies=["TC001"]
)
]
result = test_engine.execute_test_suite(test_cases)
assert len(result.results) == 2
assert result.passed == 2
def test_execute_test_suite_stop_on_failure(self, test_engine, mock_validation_engine):
"""测试执行测试套件(失败时停止)"""
mock_validation_engine.validate_response.return_value = (False, "验证失败")
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={}
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={}
)
]
result = test_engine.execute_test_suite(test_cases, stop_on_failure=True)
assert len(result.results) == 1
assert result.passed == 0
assert result.failed == 1
def test_execute_test_suite_skip_disabled(self, test_engine):
"""测试执行测试套件(跳过已禁用的用例)"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=False
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
enabled=True
)
]
result = test_engine.execute_test_suite(test_cases)
assert len(result.results) == 1
assert result.skipped == 1
def test_execute_test_cases_by_filter_module(self, test_engine):
"""测试按模块过滤执行测试用例"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="module1",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={}
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="module2",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={}
)
]
result = test_engine.execute_test_cases_by_filter(test_cases, module_filter="module1")
assert len(result.results) == 1
assert result.results[0].test_case.module == "module1"
def test_execute_test_cases_by_filter_tag(self, test_engine):
"""测试按标签过滤执行测试用例"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
tags=["smoke", "regression"]
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
tags=["regression"]
)
]
result = test_engine.execute_test_cases_by_filter(test_cases, tag_filter=["smoke"])
assert len(result.results) == 1
assert "smoke" in result.results[0].test_case.tags
def test_execute_test_cases_by_filter_priority(self, test_engine):
"""测试按优先级过滤执行测试用例"""
test_cases = [
TestCase(
id="TC001",
name="测试1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
priority=1
),
TestCase(
id="TC002",
name="测试2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.GET,
headers={},
priority=2
)
]
result = test_engine.execute_test_cases_by_filter(test_cases, priority_filter=1)
assert len(result.results) == 1
assert result.results[0].test_case.priority == 1
def test_extract_response_data(self, test_engine):
"""测试提取响应数据到上下文"""
test_case = TestCase(
id="TC001",
name="测试用例",
description="测试用例描述",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "extract", "field": "user_id", "var_name": "extracted_id"}]
)
response_body = {"user_id": "12345", "name": "test"}
test_engine._extract_response_data(test_case, response_body)
assert test_engine.get_context("extracted_id") == "12345"
@@ -0,0 +1,373 @@
"""测试编排器单元测试"""
import pytest
from pathlib import Path
from unittest.mock import Mock, MagicMock, patch
from apitest.orchestrator.test_orchestrator import TestOrchestrator
from apitest.models.test_models import TestCase, TestSuiteResult, HTTPMethod
from apitest.models.exceptions import TestRunException
@pytest.fixture
def mock_config_manager():
"""创建模拟配置管理器"""
config_manager = Mock()
config_manager.get_base_url.return_value = "http://localhost:8080"
config_manager.get_timeout.return_value = 30
config_manager.get_log_level.return_value = "INFO"
config_manager.get_log_format.return_value = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
return config_manager
@pytest.fixture
def mock_logger_manager():
"""创建模拟日志管理器"""
logger_manager = Mock()
logger = Mock()
logger_manager.get_logger.return_value = logger
return logger_manager
@pytest.fixture
def test_orchestrator(mock_config_manager, mock_logger_manager):
"""创建测试编排器实例"""
return TestOrchestrator(
config_manager=mock_config_manager,
logger_manager=mock_logger_manager
)
@pytest.fixture
def sample_test_cases():
"""创建示例测试用例"""
return [
TestCase(
id="TC001",
name="测试用例1",
description="测试用例1",
module="test",
endpoint="/api/test1",
method=HTTPMethod.GET,
headers={},
enabled=True
),
TestCase(
id="TC002",
name="测试用例2",
description="测试用例2",
module="test",
endpoint="/api/test2",
method=HTTPMethod.POST,
headers={},
enabled=True
)
]
def test_init_test_orchestrator(mock_config_manager, mock_logger_manager):
"""测试初始化测试编排器"""
orchestrator = TestOrchestrator(
config_manager=mock_config_manager,
logger_manager=mock_logger_manager
)
assert orchestrator.config_manager == mock_config_manager
assert orchestrator.logger_manager == mock_logger_manager
assert orchestrator.api_client is not None
assert orchestrator.auth_manager is not None
assert orchestrator.validation_engine is not None
assert orchestrator.test_engine is not None
assert orchestrator.report_manager is not None
assert orchestrator.logger is not None
def test_init_test_orchestrator_without_managers():
"""测试初始化测试编排器(不提供管理器)"""
mock_logger = Mock()
orchestrator = TestOrchestrator(logger=mock_logger)
assert orchestrator.config_manager is not None
assert orchestrator.logger_manager is not None
assert orchestrator.api_client is not None
assert orchestrator.auth_manager is not None
assert orchestrator.validation_engine is not None
assert orchestrator.test_engine is not None
assert orchestrator.report_manager is not None
assert orchestrator.logger is not None
def test_load_test_cases_success(test_orchestrator, tmp_path):
"""测试加载测试用例(成功)"""
import json
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "GET",
"headers": {},
"enabled": True
},
{
"id": "TC002",
"name": "测试用例2",
"description": "测试用例2",
"module": "test",
"endpoint": "/api/test2",
"method": "POST",
"headers": {},
"enabled": True
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
test_cases = test_orchestrator.load_test_cases(test_file)
assert len(test_cases) == 2
assert test_cases[0].id == "TC001"
assert test_cases[1].id == "TC002"
def test_load_test_cases_file_not_found(test_orchestrator, tmp_path):
"""测试加载测试用例(文件不存在)"""
test_file = tmp_path / "nonexistent.json"
with pytest.raises(TestRunException) as exc_info:
test_orchestrator.load_test_cases(test_file)
assert "测试用例文件不存在" in str(exc_info.value)
def test_load_test_cases_invalid_json(test_orchestrator, tmp_path):
"""测试加载测试用例(无效JSON"""
test_file = tmp_path / "invalid.json"
with open(test_file, "w", encoding="utf-8") as f:
f.write("invalid json")
with pytest.raises(TestRunException) as exc_info:
test_orchestrator.load_test_cases(test_file)
assert "加载测试用例失败" in str(exc_info.value)
def test_load_test_cases_with_all_fields(test_orchestrator, tmp_path):
"""测试加载测试用例(包含所有字段)"""
import json
test_data = [
{
"id": "TC001",
"name": "测试用例1",
"description": "测试用例1",
"module": "test",
"endpoint": "/api/test1",
"method": "GET",
"headers": {"Content-Type": "application/json"},
"params": {"param1": "value1"},
"body": {"key": "value"},
"dependencies": ["TC002"],
"tags": ["tag1", "tag2"],
"priority": 1,
"enabled": True,
"timeout": 10,
"validations": [{"type": "status_code", "value": 200}],
"extract_config": [{"type": "extract", "field": "id", "var_name": "user_id"}]
}
]
test_file = tmp_path / "test_cases.json"
with open(test_file, "w", encoding="utf-8") as f:
json.dump(test_data, f)
test_cases = test_orchestrator.load_test_cases(test_file)
assert len(test_cases) == 1
assert test_cases[0].id == "TC001"
assert test_cases[0].method == HTTPMethod.GET
assert test_cases[0].headers == {"Content-Type": "application/json"}
assert test_cases[0].params == {"param1": "value1"}
assert test_cases[0].body == {"key": "value"}
assert test_cases[0].dependencies == ["TC002"]
assert test_cases[0].tags == ["tag1", "tag2"]
assert test_cases[0].priority == 1
assert test_cases[0].enabled == True
assert test_cases[0].timeout == 10
assert len(test_cases[0].validations) == 1
def test_run_test_suite_success(test_orchestrator, sample_test_cases, tmp_path):
"""测试运行测试套件(成功)"""
report_path = tmp_path / "test_report.html"
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 2
mock_result.failed = 0
mock_result.skipped = 0
mock_result.pass_rate = 100.0
mock_result.duration = 1.0
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite(
sample_test_cases,
generate_report=False
)
assert result == mock_result
mock_execute.assert_called_once_with(sample_test_cases, stop_on_failure=False)
def test_run_test_suite_with_report(test_orchestrator, sample_test_cases, tmp_path):
"""测试运行测试套件(生成报告)"""
report_path = tmp_path / "test_report.html"
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute, \
patch.object(test_orchestrator.report_manager, 'generate_html_report') as mock_report:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 2
mock_result.failed = 0
mock_result.skipped = 0
mock_result.pass_rate = 100.0
mock_result.duration = 1.0
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite(
sample_test_cases,
generate_report=True,
report_format="html",
report_path=report_path
)
assert result == mock_result
mock_report.assert_called_once()
def test_run_test_suite_stop_on_failure(test_orchestrator, sample_test_cases):
"""测试运行测试套件(失败时停止)"""
with patch.object(test_orchestrator.test_engine, 'execute_test_suite') as mock_execute:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 1
mock_result.failed = 1
mock_result.skipped = 0
mock_result.pass_rate = 50.0
mock_result.duration = 0.5
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite(
sample_test_cases,
stop_on_failure=True,
generate_report=False
)
assert result == mock_result
mock_execute.assert_called_once_with(sample_test_cases, stop_on_failure=True)
def test_run_test_suite_by_filter_module(test_orchestrator, sample_test_cases):
"""测试按模块过滤运行测试套件"""
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 2
mock_result.failed = 0
mock_result.skipped = 0
mock_result.pass_rate = 100.0
mock_result.duration = 1.0
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite_by_filter(
sample_test_cases,
module_filter="test",
generate_report=False
)
assert result == mock_result
mock_execute.assert_called_once_with(
sample_test_cases,
module_filter="test",
tag_filter=None,
priority_filter=None
)
def test_run_test_suite_by_filter_tag(test_orchestrator, sample_test_cases):
"""测试按标签过滤运行测试套件"""
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 2
mock_result.failed = 0
mock_result.skipped = 0
mock_result.pass_rate = 100.0
mock_result.duration = 1.0
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite_by_filter(
sample_test_cases,
tag_filter=["smoke"],
generate_report=False
)
assert result == mock_result
mock_execute.assert_called_once_with(
sample_test_cases,
module_filter=None,
tag_filter=["smoke"],
priority_filter=None
)
def test_run_test_suite_by_filter_priority(test_orchestrator, sample_test_cases):
"""测试按优先级过滤运行测试套件"""
with patch.object(test_orchestrator.test_engine, 'execute_test_cases_by_filter') as mock_execute:
mock_result = Mock(spec=TestSuiteResult)
mock_result.suite_name = "Test Suite"
mock_result.total = 2
mock_result.passed = 2
mock_result.failed = 0
mock_result.skipped = 0
mock_result.pass_rate = 100.0
mock_result.duration = 1.0
mock_execute.return_value = mock_result
result = test_orchestrator.run_test_suite_by_filter(
sample_test_cases,
priority_filter=1,
generate_report=False
)
assert result == mock_result
mock_execute.assert_called_once_with(
sample_test_cases,
module_filter=None,
tag_filter=None,
priority_filter=1
)
def test_set_base_url(test_orchestrator):
"""测试设置基础URL"""
test_orchestrator.set_base_url("http://new-api.example.com")
assert test_orchestrator.api_client.base_url == "http://new-api.example.com"
def test_set_auth_token(test_orchestrator):
"""测试设置认证令牌"""
test_orchestrator.set_auth_token("test-token-123")
assert test_orchestrator.auth_manager.get_token() == "test-token-123"
@@ -0,0 +1,480 @@
import pytest
from apitest.models.test_models import TestCase, HTTPMethod, PerformanceMetrics
from apitest.core.validation_engine import ValidationEngine
from apitest.models.exceptions import ValidationException
class TestValidationEngine:
"""测试ValidationEngine验证引擎"""
@pytest.fixture
def validation_engine(self):
"""创建验证引擎实例"""
return ValidationEngine()
def test_validate_status_code_success(self, validation_engine):
"""测试状态码验证成功"""
test_case = TestCase(
id="TC001",
name="测试状态码",
description="测试状态码验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "status_code", "value": 200}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "success"},
{}
)
assert passed == True
assert error == ""
def test_validate_status_code_failure(self, validation_engine):
"""测试状态码验证失败"""
test_case = TestCase(
id="TC001",
name="测试状态码",
description="测试状态码验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "status_code", "value": 200}]
)
passed, error = validation_engine.validate_response(
test_case,
404,
{"message": "not found"},
{}
)
assert passed == False
assert "状态码验证失败" in error
def test_validate_contains_success(self, validation_engine):
"""测试包含验证成功"""
test_case = TestCase(
id="TC002",
name="测试包含",
description="测试包含验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "contains", "value": "success"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "operation success"},
{}
)
assert passed == True
assert error == ""
def test_validate_contains_failure(self, validation_engine):
"""测试包含验证失败"""
test_case = TestCase(
id="TC002",
name="测试包含",
description="测试包含验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "contains", "value": "error"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "operation success"},
{}
)
assert passed == False
assert "包含验证失败" in error
def test_validate_contains_with_field(self, validation_engine):
"""测试字段包含验证"""
test_case = TestCase(
id="TC003",
name="测试字段包含",
description="测试字段包含验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "contains", "field": "message", "value": "success"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "operation success"},
{}
)
assert passed == True
def test_validate_equals_success(self, validation_engine):
"""测试相等验证成功"""
test_case = TestCase(
id="TC004",
name="测试相等",
description="测试相等验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "equals", "field": "status", "value": "ok"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"status": "ok"},
{}
)
assert passed == True
def test_validate_equals_failure(self, validation_engine):
"""测试相等验证失败"""
test_case = TestCase(
id="TC004",
name="测试相等",
description="测试相等验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "equals", "field": "status", "value": "ok"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"status": "error"},
{}
)
assert passed == False
assert "相等验证失败" in error
def test_validate_json_path_success(self, validation_engine):
"""测试JSON路径验证成功"""
test_case = TestCase(
id="TC005",
name="测试JSON路径",
description="测试JSON路径验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "json_path", "path": "data.user.name", "value": "John"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"data": {"user": {"name": "John"}}},
{}
)
assert passed == True
def test_validate_json_path_failure(self, validation_engine):
"""测试JSON路径验证失败"""
test_case = TestCase(
id="TC005",
name="测试JSON路径",
description="测试JSON路径验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "json_path", "path": "data.user.name", "value": "Jane"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"data": {"user": {"name": "John"}}},
{}
)
assert passed == False
assert "JSON路径验证失败" in error
def test_validate_regex_success(self, validation_engine):
"""测试正则表达式验证成功"""
test_case = TestCase(
id="TC006",
name="测试正则表达式",
description="测试正则表达式验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "regex", "field": "email", "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"email": "test@example.com"},
{}
)
assert passed == True
def test_validate_regex_failure(self, validation_engine):
"""测试正则表达式验证失败"""
test_case = TestCase(
id="TC006",
name="测试正则表达式",
description="测试正则表达式验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "regex", "field": "email", "pattern": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"email": "invalid-email"},
{}
)
assert passed == False
assert "正则表达式验证失败" in error
def test_validate_header_success(self, validation_engine):
"""测试响应头验证成功"""
test_case = TestCase(
id="TC007",
name="测试响应头",
description="测试响应头验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "header", "name": "Content-Type", "value": "application/json"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{},
{"Content-Type": "application/json"}
)
assert passed == True
def test_validate_header_not_found(self, validation_engine):
"""测试响应头不存在"""
test_case = TestCase(
id="TC007",
name="测试响应头",
description="测试响应头验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "header", "name": "X-Custom-Header"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{},
{"Content-Type": "application/json"}
)
assert passed == False
assert "响应头中未找到" in error
def test_validate_schema_success(self, validation_engine):
"""测试结构验证成功"""
test_case = TestCase(
id="TC008",
name="测试结构",
description="测试结构验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "schema", "schema": {"name": "str", "age": "int"}}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"name": "John", "age": 30},
{}
)
assert passed == True
def test_validate_schema_failure(self, validation_engine):
"""测试结构验证失败"""
test_case = TestCase(
id="TC008",
name="测试结构",
description="测试结构验证",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "schema", "schema": {"name": "str", "age": "int"}}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"name": "John", "age": "thirty"},
{}
)
assert passed == False
assert "字段 age 类型错误" in error
def test_validate_performance_success(self, validation_engine):
"""测试性能验证成功"""
from datetime import datetime
performance = PerformanceMetrics(
timestamp=datetime.now(),
response_time=1000,
request_size=100,
response_size=200
)
passed, error = validation_engine.validate_performance(performance, 5000)
assert passed == True
assert error == ""
def test_validate_performance_failure(self, validation_engine):
"""测试性能验证失败"""
from datetime import datetime
performance = PerformanceMetrics(
timestamp=datetime.now(),
response_time=6000,
request_size=100,
response_size=200
)
passed, error = validation_engine.validate_performance(performance, 5000)
assert passed == False
assert "响应时间超过阈值" in error
def test_validate_multiple_validations(self, validation_engine):
"""测试多个验证规则"""
test_case = TestCase(
id="TC009",
name="测试多验证",
description="测试多个验证规则",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[
{"type": "status_code", "value": 200},
{"type": "contains", "value": "success"},
{"type": "equals", "field": "status", "value": "ok"}
]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"status": "ok", "message": "operation success"},
{}
)
assert passed == True
def test_validate_multiple_validations_failure(self, validation_engine):
"""测试多个验证规则(其中一个失败)"""
test_case = TestCase(
id="TC009",
name="测试多验证",
description="测试多个验证规则",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[
{"type": "status_code", "value": 200},
{"type": "contains", "value": "error"},
{"type": "equals", "field": "status", "value": "ok"}
]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"status": "ok", "message": "operation success"},
{}
)
assert passed == False
assert "包含验证失败" in error
def test_validate_no_validations(self, validation_engine):
"""测试无验证规则"""
test_case = TestCase(
id="TC010",
name="测试无验证",
description="测试无验证规则",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={}
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "success"},
{}
)
assert passed == True
assert error == ""
def test_validate_unsupported_type(self, validation_engine):
"""测试不支持的验证类型"""
test_case = TestCase(
id="TC011",
name="测试不支持的类型",
description="测试不支持的验证类型",
module="test",
endpoint="/api/test",
method=HTTPMethod.GET,
headers={},
validations=[{"type": "unsupported_type"}]
)
passed, error = validation_engine.validate_response(
test_case,
200,
{"message": "success"},
{}
)
assert passed == False
assert "不支持的验证类型" in error