mcpField
T
testing-builder

testing-builder

github.com/jackspace测试与质量1641 次安装12 周前更新
代码仓库

关于 testing-builder

Testing Builder 是用于自动生成测试的技能,面向希望减少手动编写测试工作的开发者。它分析代码、参考已保存的测试偏好,并生成覆盖边界条件的测试套件,同时保存测试模式供后续使用。

主要功能

  1. 01分析代码并确定测试类型
  2. 02读取已保存的测试偏好和模式
  3. 03生成单元、集成、端到端及回归测试
  4. 04检查覆盖范围与边界条件
  5. 05保存测试模式以供后续复用

适用场景

  1. 01为新函数生成单元测试
  2. 02为修复后的缺陷创建回归测试
  3. 03按项目习惯编写 API 集成测试
  4. 04生成关键用户流程的端到端测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jackspace/claudeskillz --skill testing-builder

安装后在 Claude Code 中输入/testing-builder确认已加载。

附带文件SKILL.json

详细介绍

测试构建器

目的

自动生成测试,并学习你的测试风格。通过让测试变得毫不费力,消除 ADHD 用户在测试方面的障碍:

  1. 分析代码,了解需要测试的内容
  2. 从记忆中回忆你的测试模式
  3. 生成全面的测试套件
  4. 确保覆盖所有边界情况
  5. 保存测试模式,供将来使用

面向 ADHD 用户:零阻力——无需手动操作,即可立即创建测试。 面向所有用户:无需繁琐工作,即可获得全面覆盖。 学习系统:随着时间推移,越来越擅长匹配你的测试风格。

激活条件

  • 用户说:“编写测试”“测试这个”“添加测试”“覆盖率”
  • 修复 Bug 后:“创建回归测试”
  • 代码审查时:“需要测试”
  • 用户提到:“单元测试”“集成测试”“E2E 测试”

核心工作流

1. 分析代码

步骤 1:确定要测试的内容

// Example: Analyzing a function
function calculateDiscount(price, discountPercent, customerType) {
  if (customerType === 'premium') {
    discountPercent += 10;
  }
  return price * (1 - discountPercent / 100);
}

// Identify:
- Function name: calculateDiscount
- Parameters: price, discountPercent, customerType
- Logic branches: premium vs non-premium
- Edge cases: negative values, zero, boundary conditions
- Return type: number

步骤 2:确定所需的测试类型

  • 单元测试:纯函数、实用工具、组件
  • 集成测试:多个组件协同工作、API 端点
  • E2E 测试:完整的用户工作流、关键路径
  • 回归测试:针对已修复的特定 Bug
2. 回忆测试模式

向 context-manager 查询:

search memories:
- Type: PREFERENCE, PROCEDURE
- Tags: testing, test-framework, test-style
- Project: current project

回忆内容:

  • 测试框架(Jest、Vitest、Pytest 等)
  • 断言风格(expect、assert、should)
  • 测试结构(describe/it、test 块)
  • Mock 模式(jest.mock、vi.mock)
  • 覆盖率要求
  • 文件命名约定

记忆示例:

PREFERENCE: Testing style for BOOSTBOX
- Framework: Vitest
- Assertion: expect()
- Structure: describe/it blocks
- Coverage: Minimum 80%
- File naming: {filename}.test.js
3. 生成测试套件

单元测试模板:

import { describe, it, expect } from 'vitest';
import { calculateDiscount } from './discount';

describe('calculateDiscount', () => {
  describe('basic calculations', () => {
    it('should calculate discount correctly for regular customers', () => {
      const result = calculateDiscount(100, 10, 'regular');
      expect(result).toBe(90);
    });

    it('should add bonus discount for premium customers', () => {
      const result = calculateDiscount(100, 10, 'premium');
      expect(result).toBe(80); // 10% + 10% bonus
    });
  });

  describe('edge cases', () => {
    it('should handle zero discount', () => {
      const result = calculateDiscount(100, 0, 'regular');
      expect(result).toBe(100);
    });

    it('should handle 100% discount', () => {
      const result = calculateDiscount(100, 100, 'regular');
      expect(result).toBe(0);
    });

    it('should handle zero price', () => {
      const result = calculateDiscount(0, 10, 'regular');
      expect(result).toBe(0);
    });
  });

  describe('invalid inputs', () => {
    it('should handle negative price', () => {
      const result = calculateDiscount(-100, 10, 'regular');
      expect(result).toBeLessThan(0);
    });

    it('should handle invalid customer type', () => {
      const result = calculateDiscount(100, 10, 'unknown');
      expect(result).toBe(90); // Falls back to regular
    });
  });
});

集成测试模板(API 端点):

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from './app';
import { db } from './db';

describe('POST /api/users', () => {
  beforeAll(async () => {
    await db.connect();
  });

  afterAll(async () => {
    await db.disconnect();
  });

  it('should create a new user', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        email: '[email protected]',
        name: 'Test User'
      });

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('id');
    expect(response.body.email).toBe('[email protected]');
  });

  it('should reject duplicate email', async () => {
    await request(app).post('/api/users').send({
      email: '[email protected]',
      name: 'User 1'
    });

    const response = await request(app)
      .post('/api/users')
      .send({
        email: '[email protected]',
        name: 'User 2'
      });

    expect(response.status).toBe(409);
    expect(response.body.error).toContain('already exists');
  });

  it('should validate required fields', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        name: 'No Email'
      });

    expect(response.status).toBe(400);
    expect(response.body.error).toContain('email');
  });
});

E2E 测试模板(Playwright):

import { test, expect } from '@playwright/test';

test.describe('User Login Flow', () => {
  test('should login successfully with valid credentials', async ({ page }) => {
    await page.goto('/login');

    await page.fill('[name="email"]', '[email protected]');
    await page.fill('[name="password"]', 'password123');
    await page.click('button[type="submit"]');

    await expect(page).toHaveURL('/dashboard');
    await expect(page.locator('.welcome-message')).toContainText('Welcome');
  });

  test('should show error with invalid credentials', async ({ page }) => {
    await page.goto('/login');

    await page.fill('[name="email"]', '[email protected]');
    await page.fill('[name="password"]', 'wrongpass');
    await page.click('button[type="submit"]');

    await expect(page.locator('.error-message')).toContainText('Invalid');
    await expect(page).toHaveURL('/login');
  });

  test('should validate required fields', async ({ page }) => {
    await page.goto('/login');

    await page.click('button[type="submit"]');

    await expect(page.locator('[name="email"]:invalid')).toBeVisible();
    await expect(page.locator('[name="password"]:invalid')).toBeVisible();
  });
});
4. 覆盖率分析

检查已测试的内容:

// Coverage requirements (from memory or defaults)
const requirements = {
  statements: 80,
  branches: 75,
  functions: 90,
  lines: 80
};

// Identify untested scenarios
const missing = [
  'Error handling for network failures',
  'Loading state transitions',
  'Concurrent operations',
  'Cleanup on unmount'
];

// Generate additional tests for missing coverage
5. 保存测试模式

生成测试后:

# Save testing style to memory
remember: Testing pattern for authentication
Type: PROCEDURE
Tags: testing, authentication, integration
Content: For auth endpoints, always test:
- Valid credentials → success
- Invalid credentials → 401
- Missing fields → 400
- Expired token → 401
- Token refresh flow

特定框架的模式

Jest/Vitest(JavaScript/TypeScript)
import { describe, it, expect, vi, beforeEach } from 'vitest';

describe('Component', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should do something', () => {
    expect(result).toBe(expected);
  });
});

Mock:

// Mock module
vi.mock('./api', () => ({
  fetchData: vi.fn(() => Promise.resolve({ data: [] }))
}));

// Mock implementation
const mockFn = vi.fn().mockImplementation(() => 'value');
Pytest(Python)
import pytest
from mymodule import calculate_discount

class TestCalculateDiscount:
    def test_regular_customer(self):
        result = calculate_discount(100, 10, 'regular')
        assert result == 90

    def test_premium_customer(self):
        result = calculate_discount(100, 10, 'premium')
        assert result == 80

    @pytest.mark.parametrize("price,discount,expected", [
        (100, 0, 100),
        (100, 100, 0),
        (0, 10, 0),
    ])
    def test_edge_cases(self, price, discount, expected):
        result = calculate_discount(price, discount, 'regular')
        assert result == expected

Fixtures:

@pytest.fixture
def sample_user():
    return {
        'id': 1,
        'email': '[email protected]',
        'name': 'Test User'
    }

def test_user_creation(sample_user):
    assert sample_user['email'] == '[email protected]'
React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import { expect, test } from 'vitest';
import { UserList } from './UserList';

test('renders user list', () => {
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];

  render(<UserList users={users} />);

  expect(screen.getByText('Alice')).toBeInTheDocument();
  expect(screen.getByText('Bob')).toBeInTheDocument();
});

test('handles empty user list', () => {
  render(<UserList users={[]} />);

  expect(screen.getByText('No users found')).toBeInTheDocument();
});

test('handles user click', () => {
  const handleClick = vi.fn();
  const users = [{ id: 1, name: 'Alice' }];

  render(<UserList users={users} onUserClick={handleClick} />);

  fireEvent.click(screen.getByText('Alice'));

  expect(handleClick).toHaveBeenCalledWith(users[0]);
});

测试类别

单元测试

测试内容:

  • 纯函数
  • 实用工具函数
  • 组件逻辑
  • 类方法

重点关注:

  • 输入/输出正确性
  • 边界情况
  • 错误条件
  • 边界值
集成测试

测试内容:

  • API 端点
  • 数据库操作
  • 多个组件协同工作
  • 外部服务集成

重点关注:

  • 组件交互
  • 数据流
  • 错误传播
  • 事务处理
E2E 测试

测试内容:

  • 关键用户流程
  • 完整功能
  • 多步骤流程
  • 跨页面工作流

重点关注:

  • 用户视角
  • 真实浏览器交互
  • 全栈集成
  • 类生产环境
回归测试

创建时机:

  • 修复 Bug 后
  • 防止已知问题
  • 历史问题

结构:

describe('Regression: Bug #123 - User map undefined', () => {
  it('should handle undefined users array', () => {
    // Test that previously failed
    const result = renderUserList(undefined);
    expect(result).not.toThrow();
  });
});

上下文集成

回忆测试偏好

生成测试前:

// Query context-manager
const preferences = searchMemories({
  type: 'PREFERENCE',
  tags: ['testing', project],
  recent: true
});

// Apply preferences
const testConfig = {
  framework: preferences.framework || 'vitest',
  style: preferences.style || 'describe/it',
  coverage: preferences.coverage || 80,
  naming: preferences.naming || '{name}.test.js'
};
保存测试模式

生成测试后:

remember: Testing pattern for API endpoints
Type: PROCEDURE
Tags: testing, api, integration
Content: For API endpoints, always include:
1. Happy path (200/201)
2. Validation errors (400)
3. Auth errors (401/403)
4. Not found (404)
5. Server errors (500)
6. Rate limiting (429)
从反馈中学习

如果用户修改了生成的测试:

// Analyze what user changed
const changes = diff(generatedTest, userModifiedTest);

// Save as new pattern if significant
if (changes.significant) {
  saveMemory({
    type: 'PREFERENCE',
    content: `User prefers ${changes.pattern}`,
    tags: ['testing', 'user-preference']
  });
}

与其他技能集成

Error Debugger

当 error-debugger 修复 Bug 时:

Automatically invoke testing-builder
Create regression test for: {bug_scenario}
Ensure test fails before fix, passes after
Context Manager

加载测试偏好:

Query for PREFERENCE with tags: [testing, test-framework]
Apply framework, style, coverage preferences

保存新模式:

After generating tests for new scenario
Save pattern as PROCEDURE for future reference
Code Quality Auditor

检查测试质量:

After generating tests:
→ Invoke code-quality-auditor
→ Verify test coverage
→ Check for test smells
→ Ensure meaningful assertions

快速参考

测试生成检查清单

✅ 已覆盖正常路径 ✅ 已测试边界情况 ✅ 已处理错误条件 ✅ 已验证无效输入 ✅ 已检查边界值 ✅ 已处理异步操作 ✅ 已正确配置 Mock ✅ 已正确完成清理 ✅ 断言有意义

常见测试模式
场景模式
函数输入 → 输出断言
组件渲染 → 查询 → 断言
API请求 → 响应 → 断言
异步await → Promise → 结果
错误Try/catch → 错误断言
Mock设置 → 调用 → 验证 Mock
触发短语
  • "write tests"
  • "test this"
  • "add coverage"
  • "create regression test"
  • "needs tests"
文件位置
  • 测试模式:~/.claude-memories/ (Linux/macOS) or %USERPROFILE%\.claude-memories\ (Windows)procedures/(标记为 "testing")
  • 测试偏好:~/.claude-memories/ (Linux/macOS) or %USERPROFILE%\.claude-memories\ (Windows)preferences/(标记为 "testing")
  • 生成的测试:{filename}.test.{ext} 或 tests/{filename}.test.{ext}
成功标准

✅ 在 30 秒内生成测试 ✅ 覆盖全面(默认 80%+) ✅ 包含所有边缘情况 ✅ 测试遵循项目风格 ✅ 无需手动操作 ✅ 保存测试模式以供后续使用

评论

支持 Markdown