18_如何测试一个MCPServer

ai #mcp

如何测试一个MCP Server:全面指南与最佳实践

总起部分:MCP Server测试的重要性与挑战

Model Context Protocol (MCP) 作为连接AI Agent与外部系统的关键协议,其服务器的可靠性和稳定性直接影响整个AI应用系统的质量。随着MCP生态系统的快速发展,越来越多的开发者开始构建和部署MCP服务器,但如何有效地测试这些服务器却成为一个亟待解决的问题。

MCP Server测试的重要性

MCP服务器作为AI Agent与外部系统交互的桥梁,承担着数据转换、协议适配、错误处理等关键职责。一个未经充分测试的MCP服务器可能导致以下问题:

  • 数据一致性错误:AI Agent获取的数据不准确或不完整
  • 协议兼容性问题:与不同MCP客户端的通信失败
  • 性能瓶颈:响应时间过长影响用户体验
  • 安全漏洞:可能被恶意利用攻击AI系统

通过全面的测试策略,我们可以确保MCP服务器在各种场景下都能稳定运行,为AI Agent提供可靠的服务支持。

MCP Server测试面临的挑战

MCP服务器的测试相比传统Web服务具有独特的挑战:

  1. 协议复杂性:MCP协议涉及JSON-RPC通信、资源管理、工具调用等多个层面
  2. 异步交互:AI Agent与服务器的交互通常是异步的,测试需要处理时间依赖
  3. 多语言支持:MCP SDK支持多种编程语言,需要针对不同语言设计测试策略
  4. 模拟AI客户端:需要模拟AI Agent的行为进行端到端测试
  5. 状态管理:某些MCP服务器维护状态,测试需要考虑状态转换

本文将系统性地介绍MCP服务器的测试方法,从基础的单元测试到复杂的端到端测试,帮助开发者构建高质量的MCP服务器。


第一章:MCP Server测试基础

1.1 MCP协议概述

Model Context Protocol是一个开放标准,用于连接AI模型与外部数据源和工具。MCP服务器通过JSON-RPC 2.0协议提供以下核心功能:

  • 工具(Tools):可供AI Agent调用的函数
  • 资源(Resources):可访问的数据源
  • 提示(Prompts):预定义的提示模板
  • 日志(Logging):结构化日志记录

了解这些核心概念是设计有效测试策略的基础。

1.2 测试金字塔

对于MCP服务器,我们采用经典的测试金字塔模型:

        /\
       /  \
      / E2E \     端到端测试(少量)
     /______\
    /        \
   /Integration\ 集成测试(适量)
  /__________\
 /            \
/  Unit Tests  \   单元测试(大量)
/______________\
  • 单元测试:测试单个函数、类或模块
  • 集成测试:测试多个组件的交互
  • 端到端测试:测试完整的工作流程

1.3 测试环境设置

TypeScript环境

// 安装测试依赖
npm install --save-dev jest @types/jest ts-jest

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/index.ts'
  ]
};

Python环境

# 安装测试依赖
pip install pytest pytest-asyncio pytest-cov

# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = --cov=src --cov-report=html

Go环境

// 初始化测试模块
go mod init mcp-server-test
go get github.com/stretchr/testify/assert
go get github.com/stretchr/testify/mock

1.4 测试数据管理

// tests/fixtures/test-data.ts
export const createTestStory = (overrides?: any) => ({
  id: 'test-story-id',
  title: 'Test Story',
  description: 'Test story description',
  status: 'active',
  work_directory: '/test/workspace',
  created_at: new Date(),
  updated_at: new Date(),
  ...overrides,
});

export const createTestTask = (storyId: string, overrides?: any) => ({
  id: 'test-task-id',
  story_id: storyId,
  title: 'Test Task',
  description: 'Test task description',
  status: 'pending',
  file_path: null,
  sequence_order: 1,
  created_at: new Date(),
  updated_at: new Date(),
  ...overrides,
});

第二章:单元测试实现

单元测试是MCP服务器测试的基础,专注于验证单个组件的功能正确性。我们将分别介绍Python、TypeScript和Go中的单元测试实现。

2.1 TypeScript单元测试

工具函数测试

以task_mcp项目中的日志工具为例:

// tests/unit/logger.test.ts
import { logger, createLoggerWithContext, createTimer } from '@/utils/logger.js';
import { LogLevel } from '@/types/common-types.js';

describe('Logger', () => {
  beforeEach(() => {
    // 重置日志级别
    logger.level = LogLevel.INFO;
  });

  describe('基础日志功能', () => {
    it('应该能够记录不同级别的日志', () => {
      expect(() => {
        logger.error('Test error message');
        logger.warn('Test warning message');
        logger.info('Test info message');
        logger.debug('Test debug message');
      }).not.toThrow();
    });

    it('应该能够记录带有元数据的日志', () => {
      expect(() => {
        logger.info('Test message with metadata', { userId: '123', action: 'test' });
      }).not.toThrow();
    });
  });

  describe('上下文日志器', () => {
    it('应该能够创建带上下文的日志器', () => {
      const contextLogger = createLoggerWithContext('TestContext');
      expect(contextLogger).toBeDefined();
    });

    it('应该在日志消息中包含上下文信息', () => {
      const contextLogger = createLoggerWithContext('TestContext');
      const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
      
      contextLogger.info('Test message');
      
      expect(consoleSpy).toHaveBeenCalledWith(
        expect.stringContaining('[TestContext] Test message')
      );
      
      consoleSpy.mockRestore();
    });
  });
});

服务层测试

// tests/unit/story-service.test.ts
import { StoryService } from '@/services/story-service.js';
import { StoryRepository } from '@/repositories/story-repository.js';
import { createTestStory } from '../fixtures/test-data.js';

// Mock依赖
jest.mock('@/repositories/story-repository.js');

describe('StoryService', () => {
  let storyService: StoryService;
  let mockRepository: jest.Mocked<StoryRepository>;

  beforeEach(() => {
    mockRepository = new StoryRepository() as jest.Mocked<StoryRepository>;
    storyService = new StoryService(mockRepository);
  });

  describe('createStory', () => {
    it('应该能够创建新故事', async () => {
      // Arrange
      const storyData = createTestStory();
      mockRepository.create.mockResolvedValue(storyData);

      // Act
      const result = await storyService.createStory(storyData);

      // Assert
      expect(result).toEqual(storyData);
      expect(mockRepository.create).toHaveBeenCalledWith(storyData);
    });

    it('应该处理创建失败的情况', async () => {
      // Arrange
      const storyData = createTestStory();
      const error = new Error('Database error');
      mockRepository.create.mockRejectedValue(error);

      // Act & Assert
      await expect(storyService.createStory(storyData)).rejects.toThrow('Database error');
    });
  });

  describe('getStoryById', () => {
    it('应该能够根据ID获取故事', async () => {
      // Arrange
      const storyData = createTestStory();
      mockRepository.findById.mockResolvedValue(storyData);

      // Act
      const result = await storyService.getStoryById('test-story-id');

      // Assert
      expect(result).toEqual(storyData);
      expect(mockRepository.findById).toHaveBeenCalledWith('test-story-id');
    });

    it('当故事不存在时应该返回null', async () => {
      // Arrange
      mockRepository.findById.mockResolvedValue(null);

      // Act
      const result = await storyService.getStoryById('nonexistent-id');

      // Assert
      expect(result).toBeNull();
    });
  });
});

工具测试

// tests/unit/story-tools.test.ts
import { create_story, complete_story } from '@/tools/story-tools.js';
import { StoryService } from '@/services/story-service.js';

jest.mock('@/services/story-service.js');

describe('Story Tools', () => {
  let mockStoryService: jest.Mocked<StoryService>;

  beforeEach(() => {
    mockStoryService = new StoryService({} as any) as jest.Mocked<StoryService>;
    (StoryService as jest.MockedClass<typeof StoryService>).mockImplementation(() => mockStoryService);
  });

  describe('create_story', () => {
    it('应该能够创建故事', async () => {
      // Arrange
      const params = {
        work_directory: '/test/workspace',
        title: 'Test Story',
        description: 'Test description'
      };
      const expectedResult = {
        story_id: 'test-story-id',
        title: 'Test Story',
        status: 'active',
        created_at: '2023-10-06T10:00:00Z'
      };
      mockStoryService.createStory.mockResolvedValue(expectedResult);

      // Act
      const result = await create_story(params);

      // Assert
      expect(result).toEqual(expectedResult);
      expect(mockStoryService.createStory).toHaveBeenCalledWith(params);
    });
  });
});

2.2 Python单元测试

基础工具函数测试

# tests/test_logger.py
import pytest
from unittest.mock import patch, MagicMock
from mcp_server.utils.logger import Logger, create_logger_with_context, create_timer

class TestLogger:
    def setup_method(self):
        self.logger = Logger("TestContext")

    def test_log_different_levels(self):
        """测试不同级别的日志记录"""
        with patch('mcp_server.utils.logger.logger') as mock_logger:
            self.logger.error("Test error")
            self.logger.warn("Test warning")
            self.logger.info("Test info")
            self.logger.debug("Test debug")
            
            # 验证所有日志方法都被调用
            assert mock_logger.error.called
            assert mock_logger.warn.called
            assert mock_logger.info.called
            assert mock_logger.debug.called

    def test_log_with_metadata(self):
        """测试带元数据的日志记录"""
        with patch('mcp_server.utils.logger.logger') as mock_logger:
            self.logger.info("Test message", {"user_id": "123", "action": "test"})
            
            mock_logger.info.assert_called_once()
            call_args = mock_logger.info.call_args
            assert "[TestContext] Test message" in call_args[0][0]
            assert call_args[1]["user_id"] == "123"

    def test_operation_logging(self):
        """测试操作日志记录"""
        with patch('mcp_server.utils.logger.logger') as mock_logger:
            self.logger.start_operation("TestOperation", {"param": "value"})
            self.logger.complete_operation("TestOperation", 100)
            self.logger.fail_operation("TestOperation", Exception("Test error"))
            
            assert mock_logger.info.call_count == 2
            assert mock_logger.error.call_count == 1

class TestTimer:
    def test_timer_creation(self):
        """测试计时器创建"""
        logger = Logger("TestContext")
        timer = create_timer("TestOperation", logger)
        assert timer is not None
        assert timer.operation == "TestOperation"

    @pytest.mark.asyncio
    async def test_timer_measurement(self):
        """测试计时器测量"""
        logger = Logger("TestContext")
        timer = create_timer("TestOperation", logger)
        
        # 模拟一些操作
        import asyncio
        await asyncio.sleep(0.01)
        
        duration = timer.done()
        assert duration >= 10  # 至少10ms

服务层测试

# tests/test_story_service.py
import pytest
from unittest.mock import Mock, patch
from mcp_server.services.story_service import StoryService
from mcp_server.models.story import Story

class TestStoryService:
    def setup_method(self):
        self.mock_repository = Mock()
        self.story_service = StoryService(self.mock_repository)

    def test_create_story_success(self):
        """测试成功创建故事"""
        # Arrange
        story_data = {
            "id": "test-story-id",
            "title": "Test Story",
            "description": "Test description",
            "status": "active",
            "work_directory": "/test/workspace"
        }
        expected_story = Story(**story_data)
        self.mock_repository.create.return_value = expected_story

        # Act
        result = self.story_service.create_story(story_data)

        # Assert
        assert result == expected_story
        self.mock_repository.create.assert_called_once_with(story_data)

    def test_create_story_failure(self):
        """测试创建故事失败"""
        # Arrange
        story_data = {"title": "Test Story"}
        self.mock_repository.create.side_effect = Exception("Database error")

        # Act & Assert
        with pytest.raises(Exception, match="Database error"):
            self.story_service.create_story(story_data)

    def test_get_story_by_id_success(self):
        """测试成功获取故事"""
        # Arrange
        story_id = "test-story-id"
        expected_story = Story(
            id=story_id,
            title="Test Story",
            description="Test description",
            status="active",
            work_directory="/test/workspace"
        )
        self.mock_repository.find_by_id.return_value = expected_story

        # Act
        result = self.story_service.get_story_by_id(story_id)

        # Assert
        assert result == expected_story
        self.mock_repository.find_by_id.assert_called_once_with(story_id)

    def test_get_story_by_id_not_found(self):
        """测试获取不存在的故事"""
        # Arrange
        story_id = "nonexistent-id"
        self.mock_repository.find_by_id.return_value = None

        # Act
        result = self.story_service.get_story_by_id(story_id)

        # Assert
        assert result is None

MCP工具测试

# tests/test_story_tools.py
import pytest
from unittest.mock import Mock, patch
from mcp_server.tools.story_tools import create_story, complete_story

class TestStoryTools:
    def setup_method(self):
        self.mock_service = Mock()

    @patch('mcp_server.tools.story_tools.StoryService')
    def test_create_story_success(self, mock_service_class):
        """测试成功创建故事工具"""
        # Arrange
        mock_service_class.return_value = self.mock_service
        params = {
            "work_directory": "/test/workspace",
            "title": "Test Story",
            "description": "Test description"
        }
        expected_result = {
            "story_id": "test-story-id",
            "title": "Test Story",
            "status": "active",
            "created_at": "2023-10-06T10:00:00Z"
        }
        self.mock_service.create_story.return_value = expected_result

        # Act
        result = create_story(params)

        # Assert
        assert result == expected_result
        self.mock_service.create_story.assert_called_once_with(params)

    @patch('mcp_server.tools.story_tools.StoryService')
    def test_complete_story_success(self, mock_service_class):
        """测试成功完成故事工具"""
        # Arrange
        mock_service_class.return_value = self.mock_service
        params = {
            "work_directory": "/test/workspace",
            "story_id": "test-story-id"
        }
        expected_result = {
            "story_id": "test-story-id",
            "status": "completed",
            "completed_at": "2023-10-06T10:00:00Z"
        }
        self.mock_service.complete_story.return_value = expected_result

        # Act
        result = complete_story(params)

        # Assert
        assert result == expected_result
        self.mock_service.complete_story.assert_called_once_with(params)

2.3 Go单元测试

基础工具函数测试

// logger_test.go
package utils

import (
	"testing"
	"bytes"
	"github.com/stretchr/testify/assert"
)

func TestLogger_LogLevels(t *testing.T) {
	// 创建一个缓冲区来捕获日志输出
	var buf bytes.Buffer
	logger := NewLogger("TestContext", &buf)

	// 测试不同级别的日志
	logger.Error("Test error message")
	logger.Warn("Test warning message")
	logger.Info("Test info message")
	logger.Debug("Test debug message")

	output := buf.String()
	assert.Contains(t, output, "[TestContext] Test error message")
	assert.Contains(t, output, "[TestContext] Test warning message")
	assert.Contains(t, output, "[TestContext] Test info message")
	assert.Contains(t, output, "[TestContext] Test debug message")
}

func TestLogger_LogWithMetadata(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger("TestContext", &buf)

	logger.Info("Test message", map[string]interface{}{
		"user_id": "123",
		"action":  "test",
	})

	output := buf.String()
	assert.Contains(t, output, "[TestContext] Test message")
	assert.Contains(t, output, "user_id")
	assert.Contains(t, output, "action")
}

func TestTimer_Measurement(t *testing.T) {
	var buf bytes.Buffer
	logger := NewLogger("TestContext", &buf)
	timer := NewTimer("TestOperation", logger)

	// 模拟一些操作
	time.Sleep(10 * time.Millisecond)

	duration := timer.Done()
	assert.GreaterOrEqual(t, duration, int64(10))
	
	output := buf.String()
	assert.Contains(t, output, "Performance: TestOperation")
}

服务层测试

// story_service_test.go
package services

import (
	"testing"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockStoryRepository 是StoryRepository的模拟实现
type MockStoryRepository struct {
	mock.Mock
}

func (m *MockStoryRepository) Create(story *Story) (*Story, error) {
	args := m.Called(story)
	return args.Get(0).(*Story), args.Error(1)
}

func (m *MockStoryRepository) FindByID(id string) (*Story, error) {
	args := m.Called(id)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*Story), args.Error(1)
}

func TestStoryService_CreateStory_Success(t *testing.T) {
	// Arrange
	mockRepo := new(MockStoryRepository)
	service := NewStoryService(mockRepo)
	
	story := &Story{
		ID:            "test-story-id",
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: "/test/workspace",
	}
	
	mockRepo.On("Create", story).Return(story, nil)

	// Act
	result, err := service.CreateStory(story)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, story, result)
	mockRepo.AssertExpectations(t)
}

func TestStoryService_CreateStory_Failure(t *testing.T) {
	// Arrange
	mockRepo := new(MockStoryRepository)
	service := NewStoryService(mockRepo)
	
	story := &Story{
		ID:    "test-story-id",
		Title: "Test Story",
	}
	
	expectedError := errors.New("database error")
	mockRepo.On("Create", story).Return(nil, expectedError)

	// Act
	result, err := service.CreateStory(story)

	// Assert
	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Equal(t, expectedError, err)
	mockRepo.AssertExpectations(t)
}

func TestStoryService_GetStoryByID_Success(t *testing.T) {
	// Arrange
	mockRepo := new(MockStoryRepository)
	service := NewStoryService(mockRepo)
	
	storyID := "test-story-id"
	expectedStory := &Story{
		ID:            storyID,
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: "/test/workspace",
	}
	
	mockRepo.On("FindByID", storyID).Return(expectedStory, nil)

	// Act
	result, err := service.GetStoryByID(storyID)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, expectedStory, result)
	mockRepo.AssertExpectations(t)
}

func TestStoryService_GetStoryByID_NotFound(t *testing.T) {
	// Arrange
	mockRepo := new(MockStoryRepository)
	service := NewStoryService(mockRepo)
	
	storyID := "nonexistent-id"
	mockRepo.On("FindByID", storyID).Return(nil, nil)

	// Act
	result, err := service.GetStoryByID(storyID)

	// Assert
	assert.NoError(t, err)
	assert.Nil(t, result)
	mockRepo.AssertExpectations(t)
}

MCP工具测试

// story_tools_test.go
package tools

import (
	"testing"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// MockStoryService 是StoryService的模拟实现
type MockStoryService struct {
	mock.Mock
}

func (m *MockStoryService) CreateStory(params *CreateStoryParams) (*CreateStoryResult, error) {
	args := m.Called(params)
	return args.Get(0).(*CreateStoryResult), args.Error(1)
}

func (m *MockStoryService) CompleteStory(params *CompleteStoryParams) (*CompleteStoryResult, error) {
	args := m.Called(params)
	return args.Get(0).(*CompleteStoryResult), args.Error(1)
}

func TestCreateStory_Success(t *testing.T) {
	// Arrange
	mockService := new(MockStoryService)
	
	params := &CreateStoryParams{
		WorkDirectory: "/test/workspace",
		Title:         "Test Story",
		Description:   "Test description",
	}
	
	expectedResult := &CreateStoryResult{
		StoryID:   "test-story-id",
		Title:     "Test Story",
		Status:    "active",
		CreatedAt: "2023-10-06T10:00:00Z",
	}
	
	mockService.On("CreateStory", params).Return(expectedResult, nil)

	// Act
	result, err := CreateStory(mockService, params)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, expectedResult, result)
	mockService.AssertExpectations(t)
}

func TestCompleteStory_Success(t *testing.T) {
	// Arrange
	mockService := new(MockStoryService)
	
	params := &CompleteStoryParams{
		WorkDirectory: "/test/workspace",
		StoryID:       "test-story-id",
	}
	
	expectedResult := &CompleteStoryResult{
		StoryID:     "test-story-id",
		Status:      "completed",
		CompletedAt: "2023-10-06T10:00:00Z",
	}
	
	mockService.On("CompleteStory", params).Return(expectedResult, nil)

	// Act
	result, err := CompleteStory(mockService, params)

	// Assert
	assert.NoError(t, err)
	assert.Equal(t, expectedResult, result)
	mockService.AssertExpectations(t)
}

第三章:集成测试实现

集成测试关注多个组件之间的交互,验证它们能够正确协作。对于MCP服务器,集成测试通常涉及数据库操作、文件系统操作以及多个服务层的协作。

3.1 TypeScript集成测试

数据库集成测试

// tests/integration/database.test.ts
import { DatabaseConnection } from '@/database/connection.js';
import { StoryRepository } from '@/repositories/story-repository.js';
import { TaskRepository } from '@/repositories/task-repository.js';
import { createTestWorkDir, cleanupTestWorkDir } from '../helpers/test-helpers.js';

describe('数据库集成测试', () => {
  let dbConnection: DatabaseConnection;
  let storyRepository: StoryRepository;
  let taskRepository: TaskRepository;
  let workDir: string;

  beforeAll(async () => {
    workDir = createTestWorkDir('db-integration');
    dbConnection = new DatabaseConnection(workDir);
    await dbConnection.initialize();
    
    storyRepository = new StoryRepository(dbConnection);
    taskRepository = new TaskRepository(dbConnection);
  });

  afterAll(async () => {
    await dbConnection.close();
    cleanupTestWorkDir(workDir);
  });

  beforeEach(async () => {
    // 清理数据库
    await dbConnection.execute('DELETE FROM task');
    await dbConnection.execute('DELETE FROM story');
  });

  describe('故事和任务的关联操作', () => {
    it('应该能够创建故事并关联任务', async () => {
      // 创建故事
      const story = await storyRepository.create({
        id: 'test-story-id',
        title: 'Test Story',
        description: 'Test description',
        status: 'active',
        work_directory: workDir
      });

      expect(story).toBeDefined();
      expect(story.id).toBe('test-story-id');

      // 为故事创建任务
      const task1 = await taskRepository.create({
        id: 'test-task-1',
        story_id: story.id,
        title: 'Test Task 1',
        description: 'First test task',
        status: 'pending',
        sequence_order: 1
      });

      const task2 = await taskRepository.create({
        id: 'test-task-2',
        story_id: story.id,
        title: 'Test Task 2',
        description: 'Second test task',
        status: 'pending',
        sequence_order: 2
      });

      expect(task1).toBeDefined();
      expect(task2).toBeDefined();

      // 验证关联关系
      const storyTasks = await taskRepository.findByStoryId(story.id);
      expect(storyTasks).toHaveLength(2);
      expect(storyTasks[0].sequence_order).toBe(1);
      expect(storyTasks[1].sequence_order).toBe(2);
    });

    it('应该能够按顺序获取下一个任务', async () => {
      // 创建故事
      const story = await storyRepository.create({
        id: 'test-story-id',
        title: 'Test Story',
        description: 'Test description',
        status: 'active',
        work_directory: workDir
      });

      // 创建多个任务
      await taskRepository.create({
        id: 'test-task-1',
        story_id: story.id,
        title: 'Test Task 1',
        status: 'completed',
        sequence_order: 1
      });

      await taskRepository.create({
        id: 'test-task-2',
        story_id: story.id,
        title: 'Test Task 2',
        status: 'pending',
        sequence_order: 2
      });

      await taskRepository.create({
        id: 'test-task-3',
        story_id: story.id,
        title: 'Test Task 3',
        status: 'pending',
        sequence_order: 3
      });

      // 获取下一个任务
      const nextTask = await taskRepository.findNextTask(story.id);
      expect(nextTask).toBeDefined();
      expect(nextTask!.id).toBe('test-task-2');
      expect(nextTask!.sequence_order).toBe(2);
    });
  });

  describe('事务处理', () => {
    it('应该在事务失败时回滚所有操作', async () => {
      // 开始事务
      const transaction = await dbConnection.beginTransaction();

      try {
        // 在事务中创建故事
        await storyRepository.createWithTransaction(transaction, {
          id: 'test-story-id',
          title: 'Test Story',
          description: 'Test description',
          status: 'active',
          work_directory: workDir
        });

        // 模拟失败
        throw new Error('Simulated error');
      } catch (error) {
        // 回滚事务
        await transaction.rollback();
      }

      // 验证数据已回滚
      const story = await storyRepository.findById('test-story-id');
      expect(story).toBeNull();
    });

    it('应该在事务成功时提交所有操作', async () => {
      // 开始事务
      const transaction = await dbConnection.beginTransaction();

      try {
        // 在事务中创建故事
        await storyRepository.createWithTransaction(transaction, {
          id: 'test-story-id',
          title: 'Test Story',
          description: 'Test description',
          status: 'active',
          work_directory: workDir
        });

        // 提交事务
        await transaction.commit();
      } catch (error) {
        await transaction.rollback();
        throw error;
      }

      // 验证数据已提交
      const story = await storyRepository.findById('test-story-id');
      expect(story).toBeDefined();
      expect(story!.id).toBe('test-story-id');
    });
  });
});

文件系统集成测试

// tests/integration/file-system.test.ts
import { FileService } from '@/services/file-service.js';
import { createTestWorkDir, cleanupTestWorkDir } from '../helpers/test-helpers.js';
import * as fs from 'fs/promises';
import * as path from 'path';

describe('文件系统集成测试', () => {
  let fileService: FileService;
  let workDir: string;

  beforeAll(async () => {
    workDir = createTestWorkDir('fs-integration');
    fileService = new FileService();
  });

  afterAll(async () => {
    cleanupTestWorkDir(workDir);
  });

  describe('任务文件管理', () => {
    it('应该能够创建和读取任务文件', async () => {
      const filePath = path.join(workDir, 'T1.md');
      const content = '# Test Task\n\nThis is a test task file.';

      // 创建文件
      await fileService.writeFile(filePath, content);

      // 读取文件
      const readContent = await fileService.readFile(filePath);
      expect(readContent).toBe(content);

      // 验证文件存在
      const exists = await fileService.fileExists(filePath);
      expect(exists).toBe(true);
    });

    it('应该能够更新任务文件', async () => {
      const filePath = path.join(workDir, 'T1.md');
      const originalContent = '# Original Task\n\nOriginal content.';
      const updatedContent = '# Updated Task\n\nUpdated content.';

      // 创建原始文件
      await fileService.writeFile(filePath, originalContent);

      // 更新文件
      await fileService.writeFile(filePath, updatedContent);

      // 验证更新
      const readContent = await fileService.readFile(filePath);
      expect(readContent).toBe(updatedContent);
    });

    it('应该能够删除任务文件', async () => {
      const filePath = path.join(workDir, 'T1.md');
      const content = '# Test Task';

      // 创建文件
      await fileService.writeFile(filePath, content);

      // 验证文件存在
      let exists = await fileService.fileExists(filePath);
      expect(exists).toBe(true);

      // 删除文件
      await fileService.deleteFile(filePath);

      // 验证文件已删除
      exists = await fileService.fileExists(filePath);
      expect(exists).toBe(false);
    });
  });

  describe('目录管理', () => {
    it('应该能够创建和验证故事目录', async () => {
      const storyDir = path.join(workDir, 'S1');

      // 创建目录
      await fileService.createDirectory(storyDir);

      // 验证目录存在
      const exists = await fileService.directoryExists(storyDir);
      expect(exists).toBe(true);

      // 在目录中创建文件
      const taskFile = path.join(storyDir, 'T1.md');
      await fileService.writeFile(taskFile, '# Task 1');

      // 列出目录内容
      const files = await fileService.listDirectory(storyDir);
      expect(files).toContain('T1.md');
    });
  });
});

MCP工具集成测试

// tests/integration/mcp-tools.test.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createMCPServer } from '@/server.js';
import { createTestWorkDir, cleanupTestWorkDir } from '../helpers/test-helpers.js';

describe('MCP工具集成测试', () => {
  let server: Server;
  let transport: StdioServerTransport;
  let workDir: string;

  beforeAll(async () => {
    workDir = createTestWorkDir('mcp-integration');
    server = createMCPServer();
    transport = new StdioServerTransport();
    await server.connect(transport);
  });

  afterAll(async () => {
    await server.close();
    cleanupTestWorkDir(workDir);
  });

  describe('完整的MCP工作流程', () => {
    it('应该能够执行完整的任务管理工作流程', async () => {
      // 1. 创建故事
      const createStoryResult = await server.request({
        method: 'tools/call',
        params: {
          name: 'create_story',
          arguments: {
            work_directory: workDir,
            title: 'Integration Test Story',
            description: 'A story for integration testing'
          }
        }
      });

      expect(createStoryResult.success).toBe(true);
      const storyId = createStoryResult.content.story_id;

      // 2. 创建多个任务
      const createTask1Result = await server.request({
        method: 'tools/call',
        params: {
          name: 'create_task',
          arguments: {
            work_directory: workDir,
            story_id: storyId,
            title: 'Integration Test Task 1',
            sequence_order: 1
          }
        }
      });

      const createTask2Result = await server.request({
        method: 'tools/call',
        params: {
          name: 'create_task',
          arguments: {
            work_directory: workDir,
            story_id: storyId,
            title: 'Integration Test Task 2',
            sequence_order: 2
          }
        }
      });

      expect(createTask1Result.success).toBe(true);
      expect(createTask2Result.success).toBe(true);

      // 3. 获取下一个任务
      const getNextTaskResult = await server.request({
        method: 'tools/call',
        params: {
          name: 'get_next_task',
          arguments: {
            work_directory: workDir,
            story_id: storyId
          }
        }
      });

      expect(getNextTaskResult.success).toBe(true);
      expect(getNextTaskResult.content.sequence_order).toBe(1);

      const taskId = getNextTaskResult.content.task_id;

      // 4. 更新任务文件
      const updateTaskFileResult = await server.request({
        method: 'tools/call',
        params: {
          name: 'update_task_file',
          arguments: {
            work_directory: workDir,
            task_id: taskId,
            file_path: `${workDir}/T1.md`
          }
        }
      });

      expect(updateTaskFileResult.success).toBe(true);

      // 5. 完成任务
      const completeTaskResult = await server.request({
        method: 'tools/call',
        params: {
          name: 'complete_task',
          arguments: {
            work_directory: workDir,
            task_id: taskId
          }
        }
      });

      expect(completeTaskResult.success).toBe(true);

      // 6. 获取下一个任务(应该是第二个任务)
      const getNextTask2Result = await server.request({
        method: 'tools/call',
        params: {
          name: 'get_next_task',
          arguments: {
            work_directory: workDir,
            story_id: storyId
          }
        }
      });

      expect(getNextTask2Result.success).toBe(true);
      expect(getNextTask2Result.content.sequence_order).toBe(2);

      // 7. 完成故事
      const completeStoryResult = await server.request({
        method: 'tools/call',
        params: {
          name: 'complete_story',
          arguments: {
            work_directory: workDir,
            story_id: storyId
          }
        }
      });

      expect(completeStoryResult.success).toBe(true);
    });
  });
});

3.2 Python集成测试

数据库集成测试

# tests/integration/test_database.py
import pytest
import tempfile
import os
from mcp_server.database.connection import DatabaseConnection
from mcp_server.repositories.story_repository import StoryRepository
from mcp_server.repositories.task_repository import TaskRepository

@pytest.fixture
def test_db():
    """创建测试数据库"""
    temp_dir = tempfile.mkdtemp()
    db_path = os.path.join(temp_dir, "test.db")
    
    connection = DatabaseConnection(db_path)
    connection.initialize()
    
    yield connection
    
    connection.close()
    os.remove(db_path)
    os.rmdir(temp_dir)

@pytest.fixture
def story_repository(test_db):
    """创建故事仓库"""
    return StoryRepository(test_db)

@pytest.fixture
def task_repository(test_db):
    """创建任务仓库"""
    return TaskRepository(test_db)

class TestDatabaseIntegration:
    def test_story_task_relationship(self, story_repository, task_repository):
        """测试故事和任务的关联关系"""
        # 创建故事
        story = story_repository.create({
            "id": "test-story-id",
            "title": "Test Story",
            "description": "Test description",
            "status": "active",
            "work_directory": "/test/workspace"
        })
        
        assert story is not None
        assert story.id == "test-story-id"
        
        # 为故事创建任务
        task1 = task_repository.create({
            "id": "test-task-1",
            "story_id": story.id,
            "title": "Test Task 1",
            "description": "First test task",
            "status": "pending",
            "sequence_order": 1
        })
        
        task2 = task_repository.create({
            "id": "test-task-2",
            "story_id": story.id,
            "title": "Test Task 2",
            "description": "Second test task",
            "status": "pending",
            "sequence_order": 2
        })
        
        assert task1 is not None
        assert task2 is not None
        
        # 验证关联关系
        story_tasks = task_repository.find_by_story_id(story.id)
        assert len(story_tasks) == 2
        assert story_tasks[0].sequence_order == 1
        assert story_tasks[1].sequence_order == 2

    def test_get_next_task(self, story_repository, task_repository):
        """测试获取下一个任务"""
        # 创建故事
        story = story_repository.create({
            "id": "test-story-id",
            "title": "Test Story",
            "description": "Test description",
            "status": "active",
            "work_directory": "/test/workspace"
        })
        
        # 创建多个任务
        task_repository.create({
            "id": "test-task-1",
            "story_id": story.id,
            "title": "Test Task 1",
            "status": "completed",
            "sequence_order": 1
        })
        
        task_repository.create({
            "id": "test-task-2",
            "story_id": story.id,
            "title": "Test Task 2",
            "status": "pending",
            "sequence_order": 2
        })
        
        task_repository.create({
            "id": "test-task-3",
            "story_id": story.id,
            "title": "Test Task 3",
            "status": "pending",
            "sequence_order": 3
        })
        
        # 获取下一个任务
        next_task = task_repository.find_next_task(story.id)
        assert next_task is not None
        assert next_task.id == "test-task-2"
        assert next_task.sequence_order == 2

    def test_transaction_rollback(self, story_repository, test_db):
        """测试事务回滚"""
        # 开始事务
        transaction = test_db.begin_transaction()
        
        try:
            # 在事务中创建故事
            story_repository.create_with_transaction(transaction, {
                "id": "test-story-id",
                "title": "Test Story",
                "description": "Test description",
                "status": "active",
                "work_directory": "/test/workspace"
            })
            
            # 模拟失败
            raise Exception("Simulated error")
        except Exception:
            # 回滚事务
            transaction.rollback()
        
        # 验证数据已回滚
        story = story_repository.find_by_id("test-story-id")
        assert story is None

    def test_transaction_commit(self, story_repository, test_db):
        """测试事务提交"""
        # 开始事务
        transaction = test_db.begin_transaction()
        
        try:
            # 在事务中创建故事
            story_repository.create_with_transaction(transaction, {
                "id": "test-story-id",
                "title": "Test Story",
                "description": "Test description",
                "status": "active",
                "work_directory": "/test/workspace"
            })
            
            # 提交事务
            transaction.commit()
        except Exception:
            transaction.rollback()
            raise
        
        # 验证数据已提交
        story = story_repository.find_by_id("test-story-id")
        assert story is not None
        assert story.id == "test-story-id"

文件系统集成测试

# tests/integration/test_file_system.py
import pytest
import tempfile
import os
from mcp_server.services.file_service import FileService

@pytest.fixture
def test_dir():
    """创建测试目录"""
    temp_dir = tempfile.mkdtemp()
    yield temp_dir
    # 清理测试目录
    for root, dirs, files in os.walk(temp_dir, topdown=False):
        for name in files:
            os.remove(os.path.join(root, name))
        for name in dirs:
            os.rmdir(os.path.join(root, name))
    os.rmdir(temp_dir)

@pytest.fixture
def file_service():
    """创建文件服务"""
    return FileService()

class TestFileSystemIntegration:
    def test_create_read_file(self, file_service, test_dir):
        """测试创建和读取文件"""
        file_path = os.path.join(test_dir, "T1.md")
        content = "# Test Task\n\nThis is a test task file."
        
        # 创建文件
        file_service.write_file(file_path, content)
        
        # 读取文件
        read_content = file_service.read_file(file_path)
        assert read_content == content
        
        # 验证文件存在
        exists = file_service.file_exists(file_path)
        assert exists is True

    def test_update_file(self, file_service, test_dir):
        """测试更新文件"""
        file_path = os.path.join(test_dir, "T1.md")
        original_content = "# Original Task\n\nOriginal content."
        updated_content = "# Updated Task\n\nUpdated content."
        
        # 创建原始文件
        file_service.write_file(file_path, original_content)
        
        # 更新文件
        file_service.write_file(file_path, updated_content)
        
        # 验证更新
        read_content = file_service.read_file(file_path)
        assert read_content == updated_content

    def test_delete_file(self, file_service, test_dir):
        """测试删除文件"""
        file_path = os.path.join(test_dir, "T1.md")
        content = "# Test Task"
        
        # 创建文件
        file_service.write_file(file_path, content)
        
        # 验证文件存在
        exists = file_service.file_exists(file_path)
        assert exists is True
        
        # 删除文件
        file_service.delete_file(file_path)
        
        # 验证文件已删除
        exists = file_service.file_exists(file_path)
        assert exists is False

    def test_directory_operations(self, file_service, test_dir):
        """测试目录操作"""
        story_dir = os.path.join(test_dir, "S1")
        
        # 创建目录
        file_service.create_directory(story_dir)
        
        # 验证目录存在
        exists = file_service.directory_exists(story_dir)
        assert exists is True
        
        # 在目录中创建文件
        task_file = os.path.join(story_dir, "T1.md")
        file_service.write_file(task_file, "# Task 1")
        
        # 列出目录内容
        files = file_service.list_directory(story_dir)
        assert "T1.md" in files

MCP工具集成测试

# tests/integration/test_mcp_tools.py
import pytest
import asyncio
from mcp_server.server import create_mcp_server
from mcp_server.client import MCPClient

@pytest.fixture
async def mcp_server():
    """创建MCP服务器"""
    server = create_mcp_server()
    await server.start()
    yield server
    await server.stop()

@pytest.fixture
async def mcp_client(mcp_server):
    """创建MCP客户端"""
    client = MCPClient()
    await client.connect()
    yield client
    await client.disconnect()

@pytest.fixture
def test_workspace():
    """创建测试工作空间"""
    import tempfile
    import os
    
    temp_dir = tempfile.mkdtemp()
    yield temp_dir
    
    # 清理
    for root, dirs, files in os.walk(temp_dir, topdown=False):
        for name in files:
            os.remove(os.path.join(root, name))
        for name in dirs:
            os.rmdir(os.path.join(root, name))
    os.rmdir(temp_dir)

class TestMCPToolsIntegration:
    @pytest.mark.asyncio
    async def test_complete_task_workflow(self, mcp_client, test_workspace):
        """测试完整的任务管理工作流程"""
        # 1. 创建故事
        create_story_result = await mcp_client.call_tool("create_story", {
            "work_directory": test_workspace,
            "title": "Integration Test Story",
            "description": "A story for integration testing"
        })
        
        assert create_story_result["success"] is True
        story_id = create_story_result["content"]["story_id"]
        
        # 2. 创建多个任务
        create_task1_result = await mcp_client.call_tool("create_task", {
            "work_directory": test_workspace,
            "story_id": story_id,
            "title": "Integration Test Task 1",
            "sequence_order": 1
        })
        
        create_task2_result = await mcp_client.call_tool("create_task", {
            "work_directory": test_workspace,
            "story_id": story_id,
            "title": "Integration Test Task 2",
            "sequence_order": 2
        })
        
        assert create_task1_result["success"] is True
        assert create_task2_result["success"] is True
        
        # 3. 获取下一个任务
        get_next_task_result = await mcp_client.call_tool("get_next_task", {
            "work_directory": test_workspace,
            "story_id": story_id
        })
        
        assert get_next_task_result["success"] is True
        assert get_next_task_result["content"]["sequence_order"] == 1
        task_id = get_next_task_result["content"]["task_id"]
        
        # 4. 更新任务文件
        update_task_file_result = await mcp_client.call_tool("update_task_file", {
            "work_directory": test_workspace,
            "task_id": task_id,
            "file_path": f"{test_workspace}/T1.md"
        })
        
        assert update_task_file_result["success"] is True
        
        # 5. 完成任务
        complete_task_result = await mcp_client.call_tool("complete_task", {
            "work_directory": test_workspace,
            "task_id": task_id
        })
        
        assert complete_task_result["success"] is True
        
        # 6. 获取下一个任务(应该是第二个任务)
        get_next_task2_result = await mcp_client.call_tool("get_next_task", {
            "work_directory": test_workspace,
            "story_id": story_id
        })
        
        assert get_next_task2_result["success"] is True
        assert get_next_task2_result["content"]["sequence_order"] == 2
        
        # 7. 完成故事
        complete_story_result = await mcp_client.call_tool("complete_story", {
            "work_directory": test_workspace,
            "story_id": story_id
        })
        
        assert complete_story_result["success"] is True

3.3 Go集成测试

数据库集成测试

// integration/database_test.go
package integration

import (
	"os"
	"path/filepath"
	"testing"
	"time"
	"mcp-server/database"
	"mcp-server/repositories"
	"mcp-server/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type DatabaseIntegrationTestSuite struct {
	suite.Suite
	dbConn        *database.Connection
	storyRepo     *repositories.StoryRepository
	taskRepo      *repositories.TaskRepository
	testDir       string
}

func (suite *DatabaseIntegrationTestSuite) SetupSuite() {
	// 创建测试目录
	tempDir, err := os.MkdirTemp("", "mcp-db-test")
	suite.Require().NoError(err)
	suite.testDir = tempDir

	// 初始化数据库连接
	dbPath := filepath.Join(tempDir, "test.db")
	suite.dbConn = database.NewConnection(dbPath)
	err = suite.dbConn.Initialize()
	suite.Require().NoError(err)

	// 创建仓库
	suite.storyRepo = repositories.NewStoryRepository(suite.dbConn)
	suite.taskRepo = repositories.NewTaskRepository(suite.dbConn)
}

func (suite *DatabaseIntegrationTestSuite) TearDownSuite() {
	// 关闭数据库连接
	suite.dbConn.Close()

	// 清理测试目录
	os.RemoveAll(suite.testDir)
}

func (suite *DatabaseIntegrationTestSuite) SetupTest() {
	// 每个测试前清理数据库
	suite.dbConn.Execute("DELETE FROM task")
	suite.dbConn.Execute("DELETE FROM story")
}

func (suite *DatabaseIntegrationTestSuite) TestStoryTaskRelationship() {
	// 创建故事
	story := &models.Story{
		ID:            "test-story-id",
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: suite.testDir,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	createdStory, err := suite.storyRepo.Create(story)
	suite.NoError(err)
	suite.NotNil(createdStory)
	suite.Equal("test-story-id", createdStory.ID)

	// 为故事创建任务
	task1 := &models.Task{
		ID:            "test-task-1",
		StoryID:       story.ID,
		Title:         "Test Task 1",
		Description:   "First test task",
		Status:        "pending",
		SequenceOrder: 1,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	task2 := &models.Task{
		ID:            "test-task-2",
		StoryID:       story.ID,
		Title:         "Test Task 2",
		Description:   "Second test task",
		Status:        "pending",
		SequenceOrder: 2,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	createdTask1, err := suite.taskRepo.Create(task1)
	suite.NoError(err)
	suite.NotNil(createdTask1)

	createdTask2, err := suite.taskRepo.Create(task2)
	suite.NoError(err)
	suite.NotNil(createdTask2)

	// 验证关联关系
	storyTasks, err := suite.taskRepo.FindByStoryID(story.ID)
	suite.NoError(err)
	suite.Len(storyTasks, 2)
	suite.Equal(1, storyTasks[0].SequenceOrder)
	suite.Equal(2, storyTasks[1].SequenceOrder)
}

func (suite *DatabaseIntegrationTestSuite) TestGetNextTask() {
	// 创建故事
	story := &models.Story{
		ID:            "test-story-id",
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: suite.testDir,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	createdStory, err := suite.storyRepo.Create(story)
	suite.NoError(err)

	// 创建多个任务
	suite.taskRepo.Create(&models.Task{
		ID:            "test-task-1",
		StoryID:       createdStory.ID,
		Title:         "Test Task 1",
		Status:        "completed",
		SequenceOrder: 1,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	})

	suite.taskRepo.Create(&models.Task{
		ID:            "test-task-2",
		StoryID:       createdStory.ID,
		Title:         "Test Task 2",
		Status:        "pending",
		SequenceOrder: 2,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	})

	suite.taskRepo.Create(&models.Task{
		ID:            "test-task-3",
		StoryID:       createdStory.ID,
		Title:         "Test Task 3",
		Status:        "pending",
		SequenceOrder: 3,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	})

	// 获取下一个任务
	nextTask, err := suite.taskRepo.FindNextTask(createdStory.ID)
	suite.NoError(err)
	suite.NotNil(nextTask)
	suite.Equal("test-task-2", nextTask.ID)
	suite.Equal(2, nextTask.SequenceOrder)
}

func (suite *DatabaseIntegrationTestSuite) TestTransactionRollback() {
	// 开始事务
	transaction, err := suite.dbConn.BeginTransaction()
	suite.NoError(err)

	// 在事务中创建故事
	story := &models.Story{
		ID:            "test-story-id",
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: suite.testDir,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	_, err = suite.storyRepo.CreateWithTransaction(transaction, story)
	suite.NoError(err)

	// 模拟失败
	err = transaction.Rollback()
	suite.NoError(err)

	// 验证数据已回滚
	foundStory, err := suite.storyRepo.FindByID("test-story-id")
	suite.NoError(err)
	suite.Nil(foundStory)
}

func (suite *DatabaseIntegrationTestSuite) TestTransactionCommit() {
	// 开始事务
	transaction, err := suite.dbConn.BeginTransaction()
	suite.NoError(err)

	// 在事务中创建故事
	story := &models.Story{
		ID:            "test-story-id",
		Title:         "Test Story",
		Description:   "Test description",
		Status:        "active",
		WorkDirectory: suite.testDir,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}

	_, err = suite.storyRepo.CreateWithTransaction(transaction, story)
	suite.NoError(err)

	// 提交事务
	err = transaction.Commit()
	suite.NoError(err)

	// 验证数据已提交
	foundStory, err := suite.storyRepo.FindByID("test-story-id")
	suite.NoError(err)
	suite.NotNil(foundStory)
	suite.Equal("test-story-id", foundStory.ID)
}

func TestDatabaseIntegrationTestSuite(t *testing.T) {
	suite.Run(t, new(DatabaseIntegrationTestSuite))
}

文件系统集成测试

// integration/file_system_test.go
package integration

import (
	"os"
	"path/filepath"
	"testing"
	"mcp-server/services"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type FileSystemIntegrationTestSuite struct {
	suite.Suite
	fileService *services.FileService
	testDir     string
}

func (suite *FileSystemIntegrationTestSuite) SetupSuite() {
	// 创建测试目录
	tempDir, err := os.MkdirTemp("", "mcp-fs-test")
	suite.Require().NoError(err)
	suite.testDir = tempDir

	// 创建文件服务
	suite.fileService = services.NewFileService()
}

func (suite *FileSystemIntegrationTestSuite) TearDownSuite() {
	// 清理测试目录
	os.RemoveAll(suite.testDir)
}

func (suite *FileSystemIntegrationTestSuite) TestCreateReadFile() {
	filePath := filepath.Join(suite.testDir, "T1.md")
	content := "# Test Task\n\nThis is a test task file."

	// 创建文件
	err := suite.fileService.WriteFile(filePath, content)
	suite.NoError(err)

	// 读取文件
	readContent, err := suite.fileService.ReadFile(filePath)
	suite.NoError(err)
	suite.Equal(content, readContent)

	// 验证文件存在
	exists, err := suite.fileService.FileExists(filePath)
	suite.NoError(err)
	suite.True(exists)
}

func (suite *FileSystemIntegrationTestSuite) TestUpdateFile() {
	filePath := filepath.Join(suite.testDir, "T1.md")
	originalContent := "# Original Task\n\nOriginal content."
	updatedContent := "# Updated Task\n\nUpdated content."

	// 创建原始文件
	err := suite.fileService.WriteFile(filePath, originalContent)
	suite.NoError(err)

	// 更新文件
	err = suite.fileService.WriteFile(filePath, updatedContent)
	suite.NoError(err)

	// 验证更新
	readContent, err := suite.fileService.ReadFile(filePath)
	suite.NoError(err)
	suite.Equal(updatedContent, readContent)
}

func (suite *FileSystemIntegrationTestSuite) TestDeleteFile() {
	filePath := filepath.Join(suite.testDir, "T1.md")
	content := "# Test Task"

	// 创建文件
	err := suite.fileService.WriteFile(filePath, content)
	suite.NoError(err)

	// 验证文件存在
	exists, err := suite.fileService.FileExists(filePath)
	suite.NoError(err)
	suite.True(exists)

	// 删除文件
	err = suite.fileService.DeleteFile(filePath)
	suite.NoError(err)

	// 验证文件已删除
	exists, err = suite.fileService.FileExists(filePath)
	suite.NoError(err)
	suite.False(exists)
}

func (suite *FileSystemIntegrationTestSuite) TestDirectoryOperations() {
	storyDir := filepath.Join(suite.testDir, "S1")

	// 创建目录
	err := suite.fileService.CreateDirectory(storyDir)
	suite.NoError(err)

	// 验证目录存在
	exists, err := suite.fileService.DirectoryExists(storyDir)
	suite.NoError(err)
	suite.True(exists)

	// 在目录中创建文件
	taskFile := filepath.Join(storyDir, "T1.md")
	err = suite.fileService.WriteFile(taskFile, "# Task 1")
	suite.NoError(err)

	// 列出目录内容
	files, err := suite.fileService.ListDirectory(storyDir)
	suite.NoError(err)
	suite.Contains(files, "T1.md")
}

func TestFileSystemIntegrationTestSuite(t *testing.T) {
	suite.Run(t, new(FileSystemIntegrationTestSuite))
}

第四章:端到端测试实现

端到端测试模拟真实用户场景,验证整个系统从输入到输出的完整流程。对于MCP服务器,端到端测试通常涉及模拟AI Agent与MCP服务器的完整交互。

4.1 TypeScript端到端测试

完整工作流程测试

// tests/e2e/complete-workflow.test.ts
import { MCPClient } from '@/testing/mcp-client.js';
import { createTestWorkDir, cleanupTestWorkDir } from '../helpers/test-helpers.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

describe('完整工作流程端到端测试', () => {
  let server: Server;
  let client: MCPClient;
  let workDir: string;

  beforeAll(async () => {
    // 创建测试工作目录
    workDir = createTestWorkDir('e2e-workflow');
    
    // 启动MCP服务器
    server = await startMCPServer();
    
    // 创建MCP客户端
    client = new MCPClient();
    await client.connect(server);
  });

  afterAll(async () => {
    await client.disconnect();
    await server.close();
    cleanupTestWorkDir(workDir);
  });

  describe('复杂任务管理场景', () => {
    it('应该能够处理多故事多任务的复杂场景', async () => {
      // 创建第一个故事
      const story1Result = await client.callTool('create_story', {
        work_directory: workDir,
        title: '学习TypeScript',
        description: '系统学习TypeScript编程语言'
      });

      expect(story1Result.success).toBe(true);
      const story1Id = story1Result.content.story_id;

      // 创建第二个故事
      const story2Result = await client.callTool('create_story', {
        work_directory: workDir,
        title: '学习React',
        description: '学习React框架开发'
      });

      expect(story2Result.success).toBe(true);
      const story2Id = story2Result.content.story_id;

      // 为第一个故事创建多个任务
      const tasks1 = [
        { title: '安装TypeScript', sequence_order: 1 },
        { title: '学习基础语法', sequence_order: 2 },
        { title: '实践项目', sequence_order: 3 }
      ];

      for (const task of tasks1) {
        const result = await client.callTool('create_task', {
          work_directory: workDir,
          story_id: story1Id,
          title: task.title,
          sequence_order: task.sequence_order
        });
        expect(result.success).toBe(true);
      }

      // 为第二个故事创建任务
      const tasks2 = [
        { title: '学习JSX', sequence_order: 1 },
        { title: '组件开发', sequence_order: 2 }
      ];

      for (const task of tasks2) {
        const result = await client.callTool('create_task', {
          work_directory: workDir,
          story_id: story2Id,
          title: task.title,
          sequence_order: task.sequence_order
        });
        expect(result.success).toBe(true);
      }

      // 交替执行两个故事的任务
      const executionOrder = [
        { story_id: story1Id, expected_sequence: 1 },
        { story_id: story2Id, expected_sequence: 1 },
        { story_id: story1Id, expected_sequence: 2 },
        { story_id: story2Id, expected_sequence: 2 },
        { story_id: story1Id, expected_sequence: 3 }
      ];

      for (const step of executionOrder) {
        // 获取下一个任务
        const nextTaskResult = await client.callTool('get_next_task', {
          work_directory: workDir,
          story_id: step.story_id
        });

        expect(nextTaskResult.success).toBe(true);
        expect(nextTaskResult.content.sequence_order).toBe(step.expected_sequence);

        // 更新任务文件
        const taskFileResult = await client.callTool('update_task_file', {
          work_directory: workDir,
          task_id: nextTaskResult.content.task_id,
          file_path: `${workDir}/S${step.story_id}/T${step.expected_sequence}.md`
        });

        expect(taskFileResult.success).toBe(true);

        // 完成任务
        const completeTaskResult = await client.callTool('complete_task', {
          work_directory: workDir,
          task_id: nextTaskResult.content.task_id
        });

        expect(completeTaskResult.success).toBe(true);
      }

      // 完成两个故事
      const completeStory1Result = await client.callTool('complete_story', {
        work_directory: workDir,
        story_id: story1Id
      });

      const completeStory2Result = await client.callTool('complete_story', {
        work_directory: workDir,
        story_id: story2Id
      });

      expect(completeStory1Result.success).toBe(true);
      expect(completeStory2Result.success).toBe(true);

      // 验证所有任务文件都已创建
      const fs = require('fs');
      const story1Dir = `${workDir}/S${story1Id}`;
      const story2Dir = `${workDir}/S${story2Id}`;

      expect(fs.existsSync(story1Dir)).toBe(true);
      expect(fs.existsSync(story2Dir)).toBe(true);

      const story1Files = fs.readdirSync(story1Dir);
      const story2Files = fs.readdirSync(story2Dir);

      expect(story1Files).toContain('T1.md');
      expect(story1Files).toContain('T2.md');
      expect(story1Files).toContain('T3.md');
      expect(story2Files).toContain('T1.md');
      expect(story2Files).toContain('T2.md');
    });
  });

  describe('错误处理场景', () => {
    it('应该能够处理无效参数错误', async () => {
      // 测试创建故事时缺少必需参数
      const invalidStoryResult = await client.callTool('create_story', {
        work_directory: workDir
        // 缺少title参数
      });

      expect(invalidStoryResult.success).toBe(false);
      expect(invalidStoryResult.error).toContain('title');

      // 测试创建任务时引用不存在的故事
      const invalidTaskResult = await client.callTool('create_task', {
        work_directory: workDir,
        story_id: 'nonexistent-story-id',
        title: 'Invalid Task',
        sequence_order: 1
      });

      expect(invalidTaskResult.success).toBe(false);
      expect(invalidTaskResult.error).toContain('story not found');
    });

    it('应该能够处理并发操作冲突', async () => {
      // 创建故事
      const storyResult = await client.callTool('create_story', {
        work_directory: workDir,
        title: 'Concurrency Test Story',
        description: 'Testing concurrent operations'
      });

      const storyId = storyResult.content.story_id;

      // 创建初始任务
      await client.callTool('create_task', {
        work_directory: workDir,
        story_id: storyId,
        title: 'Initial Task',
        sequence_order: 1
      });

      // 模拟两个客户端同时获取下一个任务
      const nextTask1 = await client.callTool('get_next_task', {
        work_directory: workDir,
        story_id: storyId
      });

      const nextTask2 = await client.callTool('get_next_task', {
        work_directory: workDir,
        story_id: storyId
      });

      // 第一个应该成功获取任务
      expect(nextTask1.success).toBe(true);
      
      // 第二个应该获取到null(任务已被第一个客户端获取)
      expect(nextTask2.success).toBe(true);
      expect(nextTask2.content).toBeNull();
    });
  });

  describe('性能测试', () => {
    it('应该能够在合理时间内处理大量操作', async () => {
      const startTime = Date.now();
      const storyCount = 10;
      const tasksPerStory = 20;

      // 创建多个故事
      const storyIds = [];
      for (let i = 0; i < storyCount; i++) {
        const storyResult = await client.callTool('create_story', {
          work_directory: workDir,
          title: `Performance Test Story ${i}`,
          description: `Testing performance with story ${i}`
        });

        storyIds.push(storyResult.content.story_id);
      }

      // 为每个故事创建多个任务
      for (const storyId of storyIds) {
        for (let j = 1; j <= tasksPerStory; j++) {
          await client.callTool('create_task', {
            work_directory: workDir,
            story_id: storyId,
            title: `Task ${j}`,
            sequence_order: j
          });
        }
      }

      const endTime = Date.now();
      const duration = endTime - startTime;

      // 验证性能:创建10个故事和200个任务应该在合理时间内完成
      expect(duration).toBeLessThan(10000); // 10秒内完成
      console.log(`创建 ${storyCount} 个故事和 ${storyCount * tasksPerStory} 个任务耗时: ${duration}ms`);
    });
  });
});

async function startMCPServer(): Promise<Server> {
  const { createMCPServer } = await import('@/server.js');
  return createMCPServer();
}

模拟AI Agent测试

// tests/e2e/ai-agent-simulation.test.ts
import { MCPClient } from '@/testing/mcp-client.js';
import { createTestWorkDir, cleanupTestWorkDir } from '../helpers/test-helpers.js';
import { AIAgentSimulator } from '@/testing/ai-agent-simulator.js';

describe('AI Agent模拟测试', () => {
  let client: MCPClient;
  let aiAgent: AIAgentSimulator;
  let workDir: string;

  beforeAll(async () => {
    workDir = createTestWorkDir('ai-agent-simulation');
    const server = await startMCPServer();
    
    client = new MCPClient();
    await client.connect(server);
    
    aiAgent = new AIAgentSimulator(client);
  });

  afterAll(async () => {
    await client.disconnect();
    cleanupTestWorkDir(workDir);
  });

  describe('智能任务规划', () => {
    it('AI Agent应该能够智能规划复杂任务', async () => {
      // AI Agent接收一个复杂目标
      const goal = '开发一个完整的Web应用,包括前端、后端和数据库';
      
      // AI Agent分析目标并创建故事
      const stories = await aiAgent.planStories(goal, workDir);
      
      expect(stories.length).toBeGreaterThan(1);
      expect(stories.some(s => s.title.includes('前端'))).toBe(true);
      expect(stories.some(s => s.title.includes('后端'))).toBe(true);
      expect(stories.some(s => s.title.includes('数据库'))).toBe(true);

      // AI Agent为每个故事规划任务
      for (const story of stories) {
        const tasks = await aiAgent.planTasks(story.id, workDir);
        expect(tasks.length).toBeGreaterThan(0);
        
        // 验证任务顺序合理
        for (let i = 0; i < tasks.length - 1; i++) {
          expect(tasks[i].sequence_order).toBeLessThan(tasks[i + 1].sequence_order);
        }
      }
    });
  });

  describe('自适应执行', () => {
    it('AI Agent应该能够根据执行情况调整计划', async () => {
      // 创建一个学习编程的故事
      const storyResult = await client.callTool('create_story', {
        work_directory: workDir,
        title: '学习Python编程',
        description: '从零开始学习Python编程语言'
      });

      const storyId = storyResult.content.story_id;

      // AI Agent初始规划任务
      const initialTasks = [
        { title: '安装Python环境', sequence_order: 1 },
        { title: '学习基础语法', sequence_order: 2 },
        { title: '学习数据结构', sequence_order: 3 },
        { title: '学习面向对象编程', sequence_order: 4 },
        { title: '实践项目开发', sequence_order: 5 }
      ];

      for (const task of initialTasks) {
        await client.callTool('create_task', {
          work_directory: workDir,
          story_id: storyId,
          title: task.title,
          sequence_order: task.sequence_order
        });
      }

      // AI Agent开始执行任务
      let currentTask = await aiAgent.getNextTask(storyId, workDir);
      expect(currentTask.title).toBe('安装Python环境');

      // 模拟任务执行失败,AI Agent需要调整计划
      const executionResult = await aiAgent.executeTask(currentTask.id, {
        success: false,
        reason: '环境配置复杂,需要更多步骤'
      });

      expect(executionResult.success).toBe(false);

      // AI Agent应该能够分解复杂任务
      const refinedTasks = await aiAgent.refineTask(currentTask.id, {
        addSteps: [
          '选择Python版本',
          '下载安装包',
          '配置环境变量',
          '验证安装'
        ]
      });

      expect(refinedTasks.length).toBeGreaterThan(1);

      // AI Agent继续执行细化后的任务
      for (const task of refinedTasks) {
        const result = await aiAgent.executeTask(task.id, { success: true });
        expect(result.success).toBe(true);
      }

      // 验证原任务已标记为完成
      const updatedTask = await aiAgent.getTask(currentTask.id, workDir);
      expect(updatedTask.status).toBe('completed');
    });
  });

  describe('学习与优化', () => {
    it('AI Agent应该能够从执行历史中学习', async () => {
      // 模拟AI Agent执行多个相似项目
      const projects = [
        { name: '电商网站', complexity: 'high' },
        { name: '博客系统', complexity: 'medium' },
        { name: '任务管理工具', complexity: 'low' }
      ];

      const executionHistory = [];

      for (const project of projects) {
        const projectResult = await aiAgent.executeProject(project, workDir);
        executionHistory.push(projectResult);
      }

      // AI Agent分析执行历史
      const insights = await aiAgent.analyzeExecutionHistory(executionHistory);

      expect(insights.patterns.length).toBeGreaterThan(0);
      expect(insights.recommendations.length).toBeGreaterThan(0);

      // 验证AI Agent能够识别常见模式
      expect(insights.patterns.some(p => p.type === 'common_task_sequence')).toBe(true);

      // 验证AI Agent能够提供优化建议
      expect(insights.recommendations.some(r => r.type === 'task_optimization')).toBe(true);

      // AI Agent应用学习到的知识规划新项目
      const newProjectPlan = await aiAgent.planProjectWithLearning(
        '新的Web应用',
        insights,
        workDir
      );

      expect(newProjectPlan.estimatedDuration).toBeLessThan(
        projects.reduce((sum, p) => sum + p.estimatedDuration, 0) / projects.length
      );
    });
  });
});

async function startMCPServer(): Promise<Server> {
  const { createMCPServer } = await import('@/server.js');
  return createMCPServer();
}

4.2 Python端到端测试

完整工作流程测试

# tests/e2e/test_complete_workflow.py
import pytest
import asyncio
import tempfile
import os
from mcp_server.testing.mcp_client import MCPClient
from mcp_server.testing.ai_agent_simulator import AIAgentSimulator

@pytest.fixture
async def test_workspace():
    """创建测试工作空间"""
    temp_dir = tempfile.mkdtemp()
    yield temp_dir
    
    # 清理
    for root, dirs, files in os.walk(temp_dir, topdown=False):
        for name in files:
            os.remove(os.path.join(root, name))
        for name in dirs:
            os.rmdir(os.path.join(root, name))
    os.rmdir(temp_dir)

@pytest.fixture
async def mcp_client():
    """创建MCP客户端"""
    from mcp_server.server import create_mcp_server
    
    server = create_mcp_server()
    await server.start()
    
    client = MCPClient()
    await client.connect(server)
    
    yield client
    
    await client.disconnect()
    await server.stop()

@pytest.fixture
async def ai_agent(mcp_client):
    """创建AI Agent模拟器"""
    return AIAgentSimulator(mcp_client)

@pytest.mark.asyncio
class TestCompleteWorkflow:
    async def test_complex_task_management_scenario(self, mcp_client, test_workspace):
        """测试复杂任务管理场景"""
        # 创建第一个故事
        story1_result = await mcp_client.call_tool("create_story", {
            "work_directory": test_workspace,
            "title": "学习Python",
            "description": "系统学习Python编程语言"
        })
        
        assert story1_result["success"] is True
        story1_id = story1_result["content"]["story_id"]
        
        # 创建第二个故事
        story2_result = await mcp_client.call_tool("create_story", {
            "work_directory": test_workspace,
            "title": "学习Django",
            "description": "学习Django Web框架"
        })
        
        assert story2_result["success"] is True
        story2_id = story2_result["content"]["story_id"]
        
        # 为第一个故事创建多个任务
        tasks1 = [
            {"title": "安装Python", "sequence_order": 1},
            {"title": "学习基础语法", "sequence_order": 2},
            {"title": "学习高级特性", "sequence_order": 3}
        ]
        
        for task in tasks1:
            result = await mcp_client.call_tool("create_task", {
                "work_directory": test_workspace,
                "story_id": story1_id,
                "title": task["title"],
                "sequence_order": task["sequence_order"]
            })
            assert result["success"] is True
        
        # 为第二个故事创建任务
        tasks2 = [
            {"title": "学习Django基础", "sequence_order": 1},
            {"title": "创建第一个项目", "sequence_order": 2}
        ]
        
        for task in tasks2:
            result = await mcp_client.call_tool("create_task", {
                "work_directory": test_workspace,
                "story_id": story2_id,
                "title": task["title"],
                "sequence_order": task["sequence_order"]
            })
            assert result["success"] is True
        
        # 交替执行两个故事的任务
        execution_order = [
            {"story_id": story1_id, "expected_sequence": 1},
            {"story_id": story2_id, "expected_sequence": 1},
            {"story_id": story1_id, "expected_sequence": 2},
            {"story_id": story2_id, "expected_sequence": 2},
            {"story_id": story1_id, "expected_sequence": 3}
        ]
        
        for step in execution_order:
            # 获取下一个任务
            next_task_result = await mcp_client.call_tool("get_next_task", {
                "work_directory": test_workspace,
                "story_id": step["story_id"]
            })
            
            assert next_task_result["success"] is True
            assert next_task_result["content"]["sequence_order"] == step["expected_sequence"]
            
            # 更新任务文件
            task_file_result = await mcp_client.call_tool("update_task_file", {
                "work_directory": test_workspace,
                "task_id": next_task_result["content"]["task_id"],
                "file_path": f"{test_workspace}/S{step['story_id']}/T{step['expected_sequence']}.md"
            })
            
            assert task_file_result["success"] is True
            
            # 完成任务
            complete_task_result = await mcp_client.call_tool("complete_task", {
                "work_directory": test_workspace,
                "task_id": next_task_result["content"]["task_id"]
            })
            
            assert complete_task_result["success"] is True
        
        # 完成两个故事
        complete_story1_result = await mcp_client.call_tool("complete_story", {
            "work_directory": test_workspace,
            "story_id": story1_id
        })
        
        complete_story2_result = await mcp_client.call_tool("complete_story", {
            "work_directory": test_workspace,
            "story_id": story2_id
        })
        
        assert complete_story1_result["success"] is True
        assert complete_story2_result["success"] is True
        
        # 验证所有任务文件都已创建
        story1_dir = f"{test_workspace}/S{story1_id}"
        story2_dir = f"{test_workspace}/S{story2_id}"
        
        assert os.path.exists(story1_dir) is True
        assert os.path.exists(story2_dir) is True
        
        story1_files = os.listdir(story1_dir)
        story2_files = os.listdir(story2_dir)
        
        assert "T1.md" in story1_files
        assert "T2.md" in story1_files
        assert "T3.md" in story1_files
        assert "T1.md" in story2_files
        assert "T2.md" in story2_files

    async def test_error_handling_scenarios(self, mcp_client, test_workspace):
        """测试错误处理场景"""
        # 测试创建故事时缺少必需参数
        invalid_story_result = await mcp_client.call_tool("create_story", {
            "work_directory": test_workspace
            # 缺少title参数
        })
        
        assert invalid_story_result["success"] is False
        assert "title" in invalid_story_result["error"]
        
        # 测试创建任务时引用不存在的故事
        invalid_task_result = await mcp_client.call_tool("create_task", {
            "work_directory": test_workspace,
            "story_id": "nonexistent-story-id",
            "title": "Invalid Task",
            "sequence_order": 1
        })
        
        assert invalid_task_result["success"] is False
        assert "story not found" in invalid_task_result["error"]

    async def test_performance_test(self, mcp_client, test_workspace):
        """测试性能"""
        import time
        
        start_time = time.time()
        story_count = 10
        tasks_per_story = 20
        
        # 创建多个故事
        story_ids = []
        for i in range(story_count):
            story_result = await mcp_client.call_tool("create_story", {
                "work_directory": test_workspace,
                "title": f"Performance Test Story {i}",
                "description": f"Testing performance with story {i}"
            })
            
            story_ids.append(story_result["content"]["story_id"])
        
        # 为每个故事创建多个任务
        for story_id in story_ids:
            for j in range(1, tasks_per_story + 1):
                await mcp_client.call_tool("create_task", {
                    "work_directory": test_workspace,
                    "story_id": story_id,
                    "title": f"Task {j}",
                    "sequence_order": j
                })
        
        end_time = time.time()
        duration = (end_time - start_time) * 1000  # 转换为毫秒
        
        # 验证性能:创建10个故事和200个任务应该在合理时间内完成
        assert duration < 10000  # 10秒内完成
        print(f"创建 {story_count} 个故事和 {story_count * tasks_per_story} 个任务耗时: {duration:.2f}ms")

@pytest.mark.asyncio
class TestAIAgentSimulation:
    async def test_intelligent_task_planning(self, ai_agent, test_workspace):
        """测试智能任务规划"""
        # AI Agent接收一个复杂目标
        goal = "开发一个完整的Web应用,包括前端、后端和数据库"
        
        # AI Agent分析目标并创建故事
        stories = await ai_agent.plan_stories(goal, test_workspace)
        
        assert len(stories) > 1
        assert any("前端" in story.title for story in stories)
        assert any("后端" in story.title for story in stories)
        assert any("数据库" in story.title for story in stories)
        
        # AI Agent为每个故事规划任务
        for story in stories:
            tasks = await ai_agent.plan_tasks(story.id, test_workspace)
            assert len(tasks) > 0
            
            # 验证任务顺序合理
            for i in range(len(tasks) - 1):
                assert tasks[i].sequence_order < tasks[i + 1].sequence_order

    async def test_adaptive_execution(self, ai_agent, test_workspace, mcp_client):
        """测试自适应执行"""
        # 创建一个学习编程的故事
        story_result = await mcp_client.call_tool("create_story", {
            "work_directory": test_workspace,
            "title": "学习Python编程",
            "description": "从零开始学习Python编程语言"
        })
        
        story_id = story_result["content"]["story_id"]
        
        # AI Agent初始规划任务
        initial_tasks = [
            {"title": "安装Python环境", "sequence_order": 1},
            {"title": "学习基础语法", "sequence_order": 2},
            {"title": "学习数据结构", "sequence_order": 3},
            {"title": "学习面向对象编程", "sequence_order": 4},
            {"title": "实践项目开发", "sequence_order": 5}
        ]
        
        for task in initial_tasks:
            await mcp_client.call_tool("create_task", {
                "work_directory": test_workspace,
                "story_id": story_id,
                "title": task["title"],
                "sequence_order": task["sequence_order"]
            })
        
        # AI Agent开始执行任务
        current_task = await ai_agent.get_next_task(story_id, test_workspace)
        assert current_task.title == "安装Python环境"
        
        # 模拟任务执行失败,AI Agent需要调整计划
        execution_result = await ai_agent.execute_task(current_task.id, {
            "success": False,
            "reason": "环境配置复杂,需要更多步骤"
        })
        
        assert execution_result["success"] is False
        
        # AI Agent应该能够分解复杂任务
        refined_tasks = await ai_agent.refine_task(current_task.id, {
            "add_steps": [
                "选择Python版本",
                "下载安装包",
                "配置环境变量",
                "验证安装"
            ]
        })
        
        assert len(refined_tasks) > 1
        
        # AI Agent继续执行细化后的任务
        for task in refined_tasks:
            result = await ai_agent.execute_task(task.id, {"success": True})
            assert result["success"] is True
        
        # 验证原任务已标记为完成
        updated_task = await ai_agent.get_task(current_task.id, test_workspace)
        assert updated_task.status == "completed"

    async def test_learning_and_optimization(self, ai_agent, test_workspace):
        """测试学习与优化"""
        # 模拟AI Agent执行多个相似项目
        projects = [
            {"name": "电商网站", "complexity": "high"},
            {"name": "博客系统", "complexity": "medium"},
            {"name": "任务管理工具", "complexity": "low"}
        ]
        
        execution_history = []
        
        for project in projects:
            project_result = await ai_agent.execute_project(project, test_workspace)
            execution_history.append(project_result)
        
        # AI Agent分析执行历史
        insights = await ai_agent.analyze_execution_history(execution_history)
        
        assert len(insights["patterns"]) > 0
        assert len(insights["recommendations"]) > 0
        
        # 验证AI Agent能够识别常见模式
        assert any(p["type"] == "common_task_sequence" for p in insights["patterns"])
        
        # 验证AI Agent能够提供优化建议
        assert any(r["type"] == "task_optimization" for r in insights["recommendations"])
        
        # AI Agent应用学习到的知识规划新项目
        new_project_plan = await ai_agent.plan_project_with_learning(
            "新的Web应用",
            insights,
            test_workspace
        )
        
        assert new_project_plan["estimated_duration"] < sum(
            p["estimated_duration"] for p in projects
        ) / len(projects)

4.3 Go端到端测试

完整工作流程测试

// e2e/complete_workflow_test.go
package e2e

import (
	"os"
	"path/filepath"
	"testing"
	"time"
	"mcp-server/testing/mcp_client"
	"mcp-server/testing/ai_agent_simulator"
	"mcp-server/server"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type CompleteWorkflowTestSuite struct {
	suite.Suite
	server      *server.MCPServer
	client      *mcp_client.MCPClient
	aiAgent     *ai_agent_simulator.AIAgentSimulator
	testDir     string
}

func (suite *CompleteWorkflowTestSuite) SetupSuite() {
	// 创建测试目录
	tempDir, err := os.MkdirTemp("", "mcp-e2e-test")
	suite.Require().NoError(err)
	suite.testDir = tempDir

	// 启动MCP服务器
	suite.server = server.NewMCPServer()
	err = suite.server.Start()
	suite.Require().NoError(err)

	// 创建MCP客户端
	suite.client = mcp_client.NewMCPClient()
	err = suite.client.Connect(suite.server)
	suite.Require().NoError(err)

	// 创建AI Agent模拟器
	suite.aiAgent = ai_agent_simulator.NewAIAgentSimulator(suite.client)
}

func (suite *CompleteWorkflowTestSuite) TearDownSuite() {
	// 清理资源
	suite.client.Disconnect()
	suite.server.Stop()
	os.RemoveAll(suite.testDir)
}

func (suite *CompleteWorkflowTestSuite) TestComplexTaskManagementScenario() {
	// 创建第一个故事
	story1Result, err := suite.client.CallTool("create_story", map[string]interface{}{
		"work_directory": suite.testDir,
		"title":         "学习Go语言",
		"description":   "系统学习Go编程语言",
	})

	suite.NoError(err)
	assert.True(suite.T(), story1Result["success"].(bool))
	story1Id := story1Result["content"].(map[string]interface{})["story_id"].(string)

	// 创建第二个故事
	story2Result, err := suite.client.CallTool("create_story", map[string]interface{}{
		"work_directory": suite.testDir,
		"title":         "学习Gin框架",
		"description":   "学习Gin Web框架",
	})

	suite.NoError(err)
	assert.True(suite.T(), story2Result["success"].(bool))
	story2Id := story2Result["content"].(map[string]interface{})["story_id"].(string)

	// 为第一个故事创建多个任务
	tasks1 := []map[string]interface{}{
		{"title": "安装Go环境", "sequence_order": 1},
		{"title": "学习基础语法", "sequence_order": 2},
		{"title": "学习并发编程", "sequence_order": 3},
	}

	for _, task := range tasks1 {
		result, err := suite.client.CallTool("create_task", map[string]interface{}{
			"work_directory":  suite.testDir,
			"story_id":        story1Id,
			"title":           task["title"],
			"sequence_order":  task["sequence_order"],
		})

		suite.NoError(err)
		assert.True(suite.T(), result["success"].(bool))
	}

	// 为第二个故事创建任务
	tasks2 := []map[string]interface{}{
		{"title": "学习Gin基础", "sequence_order": 1},
		{"title": "创建REST API", "sequence_order": 2},
	}

	for _, task := range tasks2 {
		result, err := suite.client.CallTool("create_task", map[string]interface{}{
			"work_directory":  suite.testDir,
			"story_id":        story2Id,
			"title":           task["title"],
			"sequence_order":  task["sequence_order"],
		})

		suite.NoError(err)
		assert.True(suite.T(), result["success"].(bool))
	}

	// 交替执行两个故事的任务
	executionOrder := []struct {
		storyId          string
		expectedSequence int
	}{
		{storyId: story1Id, expectedSequence: 1},
		{storyId: story2Id, expectedSequence: 1},
		{storyId: story1Id, expectedSequence: 2},
		{storyId: story2Id, expectedSequence: 2},
		{storyId: story1Id, expectedSequence: 3},
	}

	for _, step := range executionOrder {
		// 获取下一个任务
		nextTaskResult, err := suite.client.CallTool("get_next_task", map[string]interface{}{
			"work_directory": suite.testDir,
			"story_id":       step.storyId,
		})

		suite.NoError(err)
		assert.True(suite.T(), nextTaskResult["success"].(bool))
		
		content := nextTaskResult["content"].(map[string]interface{})
		assert.Equal(suite.T(), float64(step.expectedSequence), content["sequence_order"])

		// 更新任务文件
		taskFileResult, err := suite.client.CallTool("update_task_file", map[string]interface{}{
			"work_directory": suite.testDir,
			"task_id":        content["task_id"].(string),
			"file_path":      filepath.Join(suite.testDir, "S"+step.storyId, "T"+string(rune(step.expectedSequence))+".md"),
		})

		suite.NoError(err)
		assert.True(suite.T(), taskFileResult["success"].(bool))

		// 完成任务
		completeTaskResult, err := suite.client.CallTool("complete_task", map[string]interface{}{
			"work_directory": suite.testDir,
			"task_id":        content["task_id"].(string),
		})

		suite.NoError(err)
		assert.True(suite.T(), completeTaskResult["success"].(bool))
	}

	// 完成两个故事
	completeStory1Result, err := suite.client.CallTool("complete_story", map[string]interface{}{
		"work_directory": suite.testDir,
		"story_id":       story1Id,
	})

	completeStory2Result, err := suite.client.CallTool("complete_story", map[string]interface{}{
		"work_directory": suite.testDir,
		"story_id":       story2Id,
	})

	suite.NoError(err)
	assert.True(suite.T(), completeStory1Result["success"].(bool))
	assert.True(suite.T(), completeStory2Result["success"].(bool))

	// 验证所有任务文件都已创建
	story1Dir := filepath.Join(suite.testDir, "S"+story1Id)
	story2Dir := filepath.Join(suite.testDir, "S"+story2Id)

	assert.True(suite.T(), directoryExists(story1Dir))
	assert.True(suite.T(), directoryExists(story2Dir))

	story1Files, err := os.ReadDir(story1Dir)
	suite.NoError(err)
	story2Files, err := os.ReadDir(story2Dir)
	suite.NoError(err)

	assert.True(suite.T(), fileExists(story1Files, "T1.md"))
	assert.True(suite.T(), fileExists(story1Files, "T2.md"))
	assert.True(suite.T(), fileExists(story1Files, "T3.md"))
	assert.True(suite.T(), fileExists(story2Files, "T1.md"))
	assert.True(suite.T(), fileExists(story2Files, "T2.md"))
}

func (suite *CompleteWorkflowTestSuite) TestErrorHandlingScenarios() {
	// 测试创建故事时缺少必需参数
	invalidStoryResult, err := suite.client.CallTool("create_story", map[string]interface{}{
		"work_directory": suite.testDir,
		// 缺少title参数
	})

	suite.NoError(err)
	assert.False(suite.T(), invalidStoryResult["success"].(bool))
	assert.Contains(suite.T(), invalidStoryResult["error"].(string), "title")

	// 测试创建任务时引用不存在的故事
	invalidTaskResult, err := suite.client.CallTool("create_task", map[string]interface{}{
		"work_directory":  suite.testDir,
		"story_id":        "nonexistent-story-id",
		"title":           "Invalid Task",
		"sequence_order":  1,
	})

	suite.NoError(err)
	assert.False(suite.T(), invalidTaskResult["success"].(bool))
	assert.Contains(suite.T(), invalidTaskResult["error"].(string), "story not found")
}

func (suite *CompleteWorkflowTestSuite) TestPerformance() {
	startTime := time.Now()
	storyCount := 10
	tasksPerStory := 20

	// 创建多个故事
	storyIds := make([]string, 0, storyCount)
	for i := 0; i < storyCount; i++ {
		storyResult, err := suite.client.CallTool("create_story", map[string]interface{}{
			"work_directory": suite.testDir,
			"title":         fmt.Sprintf("Performance Test Story %d", i),
			"description":   fmt.Sprintf("Testing performance with story %d", i),
		})

		suite.NoError(err)
		assert.True(suite.T(), storyResult["success"].(bool))
		
		storyId := storyResult["content"].(map[string]interface{})["story_id"].(string)
		storyIds = append(storyIds, storyId)
	}

	// 为每个故事创建多个任务
	for _, storyId := range storyIds {
		for j := 1; j <= tasksPerStory; j++ {
			_, err := suite.client.CallTool("create_task", map[string]interface{}{
				"work_directory":  suite.testDir,
				"story_id":        storyId,
				"title":           fmt.Sprintf("Task %d", j),
				"sequence_order":  j,
			})

			suite.NoError(err)
		}
	}

	duration := time.Since(startTime)

	// 验证性能:创建10个故事和200个任务应该在合理时间内完成
	assert.Less(suite.T(), duration, 10*time.Second)
	fmt.Printf("创建 %d 个故事和 %d 个任务耗时: %v\n", storyCount, storyCount*tasksPerStory, duration)
}

func (suite *CompleteWorkflowTestSuite) TestIntelligentTaskPlanning() {
	// AI Agent接收一个复杂目标
	goal := "开发一个完整的微服务架构应用,包括API网关、多个服务和数据库"

	// AI Agent分析目标并创建故事
	stories, err := suite.aiAgent.PlanStories(goal, suite.testDir)
	suite.NoError(err)
	assert.Greater(suite.T(), len(stories), 1)

	// 验证包含了关键组件
	var hasAPIGateway, hasService, hasDatabase bool
	for _, story := range stories {
		if contains(story.Title, "API网关") || contains(story.Title, "API Gateway") {
			hasAPIGateway = true
		}
		if contains(story.Title, "服务") || contains(story.Title, "Service") {
			hasService = true
		}
		if contains(story.Title, "数据库") || contains(story.Title, "Database") {
			hasDatabase = true
		}
	}

	assert.True(suite.T(), hasAPIGateway)
	assert.True(suite.T(), hasService)
	assert.True(suite.T(), hasDatabase)

	// AI Agent为每个故事规划任务
	for _, story := range stories {
		tasks, err := suite.aiAgent.PlanTasks(story.ID, suite.testDir)
		suite.NoError(err)
		assert.Greater(suite.T(), len(tasks), 0)

		// 验证任务顺序合理
		for i := 0; i < len(tasks)-1; i++ {
			assert.Less(suite.T(), tasks[i].SequenceOrder, tasks[i+1].SequenceOrder)
		}
	}
}

func (suite *CompleteWorkflowTestSuite) TestAdaptiveExecution() {
	// 创建一个学习编程的故事
	storyResult, err := suite.client.CallTool("create_story", map[string]interface{}{
		"work_directory": suite.testDir,
		"title":         "学习Rust编程",
		"description":   "从零开始学习Rust编程语言",
	})

	suite.NoError(err)
	assert.True(suite.T(), storyResult["success"].(bool))
	storyId := storyResult["content"].(map[string]interface{})["story_id"].(string)

	// AI Agent初始规划任务
	initialTasks := []map[string]interface{}{
		{"title": "安装Rust环境", "sequence_order": 1},
		{"title": "学习所有权系统", "sequence_order": 2},
		{"title": "学习生命周期", "sequence_order": 3},
		{"title": "学习并发编程", "sequence_order": 4},
		{"title": "实践项目开发", "sequence_order": 5},
	}

	for _, task := range initialTasks {
		_, err := suite.client.CallTool("create_task", map[string]interface{}{
			"work_directory":  suite.testDir,
			"story_id":        storyId,
			"title":           task["title"],
			"sequence_order":  task["sequence_order"],
		})

		suite.NoError(err)
	}

	// AI Agent开始执行任务
	currentTask, err := suite.aiAgent.GetNextTask(storyId, suite.testDir)
	suite.NoError(err)
	assert.Equal(suite.T(), "安装Rust环境", currentTask.Title)

	// 模拟任务执行失败,AI Agent需要调整计划
	executionResult, err := suite.aiAgent.ExecuteTask(currentTask.ID, map[string]interface{}{
		"success": false,
		"reason":  "环境配置复杂,需要更多步骤",
	})

	suite.NoError(err)
	assert.False(suite.T(), executionResult["success"].(bool))

	// AI Agent应该能够分解复杂任务
	refinedTasks, err := suite.aiAgent.RefineTask(currentTask.ID, map[string]interface{}{
		"add_steps": []string{
			"选择Rust版本",
			"下载安装包",
			"配置环境变量",
			"验证安装",
		},
	})

	suite.NoError(err)
	assert.Greater(suite.T(), len(refinedTasks), 1)

	// AI Agent继续执行细化后的任务
	for _, task := range refinedTasks {
		result, err := suite.aiAgent.ExecuteTask(task.ID, map[string]interface{}{
			"success": true,
		})

		suite.NoError(err)
		assert.True(suite.T(), result["success"].(bool))
	}

	// 验证原任务已标记为完成
	updatedTask, err := suite.aiAgent.GetTask(currentTask.ID, suite.testDir)
	suite.NoError(err)
	assert.Equal(suite.T(), "completed", updatedTask.Status)
}

// 辅助函数
func directoryExists(path string) bool {
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return info.IsDir()
}

func fileExists(files []os.DirEntry, filename string) bool {
	for _, file := range files {
		if file.Name() == filename {
			return true
		}
	}
	return false
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && 
		(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || 
		 indexOfSubstring(s, substr) >= 0))
}

func indexOfSubstring(s, substr string) int {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return i
		}
	}
	return -1
}

func TestCompleteWorkflowTestSuite(t *testing.T) {
	suite.Run(t, new(CompleteWorkflowTestSuite))
}

第五章:测试工具与框架

选择合适的测试工具和框架是提高测试效率和质量的关键。本章将介绍MCP服务器测试中常用的工具和框架,以及如何根据项目需求进行选择。

5.1 TypeScript测试工具

Jest - 全功能测试框架

Jest是Facebook开发的JavaScript测试框架,提供了完整的测试解决方案。

安装和配置

// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "devDependencies": {
    "jest": "^29.0.0",
    "@types/jest": "^29.0.0",
    "ts-jest": "^29.0.0"
  }
}
// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/index.ts'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  setupFilesAfterEnv: ['<rootDir>/tests/setup.ts']
};

高级用法

// tests/utils/test-helpers.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';

/**
 * 创建测试用的MCP服务器
 */
export async function createTestServer(): Promise<Server> {
  const { createMCPServer } = await import('@/server.js');
  return createMCPServer();
}

/**
 * 创建测试工作目录
 */
export function createTestWorkDir(testName: string): string {
  const timestamp = Date.now();
  return `./test-data/${testName}-${timestamp}`;
}

/**
 * 清理测试工作目录
 */
export function cleanupTestWorkDir(workDir: string): void {
  if (fs.existsSync(workDir)) {
    fs.rmSync(workDir, { recursive: true, force: true });
  }
}

/**
 * 模拟MCP客户端
 */
export class MockMCPClient {
  private server: Server;
  private requestHistory: any[] = [];

  constructor(server: Server) {
    this.server = server;
  }

  async callTool(name: string, args: any): Promise<any> {
    const request = {
      method: 'tools/call',
      params: { name, arguments: args }
    };

    this.requestHistory.push(request);
    return await this.server.request(request);
  }

  getRequestHistory(): any[] {
    return this.requestHistory;
  }

  clearHistory(): void {
    this.requestHistory = [];
  }
}

Vitest - 现代化测试框架

Vitest是一个现代化的测试框架,与Vite生态系统完美集成。

配置

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [tsconfigPaths()],
  test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.{test,spec}.{ts,js}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.d.ts',
        '**/*.config.*'
      ]
    }
  }
});

示例测试

// tests/unit/logger.vitest.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { logger, createLoggerWithContext } from '@/utils/logger.js';

describe('Logger - Vitest', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('应该能够创建上下文日志器', () => {
    const contextLogger = createLoggerWithContext('TestContext');
    expect(contextLogger).toBeDefined();
  });

  it('应该能够记录不同级别的日志', () => {
    const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    
    logger.info('Test message');
    
    expect(consoleSpy).toHaveBeenCalled();
    consoleSpy.mockRestore();
  });
});

5.2 Python测试工具

Pytest - 功能强大的测试框架

Pytest是Python生态系统中最流行的测试框架之一。

配置

# pytest.ini
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = 
    --cov=src
    --cov-report=html
    --cov-report=term-missing
    --cov-fail-under=80
    -v
markers =
    unit: Unit tests
    integration: Integration tests
    e2e: End-to-end tests
    slow: Slow tests

Fixtures和参数化

# tests/conftest.py
import pytest
import tempfile
import os
from mcp_server.database.connection import DatabaseConnection
from mcp_server.server import create_mcp_server

@pytest.fixture(scope="session")
def test_database():
    """创建测试数据库"""
    temp_dir = tempfile.mkdtemp()
    db_path = os.path.join(temp_dir, "test.db")
    
    connection = DatabaseConnection(db_path)
    connection.initialize()
    
    yield connection
    
    connection.close()
    os.remove(db_path)
    os.rmdir(temp_dir)

@pytest.fixture
async def mcp_server():
    """创建MCP服务器"""
    server = create_mcp_server()
    await server.start()
    yield server
    await server.stop()

@pytest.fixture
def sample_stories():
    """提供示例故事数据"""
    return [
        {
            "id": "story-1",
            "title": "学习Python",
            "description": "学习Python编程语言",
            "status": "active"
        },
        {
            "id": "story-2", 
            "title": "学习Django",
            "description": "学习Django Web框架",
            "status": "active"
        }
    ]

@pytest.mark.parametrize("title,description,expected_status", [
    ("测试故事", "测试描述", "active"),
    ("", "空标题故事", "error"),
    ("有效故事", "", "active")
])
def test_create_story_variations(title, description, expected_status):
    """参数化测试创建故事的不同情况"""
    # 测试实现
    pass

Mock和Patch

# tests/test_mocks.py
import pytest
from unittest.mock import Mock, patch, AsyncMock
from mcp_server.services.story_service import StoryService
from mcp_server.repositories.story_repository import StoryRepository

class TestStoryServiceMocks:
    def test_create_story_with_mock(self):
        """使用Mock对象测试"""
        # 创建Mock仓库
        mock_repo = Mock(spec=StoryRepository)
        mock_repo.create.return_value = {"id": "test-id", "title": "Test Story"}
        
        # 创建服务并注入Mock
        service = StoryService(mock_repo)
        
        # 测试
        result = service.create_story({"title": "Test Story"})
        
        # 验证
        assert result["id"] == "test-id"
        mock_repo.create.assert_called_once_with({"title": "Test Story"})

    @pytest.mark.asyncio
    async def test_async_operation_with_mock(self):
        """测试异步操作"""
        mock_repo = Mock(spec=StoryRepository)
        mock_repo.create_async = AsyncMock(return_value={"id": "test-id"})
        
        service = StoryService(mock_repo)
        result = await service.create_story_async({"title": "Test Story"})
        
        assert result["id"] == "test-id"
        mock_repo.create_async.assert_called_once()

    @patch('mcp_server.services.story_service.DatabaseConnection')
    def test_with_patch(self, mock_db_connection):
        """使用patch装饰器"""
        # 配置Mock
        mock_connection = Mock()
        mock_db_connection.return_value = mock_connection
        
        # 测试代码
        service = StoryService()
        # ... 测试逻辑
        
        # 验证Mock被正确调用
        mock_db_connection.assert_called_once()

5.3 Go测试工具

Testify - 断言和Mock库

Testify是Go语言中最流行的测试库之一,提供了丰富的断言和Mock功能。

基础断言

// testify_example_test.go
package testing

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "github.com/stretchr/testify/suite"
)

func TestBasicAssertions(t *testing.T) {
    // assert - 失败时继续执行
    assert.Equal(t, "hello", "hello", "They should be equal")
    assert.NotEqual(t, "hello", "world", "They should not be equal")
    assert.True(t, true, "It should be true")
    assert.False(t, false, "It should be false")
    assert.NotNil(t, &struct{}{}, "It should not be nil")
    assert.Nil(t, nil, "It should be nil")
    
    // require - 失败时立即停止
    require.Equal(t, "hello", "hello", "They must be equal")
}

func TestErrorAssertions(t *testing.T) {
    err := someFunctionThatReturnsError()
    
    // 测试错误
    assert.Error(t, err, "Function should return an error")
    assert.EqualError(t, err, "expected error message", "Error message should match")
    assert.Contains(t, err.Error(), "expected", "Error should contain substring")
}

func TestMapAssertions(t *testing.T) {
    m := map[string]interface{}{
        "name": "John",
        "age":  30,
        "active": true,
    }
    
    assert.Contains(t, m, "name", "Map should contain key")
    assert.Equal(t, "John", m["name"], "Value should match")
    assert.NotEmpty(t, m, "Map should not be empty")
}

Mock对象

// mock_test.go
package testing

import (
    "testing"
    "github.com/stretchr/testify/mock"
    "github.com/stretchr/testify/assert"
)

// MockStoryRepository 模拟故事仓库
type MockStoryRepository struct {
    mock.Mock
}

func (m *MockStoryRepository) Create(story *Story) (*Story, error) {
    args := m.Called(story)
    return args.Get(0).(*Story), args.Error(1)
}

func (m *MockStoryRepository) FindByID(id string) (*Story, error) {
    args := m.Called(id)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*Story), args.Error(1)
}

func TestStoryServiceWithMock(t *testing.T) {
    // 创建Mock
    mockRepo := new(MockStoryRepository)
    
    // 设置期望
    expectedStory := &Story{ID: "test-id", Title: "Test Story"}
    mockRepo.On("Create", mock.AnythingOfType("*Story")).Return(expectedStory, nil)
    
    // 创建服务并注入Mock
    service := NewStoryService(mockRepo)
    
    // 测试
    result, err := service.CreateStory(&Story{Title: "Test Story"})
    
    // 验证
    assert.NoError(t, err)
    assert.Equal(t, expectedStory, result)
    mockRepo.AssertExpectations(t)
}

Test Suite

// suite_test.go
package testing

import (
    "testing"
    "github.com/stretchr/testify/suite"
)

type StoryServiceTestSuite struct {
    suite.Suite
    service     *StoryService
    mockRepo    *MockStoryRepository
    testDir     string
}

func (suite *StoryServiceTestSuite) SetupSuite() {
    // Suite级别的设置
    suite.testDir = createTestDirectory()
}

func (suite *StoryServiceTestSuite) TearDownSuite() {
    // Suite级别的清理
    cleanupTestDirectory(suite.testDir)
}

func (suite *StoryServiceTestSuite) SetupTest() {
    // 每个测试前的设置
    suite.mockRepo = new(MockStoryRepository)
    suite.service = NewStoryService(suite.mockRepo)
}

func (suite *StoryServiceTestSuite) TearDownTest() {
    // 每个测试后的清理
    suite.mockRepo.AssertExpectations(suite.T())
}

func (suite *StoryServiceTestSuite) TestCreateStory() {
    // 测试创建故事
    expectedStory := &Story{ID: "test-id", Title: "Test Story"}
    suite.mockRepo.On("Create", mock.AnythingOfType("*Story")).Return(expectedStory, nil)
    
    result, err := suite.service.CreateStory(&Story{Title: "Test Story"})
    
    suite.NoError(err)
    suite.Equal(expectedStory, result)
}

func TestStoryServiceTestSuite(t *testing.T) {
    suite.Run(t, new(StoryServiceTestSuite))
}

5.4 专用MCP测试工具

MCP Inspector

MCP Inspector是官方提供的MCP服务器测试工具,可以用于手动和自动化测试。

安装和使用

# 安装MCP Inspector
npm install -g @modelcontextprotocol/inspector

# 启动Inspector
mcp-inspector ./dist/index.js

自动化测试集成

// tests/mcp-inspector.test.ts
import { MCPInspector } from '@modelcontextprotocol/inspector';
import { createMCPServer } from '@/server.js';

describe('MCP Inspector测试', () => {
  let inspector: MCPInspector;
  let server: Server;

  beforeAll(async () => {
    server = createMCPServer();
    inspector = new MCPInspector(server);
    await inspector.start();
  });

  afterAll(async () => {
    await inspector.stop();
    await server.close();
  });

  it('应该能够列出所有可用工具', async () => {
    const tools = await inspector.listTools();
    expect(tools).toContain('create_story');
    expect(tools).toContain('create_task');
    expect(tools).toContain('get_next_task');
  });

  it('应该能够执行工具调用', async () => {
    const result = await inspector.callTool('create_story', {
      work_directory: '/test',
      title: 'Test Story'
    });

    expect(result.success).toBe(true);
    expect(result.content.story_id).toBeDefined();
  });
});

自定义测试客户端

// tests/mcp-test-client.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

export class MCPTestClient {
  private client: Client;
  private transport: StdioServerTransport;

  constructor(serverPath: string) {
    this.client = new Client({
      name: 'test-client',
      version: '1.0.0'
    }, {
      capabilities: {}
    });

    this.transport = new StdioServerTransport({
      command: 'node',
      args: [serverPath]
    });
  }

  async connect(): Promise<void> {
    await this.client.connect(this.transport);
  }

  async disconnect(): Promise<void> {
    await this.client.close();
  }

  async listTools(): Promise<any[]> {
    const result = await this.client.request({
      method: 'tools/list'
    });

    return result.tools;
  }

  async callTool(name: string, args: any): Promise<any> {
    const result = await this.client.request({
      method: 'tools/call',
      params: {
        name,
        arguments: args
      }
    });

    return result;
  }

  async listResources(): Promise<any[]> {
    const result = await this.client.request({
      method: 'resources/list'
    });

    return result.resources;
  }

  async readResource(uri: string): Promise<any> {
    const result = await this.client.request({
      method: 'resources/read',
      params: { uri }
    });

    return result.contents;
  }
}

5.5 持续集成测试

GitHub Actions配置

# .github/workflows/test.yml
name: MCP Server Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        node-version: [18.x, 20.x]
        python-version: [3.9, 3.10, 3.11]
        go-version: [1.19, 1.20, 1.21]

    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    
    - name: Setup Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    
    - name: Setup Go ${{ matrix.go-version }}
      uses: actions/setup-go@v4
      with:
        go-version: ${{ matrix.go-version }}

    - name: Install Node.js dependencies
      run: npm ci
    
    - name: Install Python dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install -r requirements-dev.txt
    
    - name: Install Go dependencies
      run: go mod download

    - name: Run TypeScript tests
      run: npm test
    
    - name: Run Python tests
      run: pytest
    
    - name: Run Go tests
      run: go test -v -race -coverprofile=coverage.out ./...

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        files: ./coverage/lcov.info,./coverage.out,./htmlcov/index.html

Docker测试环境

# Dockerfile.test
FROM node:18-alpine

# 安装Python和Go
RUN apk add --no-cache python3 py3-pip go

# 设置工作目录
WORKDIR /app

# 复制所有文件
COPY . .

# 安装依赖
RUN npm ci
RUN pip3 install -r requirements.txt
RUN pip3 install -r requirements-dev.txt
RUN go mod download

# 运行测试
CMD ["npm", "test"]
# docker-compose.test.yml
version: '3.8'

services:
  mcp-test:
    build:
      context: .
      dockerfile: Dockerfile.test
    volumes:
      - ./coverage:/app/coverage
    environment:
      - NODE_ENV=test
      - LOG_LEVEL=error
    command: npm run test:all

  mcp-test-python:
    build:
      context: .
      dockerfile: Dockerfile.test
    volumes:
      - ./htmlcov:/app/htmlcov
    environment:
      - PYTHONPATH=/app
    command: pytest --cov=src --cov-report=html

  mcp-test-go:
    build:
      context: .
      dockerfile: Dockerfile.test
    volumes:
      - ./coverage:/app/coverage
    command: go test -v -race -coverprofile=coverage.out ./...

5.6 测试报告和分析

覆盖率报告

// jest.config.js (覆盖率配置)
module.exports = {
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/index.ts',
    '!src/types/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    },
    './src/services/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90
    }
  },
  coverageReporters: [
    'text',
    'text-summary',
    'html',
    'lcov',
    'json'
  ]
};

性能测试报告

// tests/performance/performance-reporter.ts
import { writeFileSync } from 'fs';
import { join } from 'path';

export interface PerformanceMetrics {
  testName: string;
  duration: number;
  memoryUsage: NodeJS.MemoryUsage;
  cpuUsage: NodeJS.CpuUsage;
  timestamp: Date;
}

export class PerformanceReporter {
  private metrics: PerformanceMetrics[] = [];

  addMetric(metric: PerformanceMetrics): void {
    this.metrics.push(metric);
  }

  generateReport(): string {
    const report = {
      summary: this.generateSummary(),
      details: this.metrics,
      recommendations: this.generateRecommendations()
    };

    return JSON.stringify(report, null, 2);
  }

  saveReport(outputPath: string): void {
    const report = this.generateReport();
    writeFileSync(outputPath, report);
  }

  private generateSummary(): any {
    const durations = this.metrics.map(m => m.duration);
    const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
    const maxDuration = Math.max(...durations);
    const minDuration = Math.min(...durations);

    return {
      totalTests: this.metrics.length,
      averageDuration: avgDuration,
      maxDuration,
      minDuration,
      slowestTest: this.metrics.find(m => m.duration === maxDuration)?.testName,
      fastestTest: this.metrics.find(m => m.duration === minDuration)?.testName
    };
  }

  private generateRecommendations(): string[] {
    const recommendations: string[] = [];
    const summary = this.generateSummary();

    if (summary.averageDuration > 1000) {
      recommendations.push('平均测试时间过长,考虑优化测试性能');
    }

    if (summary.maxDuration > 5000) {
      recommendations.push(`测试 "${summary.slowestTest}" 耗时过长,需要优化`);
    }

    return recommendations;
  }
}

通过合理选择和使用这些测试工具和框架,可以大大提高MCP服务器的测试效率和质量,确保服务器在各种场景下都能稳定运行。


总结部分:总结与展望

总结

本文系统性地介绍了MCP服务器的测试方法,从基础的单元测试到复杂的端到端测试,涵盖了TypeScript、Python和Go三种主要编程语言的实现。通过本文的学习,读者应该能够:

  1. 理解MCP测试的重要性:认识到MCP服务器作为AI Agent与外部系统交互的桥梁,其可靠性直接影响整个AI应用系统的质量。

  2. 掌握测试金字塔:学会在MCP服务器开发中应用测试金字塔模型,合理分配单元测试、集成测试和端到端测试的比例。

  3. 实施多层次测试策略

    • 单元测试确保单个组件的功能正确性
    • 集成测试验证组件间的协作
    • 端到端测试模拟真实用户场景
  4. 选择合适的测试工具:根据项目需求选择Jest、Vitest、Pytest、Testify等测试框架,并了解如何配置和使用它们。

  5. 构建完整的测试体系:包括测试环境设置、测试数据管理、Mock对象使用、性能测试和持续集成。

最佳实践总结

基于本文的内容和实际项目经验,我们总结出以下MCP服务器测试的最佳实践:

1. 测试策略

  • 分层测试:按照测试金字塔模型,70%单元测试,20%集成测试,10%端到端测试
  • 测试驱动开发:先写测试,再实现功能,确保代码质量和可测试性
  • 持续测试:在开发过程中持续运行测试,及时发现问题

2. 测试设计

  • 独立性:每个测试应该独立运行,不依赖其他测试的状态
  • 可重复性:测试结果应该一致,不受环境因素影响
  • 可读性:测试代码应该清晰易懂,起到文档作用

3. 测试实现

  • 使用Mock对象:隔离外部依赖,提高测试速度和稳定性
  • 参数化测试:使用不同参数验证同一功能的多种情况
  • 边界测试:重点测试边界条件和异常情况

4. 测试维护

  • 定期重构:随着代码变化,及时更新和重构测试
  • 监控覆盖率:保持适当的测试覆盖率,重点关注关键路径
  • 性能监控:定期运行性能测试,防止性能退化

未来展望

随着MCP生态系统的不断发展,MCP服务器测试也将面临新的机遇和挑战:

1. 测试自动化

  • AI辅助测试:利用AI技术自动生成测试用例,提高测试覆盖率
  • 智能测试选择:根据代码变更自动选择相关测试,提高测试效率
  • 自动化测试生成:基于MCP协议规范自动生成基础测试框架

2. 测试工具演进

  • 专用MCP测试框架:出现更多专门针对MCP服务器的测试框架
  • 可视化测试工具:提供图形化界面,简化测试编写和执行
  • 云端测试平台:支持大规模并发测试和分布式测试

3. 测试标准

  • MCP测试标准:社区将形成统一的MCP服务器测试标准和规范
  • 认证体系:建立MCP服务器测试认证体系,确保质量
  • 最佳实践库:收集和分享MCP服务器测试的最佳实践

4. 性能测试

  • 负载测试:针对高并发场景的专门测试工具和方法
  • 资源监控:实时监控MCP服务器的资源使用情况
  • 性能基准:建立MCP服务器性能基准和评估体系

行动建议

为了在项目中有效实施本文介绍的测试方法,建议采取以下行动:

1. 立即行动

  • 评估现有测试:检查当前项目的测试覆盖率和质量
  • 选择测试框架:根据项目技术栈选择合适的测试框架
  • 建立测试环境:配置持续集成环境和测试基础设施

2. 短期目标(1-3个月)

  • 编写核心测试:为核心功能编写单元测试和集成测试
  • 建立测试流程:制定测试编写、审查和执行流程
  • 培训团队:对团队成员进行测试方法和工具的培训

3. 长期目标(3-12个月)

  • 完善测试体系:建立完整的测试体系,包括性能测试和安全测试
  • 优化测试效率:通过工具和流程优化提高测试效率
  • 分享经验:在团队和社区中分享测试经验和最佳实践

结语

MCP服务器测试是确保AI应用系统质量的关键环节。通过系统性的测试策略和合适的工具选择,我们可以构建高质量、高可靠性的MCP服务器。随着技术的不断发展,测试方法和工具也将不断演进,我们需要保持学习和适应的态度,持续改进测试实践。

希望本文能够为MCP服务器的测试提供有价值的指导,帮助开发者构建更好的AI应用系统。让我们一起推动MCP生态系统的发展,为AI技术的普及和应用贡献力量。

posted @ 2025-10-06 21:46  suveng  阅读(69)  评论(0)    收藏  举报