测试与调试概述#
测试和调试是开发高质量 Skills 的关键环节。本节将详细介绍 Skills 的测试方法、调试技巧和最佳实践。
测试策略#
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
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 集成测试"""
bashdef 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(
bash{ "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 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 # 运行测试并生成覆盖率报告 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。