17.5 Skills 测试与调试
测试与调试概述
测试和调试是开发高质量 Skills 的关键环节。本节将详细介绍 Skills 的测试方法、调试技巧和最佳实践。
python
## 测试策略
### 1\. 单元测试
#### 1.1 基本测试
python
# tests/test_text_processor.py
import pytest
from skills.text_processor import TextProcessorSkill
from claude_code_sdk import SkillContext
class TestTextProcessorSkill:
"""文本处理 Skill 测试"""
def setup_method(self):
"""设置方法"""
self.skill = TextProcessorSkill()
self.context = SkillContext()
def test_skill_initialization(self):
"""测试 Skill 初始化"""
assert self.skill.name == "text-processor"
assert self.skill.version == "1.0.0"
assert self.skill.description == "Process and transform text"
def test_get_parameters_schema(self):
"""测试获取参数模式"""
schema = self.skill.get_parameters_schema()
assert "properties" in schema
assert "text" in schema["properties"]
assert "operations" in schema["properties"]
assert "required" in schema
assert "text" in schema["required"]
def test_execute_uppercase(self):
"""测试大写转换"""
result = self.skill.execute(
{
"text": "hello world",
"operations": [{"type": "uppercase"}]
},
self.context
)
assert result.success
assert result.data["processed"] == "HELLO WORLD"
def test_execute_lowercase(self):
"""测试小写转换"""
result = self.skill.execute(
{
"text": "HELLO WORLD",
"operations": [{"type": "lowercase"}]
},
self.context
)
assert result.success
assert result.data["processed"] == "hello world"
def test_execute_multiple_operations(self):
"""测试多个操作"""
result = self.skill.execute(
{
"text": "Hello World",
"operations": [
{"type": "lowercase"},
{"type": "remove_spaces"}
]
},
self.context
)
assert result.success
assert result.data["processed"] == "helloworld"
def test_execute_missing_text(self):
"""测试缺少必需参数"""
result = self.skill.execute(
{"operations": [{"type": "uppercase"}]},
self.context
)
assert not result.success
assert "error" in result.data
def test_execute_invalid_operation(self):
"""测试无效操作"""
result = self.skill.execute(
{
"text": "hello",
"operations": [{"type": "invalid"}]
},
self.context
)
assert not result.success
assert "error" in result.data
#### 1.2 使用固件
python
# tests/conftest.py
import pytest
from claude_code_sdk import SkillContext
from skills.text_processor import TextProcessorSkill
@pytest.fixture
def skill():
"""Skill 固件"""
return TextProcessorSkill()
@pytest.fixture
def context():
"""上下文固件"""
return SkillContext()
@pytest.fixture
def sample_text():
"""示例文本固件"""
return "Hello World"
@pytest.fixture
def sample_operations():
"""示例操作固件"""
return [
{"type": "uppercase"}
]
@pytest.fixture
def mock_context(mocker):
"""模拟上下文固件"""
context = mocker.Mock(spec=SkillContext)
return context
#### 1.3 参数化测试
python
# tests/test_text_processor_parametrized.py
import pytest
from skills.text_processor import TextProcessorSkill
from claude_code_sdk import SkillContext
class TestTextProcessorParametrized:
"""参数化测试"""
@pytest.mark.parametrize("input_text,operation,expected_output", [
("hello", "uppercase", "HELLO"),
("HELLO", "lowercase", "hello"),
("hello", "title", "Hello"),
("hello", "reverse", "olleh"),
("hello world", "remove_spaces", "helloworld")
])
def test_operations(self, input_text, operation, expected_output):
"""参数化操作测试"""
skill = TextProcessorSkill()
context = SkillContext()
result = skill.execute(
{
"text": input_text,
"operations": [{"type": operation}]
},
context
)
assert result.success
assert result.data["processed"] == expected_output
bash
### 2. 集成测试
#### 2.1 文件系统集成测试python
python
# tests/test_file_analyzer_integration.py
import pytest import os import tempfile from skills.file_analyzer import FileAnalyzerSkill from claude_code_sdk import SkillContext
class TestFileAnalyzerIntegration: """文件分析 Skill 集成测试"""
bash
def setup_method(self):
"""设置方法"""
self.skill = FileAnalyzerSkill()
self.context = SkillContext()
# 创建临时目录
self.temp_dir = tempfile.mkdtemp()
# 创建测试文件
self.test_file = os.path.join(self.temp_dir, "test.txt")
with open(self.test_file, 'w') as f:
f.write("Hello World\nThis is a test file\n")
def teardown_method(self):
"""清理方法"""
# 删除临时目录
import shutil
shutil.rmtree(self.temp_dir)
def test_analyze_file(self):
"""测试文件分析"""
result = self.skill.execute(
{
"path": self.test_file,
"analysis_type": "all"
},
self.context
)
assert result.success
assert result.data["type"] == "file"
assert result.data["name"] == "test.txt"
assert "size" in result.data
assert "content" in result.data
def test_analyze_directory(self):
"""测试目录分析"""
result = self.skill.execute(
{
"path": self.temp_dir,
"analysis_type": "structure"
},
self.context
)
assert result.success
assert result.data["type"] == "directory"
assert result.data["file_count"] == 1
def test_analyze_nonexistent_path(self):
"""测试不存在的路径"""
result = self.skill.execute(
{
"path": "/nonexistent/path",
"analysis_type": "all"
},
self.context
)
assert not result.success
assert "error" in result.data
#### 2.2 上下文集成测试
# tests/test_context_integration.py
import pytest from claude_code_sdk import SkillContext class TestContextIntegration: """上下文集成测试""" def test_context_file_operations(self, tmp_path): """测试上下文文件操作""" context = SkillContext()
# 写入文件
test_file = tmp_path / "test.txt" context.write_file(str(test_file), "Hello World")
# 读取文件
content = context.read_file(str(test_file)) assert content == "Hello World"
# 检查文件存在
assert context.file_exists(str(test_file)) def test_context_search_operations(self, tmp_path): """测试上下文搜索操作""" context = SkillContext()
# 创建测试文件
test_file = tmp_path / "test.py" test_file.write_text("def test_function():\n pass\n")
# 搜索代码
results = context.search_codebase("test_function", str(tmp_path)) assert len(results) > 0 def test_context_command_operations(self): """测试上下文命令操作""" context = SkillContext()
# 执行命令
output = context.run_command("echo 'Hello'") assert "Hello" in output
bash
### 3. 端到端测试
#### 3.1 完整工作流测试
```python
# tests/test_e2e.py
```python
import pytest
import os
import tempfile
from skills.code_generator import CodeGeneratorSkill
from skills.test_generator import TestGeneratorSkill
from claude_code_sdk import SkillContext
class TestEndToEnd:
"""端到端测试"""
def setup_method(self):
"""设置方法"""
self.context = SkillContext()
self.temp_dir = tempfile.mkdtemp()
def teardown_method(self):
"""清理方法"""
import shutil
shutil.rmtree(self.temp_dir)
def test_code_generation_to_test_generation(self):
"""测试代码生成到测试生成的完整流程"""
# 步骤 1:生成代码
code_generator = CodeGeneratorSkill()
code_result = code_generator.execute(
{
"language": "python",
"type": "function",
"name": "calculate_sum",
"description": "Calculate sum of two numbers",
"parameters": [
{"name": "a", "type": "int"},
{"name": "b", "type": "int"}
],
"return_type": "int"
},
self.context
)
assert code_result.success
# 步骤 2:保存生成的代码
code_file = os.path.join(self.temp_dir, "utils.py")
self.context.write_file(code_file, code_result.data["code"])
# 步骤 3:生成测试
test_generator = TestGeneratorSkill()
test_result = test_generator.execute(
{
"file_path": code_file,
"test_framework": "pytest"
},
self.context
)
assert test_result.success
assert "test_code" in test_result.data
# 步骤 4:保存测试代码
test_file = os.path.join(self.temp_dir, "test_utils.py")
self.context.write_file(test_file, test_result.data["test_code"])
# 验证文件存在
assert os.path.exists(code_file)
assert os.path.exists(test_file)
```
## 调试技巧
### 1. 日志调试
#### 1.1 添加日志
```python
# src/skills/my_skill.py
import logging
class MySkill(Skill):
def __init__(self):
super().__init__(
name="my-skill",
version="1.0.0",
description="A custom Claude Code skill"
)
# 设置日志
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.DEBUG)
def execute(self, parameters, context):
self.logger.debug("Starting execution")
self.logger.debug(f"Parameters: {parameters}")
try:
# 处理逻辑
result = self.process(parameters)
self.logger.debug(f"Result: {result}")
return result
except Exception as e:
self.logger.error(f"Error: {e}", exc_info=True)
raise
```
#### 1.2 配置日志
```python
# src/skills/logger_config.py
import logging
import sys
def setup_logging(level=logging.DEBUG):
"""设置日志"""
# 创建格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 创建控制台处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(level)
console_handler.setFormatter(formatter)
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(level)
root_logger.addHandler(console_handler)
# 配置文件处理器
file_handler = logging.FileHandler('debug.log')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# 在 Skill 初始化时调用
setup_logging()
```
### 2. 断点调试
#### 2.1 使用 pdb
```python
# src/skills/my_skill.py
import pdb
class MySkill(Skill):
def execute(self, parameters, context):
# 设置断点
pdb.set_trace()
# 处理逻辑
result = self.process(parameters)
return result
```
#### 2.2 使用 ipdb
```python
# src/skills/my_skill.py
import ipdb
class MySkill(Skill):
def execute(self, parameters, context):
# 设置断点
ipdb.set_trace()
# 处理逻辑
result = self.process(parameters)
return result
```
#### 2.3 使用 VS Code 调试器
```json
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Debug Skill",
"type": "python",
"request": "launch",
"module": "pytest",
"args": [
"tests/test_my_skill.py::TestMySkill::test_execute"
],
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}/src"
}
}
]
}
```
### 3. Mock 和 Stub
#### 3.1 Mock 上下文
```python
# tests/test_my_skill_mock.py
import pytest
from unittest.mock import Mock, MagicMock
from skills.my_skill import MySkill
class TestMySkillMock:
"""使用 Mock 的测试"""
def test_execute_with_mock_context(self):
"""使用模拟上下文测试"""
skill = MySkill()
# 创建模拟上下文
mock_context = Mock()
mock_context.read_file.return_value = "file content"
mock_context.write_file.return_value = None
# 执行 Skill
result = skill.execute(
{"input": "test"},
mock_context
)
# 验证结果
assert result.success
# 验证模拟对象被调用
mock_context.read_file.assert_called_once()
```
#### 3.2 Mock 外部依赖
```python
# tests/test_my_skill_external.py
import pytest
from unittest.mock import patch
from skills.my_skill import MySkill
class TestMySkillExternal:
"""测试外部依赖"""
@patch('skills.my_skill.external_api_call')
def test_execute_with_external_api(self, mock_api):
"""使用外部 API 的测试"""
skill = MySkill()
# 设置模拟返回值
mock_api.return_value = {"status": "success"}
# 执行 Skill
result = skill.execute({"input": "test"}, Mock())
# 验证结果
assert result.success
# 验证 API 被调用
mock_api.assert_called_once()
## 性能测试
### 1\. 基准测试
python
# tests/test_performance.py
import pytest
import time
from skills.text_processor import TextProcessorSkill
from claude_code_sdk import SkillContext
class TestPerformance:
"""性能测试"""
def test_text_processing_performance(self):
"""测试文本处理性能"""
skill = TextProcessorSkill()
context = SkillContext()
# 准备测试数据
large_text = "hello " * 10000
# 测量执行时间
start_time = time.time()
result = skill.execute(
{
"text": large_text,
"operations": [{"type": "uppercase"}]
},
context
)
end_time = time.time()
# 验证结果
assert result.success
# 验证性能(应该在 1 秒内完成)
execution_time = end_time - start_time
assert execution_time < 1.0, f"Execution took {execution_time} seconds"
def test_file_analysis_performance(self, tmp_path):
"""测试文件分析性能"""
from skills.file_analyzer import FileAnalyzerSkill
skill = FileAnalyzerSkill()
context = SkillContext()
# 创建测试文件
test_file = tmp_path / "test.txt"
test_file.write_text("x" * 1000000)
# 测量执行时间
start_time = time.time()
result = skill.execute(
{
"path": str(test_file),
"analysis_type": "size"
},
context
)
end_time = time.time()
# 验证结果
assert result.success
# 验证性能
execution_time = end_time - start_time
assert execution_time < 0.5, f"Execution took {execution_time} seconds"
### 2\. 内存测试
python
# tests/test_memory.py
import pytest
import tracemalloc
from skills.text_processor import TextProcessorSkill
from claude_code_sdk import SkillContext
class TestMemory:
"""内存测试"""
def test_memory_usage(self):
"""测试内存使用"""
skill = TextProcessorSkill()
context = SkillContext()
# 开始内存跟踪
tracemalloc.start()
# 执行操作
for i in range(100):
skill.execute(
{
"text": "hello world " * 100,
"operations": [{"type": "uppercase"}]
},
context
)
# 获取内存使用情况
current, peak = tracemalloc.get_traced_memory()
# 停止内存跟踪
tracemalloc.stop()
# 验证内存使用(峰值应该小于 10MB)
peak_mb = peak / 1024 / 1024
assert peak_mb < 10, f"Peak memory usage: {peak_mb} MB"
bash
## 测试覆盖率
### 1. 生成覆盖率报告
```bash
# 运行测试并生成覆盖率报告
```python
pytest --cov=src/skills --cov-report=html --cov-report=term
# 查看覆盖率报告
open htmlcov/index.html
```
### 2. 配置覆盖率
```ini
# .coveragerc
[run]
source = src/skills
omit =
*/tests/*
*/__pycache__/*
*/site-packages/*
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
if TYPE_CHECKING:
[html]
directory = htmlcov
```
## 调试工具
### 1. 使用 pytest-debug
```bash
# 安装 pytest-debug
pip install pytest-debug
# 在测试失败时进入调试器
pytest --pdb
```
### 2. 使用 pytest-pdb
```bash
# 安装 pytest-pdb
pip install pytest-pdb
# 在测试失败时自动进入 pdb
pytest --pdb
```
### 3. 使用 pytest-sugar
```bash
# 安装 pytest-sugar
pip install pytest-sugar
# 使用更友好的输出
pytest -v
```
## 最佳实践
### 1. 测试编写原则
#### 1.1 独立性
- 每个测试应该独立运行
- 不依赖其他测试的状态
- 使用 setup 和 teardown 方法
#### 1.2 可重复性
- 测试应该可以重复运行
- 不依赖外部状态
- 使用固定的测试数据
#### 1.3 快速性
- 测试应该快速执行
- 避免不必要的等待
- 使用 Mock 隔离外部依赖
#### 1.4 可读性
- 测试名称应该清晰
- 使用描述性的断言
- 添加必要的注释
### 2. 调试技巧
#### 2.1 分而治之
- 将复杂问题分解为小问题
- 逐个测试每个组件
- 使用单元测试隔离问题
#### 2.2 添加日志
- 在关键位置添加日志
- 记录输入和输出
- 使用不同的日志级别
#### 2.3 使用断点
- 在可疑位置设置断点
- 检查变量值
- 单步执行代码
#### 2.4 使用 Mock
- Mock 外部依赖
- 控制测试环境
- 简化测试场景
## 总结
测试和调试是开发高质量 Skills 的关键环节。通过合理的测试策略、有效的调试技巧和完善的工具支持,可以显著提高 Skills 的质量和可靠性。
在下一章中,我们将探讨 Skills 的实际应用,展示如何在不同场景中使用 Skills。