Skip to content

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。

基于 MIT 许可发布 | 永久导航