К основному содержанию
скилл CX Codex

unit-test-generator — скилл для Claude Code: обзор и установка

Генерирует юнит-тесты по паттерну AAA (Arrange-Act-Assert) для функций, классов и компонентов React. Пригодится, когда нужно закрыть проект тестами с учётом граничных условий.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code
Открыть на GitHub

Что делает unit-test-generator

Скилл формирует тестовые наборы по схеме Arrange-Act-Assert. Он раскладывает тесты по тем же папкам, где лежат исходники. Проверяются обычные ветки исполнения, а также пограничные случаи и ошибки ввода.

Внутри заложены готовые паттерны для чистых функций, методов классов и компонентов React. Для асинхронных операций скилл настраивает моки fetch и очищает их в хуке afterEach. Для работы с интерфейсом задействуются утилиты @testing-library/react.

Скилл опирается на синтаксис Vitest и Jest. Поэтому в проекте уже должен быть установлен тестовый фреймворк. Без настроенного раннера запустить созданные тесты не выйдет.

Какие задачи решает

  • сгенерировать юнит-тесты по паттерну Arrange-Act-Assert
  • протестировать граничные условия и обработку исключений
  • замокать вызовы fetch в асинхронных сервисах
  • проверить логику методов классов и состояние моделей
  • протестировать рендеринг и события кликов в компонентах React

Примеры запросов агенту

  • Напиши юнит-тесты для функции divide из src/utils/calculator.ts с проверкой деления на ноль.
  • Сгенерируй тесты для Counter.tsx с проверкой начального значения и нажатия кнопок.
  • Создай тесты для метода fetchUser в src/services/userService.ts с моком сетевого ответа.

Как установить unit-test-generator

1Через Skills CLI (Vercel)

npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code

2Вручную: положить файлы

~/.claude/skills/unit-test-generator/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/unit-test-generator/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о unit-test-generator

Как установить unit-test-generator в Claude Code?

Выполните команду npx skills add patricio0312rev/skills --skill unit-test-generator -a claude-code в терминале.

Что делает скилл unit-test-generator?

Он создаёт готовые тестовые наборы по схеме Arrange-Act-Assert с учётом позитивных сценариев, крайних значений и ошибок. Тестовые файлы создаются в той же структуре папок, что и исходный код.

Какие фреймворки тестирования использует скилл?

Сгенерированный код ориентирован на Vitest и Jest, а для проверки React-компонентов используется библиотека @testing-library/react.

Исходный текст

SKILL.md · MIT
---
name: unit-test-generator
description: Generates comprehensive unit tests with AAA pattern (Arrange-Act-Assert), edge cases, error scenarios, and coverage analysis. Creates test files matching source structure with complete test suites. Use for "unit testing", "test generation", "Jest tests", or "test coverage".
---

# Unit Test Generator

Generate comprehensive unit tests with edge cases and AAA pattern.

## AAA Pattern Template

```typescript
// tests/utils/validator.test.ts
import { describe, it, expect } from "vitest";
import { validateEmail } from "@/utils/validator";

describe("validateEmail", () => {
  it("should return true for valid email", () => {
    // Arrange
    const email = "[email protected]";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(true);
  });

  it("should return false for invalid email - missing @", () => {
    // Arrange
    const email = "userexample.com";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(false);
  });

  it("should return false for invalid email - missing domain", () => {
    // Arrange
    const email = "user@";

    // Act
    const result = validateEmail(email);

    // Assert
    expect(result).toBe(false);
  });
});
```

## Comprehensive Test Cases

```typescript
// src/utils/calculator.ts
export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero");
  }
  return a / b;
}

// tests/utils/calculator.test.ts
describe("divide", () => {
  describe("happy path", () => {
    it("should divide positive numbers", () => {
      expect(divide(10, 2)).toBe(5);
    });

    it("should divide negative numbers", () => {
      expect(divide(-10, 2)).toBe(-5);
      expect(divide(10, -2)).toBe(-5);
      expect(divide(-10, -2)).toBe(5);
    });

    it("should handle decimal results", () => {
      expect(divide(10, 3)).toBeCloseTo(3.333, 3);
    });
  });

  describe("edge cases", () => {
    it("should handle zero dividend", () => {
      expect(divide(0, 5)).toBe(0);
    });

    it("should handle very large numbers", () => {
      expect(divide(Number.MAX_SAFE_INTEGER, 2)).toBe(
        Number.MAX_SAFE_INTEGER / 2
      );
    });

    it("should handle very small numbers", () => {
      expect(divide(0.0001, 0.0001)).toBe(1);
    });
  });

  describe("error cases", () => {
    it("should throw error when dividing by zero", () => {
      expect(() => divide(10, 0)).toThrow("Division by zero");
    });

    it("should throw error when dividing by negative zero", () => {
      expect(() => divide(10, -0)).toThrow("Division by zero");
    });
  });
});
```

## Async Function Testing

```typescript
// src/services/userService.ts
export async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);

  if (!response.ok) {
    throw new Error(`User not found: ${id}`);
  }

  return response.json();
}

// tests/services/userService.test.ts
describe("fetchUser", () => {
  beforeEach(() => {
    global.fetch = vi.fn();
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it("should fetch user successfully", async () => {
    // Arrange
    const mockUser = { id: "123", name: "John" };
    (global.fetch as any).mockResolvedValueOnce({
      ok: true,
      json: async () => mockUser,
    });

    // Act
    const user = await fetchUser("123");

    // Assert
    expect(user).toEqual(mockUser);
    expect(global.fetch).toHaveBeenCalledWith("/api/users/123");
  });

  it("should throw error when user not found", async () => {
    // Arrange
    (global.fetch as any).mockResolvedValueOnce({
      ok: false,
    });

    // Act & Assert
    await expect(fetchUser("999")).rejects.toThrow("User not found: 999");
  });

  it("should handle network error", async () => {
    // Arrange
    (global.fetch as any).mockRejectedValueOnce(new Error("Network error"));

    // Act & Assert
    await expect(fetchUser("123")).rejects.toThrow("Network error");
  });
});
```

## Testing Classes

```typescript
// src/models/ShoppingCart.ts
export class ShoppingCart {
  private items: CartItem[] = [];

  add(item: CartItem): void {
    this.items.push(item);
  }

  remove(itemId: string): void {
    this.items = this.items.filter((i) => i.id !== itemId);
  }

  getTotal(): number {
    return this.items.reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    );
  }

  clear(): void {
    this.items = [];
  }
}

// tests/models/ShoppingCart.test.ts
describe("ShoppingCart", () => {
  let cart: ShoppingCart;

  beforeEach(() => {
    cart = new ShoppingCart();
  });

  describe("add", () => {
    it("should add item to cart", () => {
      // Arrange
      const item = { id: "1", name: "Product", price: 10, quantity: 1 };

      // Act
      cart.add(item);

      // Assert
      expect(cart.getTotal()).toBe(10);
    });

    it("should add multiple items", () => {
      // Arrange
      const item1 = { id: "1", name: "Product 1", price: 10, quantity: 1 };
      const item2 = { id: "2", name: "Product 2", price: 20, quantity: 2 };

      // Act
      cart.add(item1);
      cart.add(item2);

      // Assert
      expect(cart.getTotal()).toBe(50); // 10 + (20 * 2)
    });
  });

  describe("remove", () => {
    it("should remove item from cart", () => {
      // Arrange
      const item = { id: "1", name: "Product", price: 10, quantity: 1 };
      cart.add(item);

      // Act
      cart.remove("1");

      // Assert
      expect(cart.getTotal()).toBe(0);
    });

    it("should not throw when removing non-existent item", () => {
      // Act & Assert
      expect(() => cart.remove("999")).not.toThrow();
    });
  });

  describe("getTotal", () => {
    it("should return 0 for empty cart", () => {
      expect(cart.getTotal()).toBe(0);
    });

    it("should calculate total with quantities", () => {
      // Arrange
      cart.add({ id: "1", name: "Product", price: 10, quantity: 3 });

      // Assert
      expect(cart.getTotal()).toBe(30);
    });
  });

  describe("clear", () => {
    it("should remove all items", () => {
      // Arrange
      cart.add({ id: "1", name: "Product 1", price: 10, quantity: 1 });
      cart.add({ id: "2", name: "Product 2", price: 20, quantity: 1 });

      // Act
      cart.clear();

      // Assert
      expect(cart.getTotal()).toBe(0);
    });
  });
});
```

## Testing React Components

```typescript
// src/components/Counter.tsx
export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// tests/components/Counter.test.tsx
import { render, screen, fireEvent } from "@testing-library/react";

describe("Counter", () => {
  it("should render with initial count of 0", () => {
    // Arrange & Act
    render(<Counter />);

    // Assert
    expect(screen.getByText("Count: 0")).toBeInTheDocument();
  });

  it("should increment count when button clicked", () => {
    // Arrange
    render(<Counter />);
    const button = screen.getByText("Increment");

    // Act
    fireEvent.click(button);

    // Assert
    expect(screen.getByText("Count: 1")).toBeInTheDocument();
  });

  it("should increment multiple times", () => {
    // Arrange
    render(<Counter />);
    const button = screen.getByText("Increment");

    // Act
    fireEvent.click(button);
    fireEvent.click(button);
    fireEvent.click(button);

    // Assert
    expect(screen.getByText("Count: 3")).toBeInTheDocument();
  });

  it("should reset count to 0", () => {
    // Arrange
    render(<Counter />);
    fireEvent.click(screen.getByText("Increment"));

    // Act
    fireEvent.click(screen.getByText("Reset"));

    // Assert
    expect(screen.getByText("Count: 0")).toBeInTheDocument();
  });
});
```

## Edge Case Categories

```typescript
// Test case generation template
interface TestCase {
  category: "happy-path" | "edge-case" | "error-case";
  description: string;
  input: any;
  expectedOutput: any;
}

const testCases: TestCase[] = [
  // Happy path
  {
    category: "happy-path",
    description: "typical valid input",
    input: "[email protected]",
    expectedOutput: true,
  },

  // Edge cases
  {
    category: "edge-case",
    description: "empty string",
    input: "",
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "null input",
    input: null,
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "undefined input",
    input: undefined,
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "whitespace only",
    input: "   ",
    expectedOutput: false,
  },
  {
    category: "edge-case",
    description: "very long email",
    input: "a".repeat(1000) + "@example.com",
    expectedOutput: false,
  },

  // Error cases
  {
    category: "error-case",
    description: "invalid format - no @",
    input: "userexample.com",
    expectedOutput: false,
  },
  {
    category: "error-case",
    description: "invalid format - multiple @",
    input: "user@@example.com",
    expectedOutput: false,
  },
];
```

## Coverage Notes

```typescript
/**
 * Coverage targets for this module:
 *
 * - Line coverage: 100% (all lines executed)
 * - Branch coverage: 100% (all if/else paths tested)
 * - Function coverage: 100% (all functions called)
 * - Statement coverage: 100% (all statements executed)
 *
 * Untested scenarios (intentionally):
 * - None - module is fully covered
 *
 * High-risk areas requiring extra attention:
 * - Division by zero handling
 * - Null/undefined input handling
 * - Type coercion edge cases
 */
```

## Test File Structure

```
src/
  utils/
    validator.ts
    calculator.ts
  services/
    userService.ts
  models/
    ShoppingCart.ts
  components/
    Counter.tsx

tests/
  utils/
    validator.test.ts
    calculator.test.ts
  services/
    userService.test.ts
  models/
    ShoppingCart.test.ts
  components/
    Counter.test.tsx
```

## Test Generation Script

```typescript
// scripts/generate-tests.ts
import * as fs from "fs";
import * as path from "path";

function generateTestTemplate(filePath: string): string {
  const fileName = path.basename(filePath, path.extname(filePath));
  const className = fileName.charAt(0).toUpperCase() + fileName.slice(1);

  return `
import { describe, it, expect } from 'vitest';
import { ${className} } from '@/${filePath}';

describe('${className}', () => {
  describe('happy path', () => {
    it('should handle typical case', () => {
      // Arrange
      const input = /* TODO */;

      // Act
      const result = ${className}(input);

      // Assert
      expect(result).toBe(/* TODO */);
    });
  });

  describe('edge cases', () => {
    it('should handle null input', () => {
      // Arrange
      const input = null;

      // Act & Assert
      expect(() => ${className}(input)).toThrow();
    });

    it('should handle empty input', () => {
      // Arrange
      const input = '';

      // Act
      const result = ${className}(input);

      // Assert
      expect(result).toBe(/* TODO */);
    });
  });

  describe('error cases', () => {
    it('should throw error for invalid input', () => {
      // Arrange
      const input = /* TODO */;

      // Act & Assert
      expect(() => ${className}(input)).toThrow('Invalid input');
    });
  });
});
  `.trim();
}
```

## Best Practices

1. **AAA pattern**: Arrange-Act-Assert structure
2. **One assertion per test**: Keep tests focused
3. **Descriptive names**: "should [expected behavior] when [condition]"
4. **Test edge cases**: null, undefined, empty, max values
5. **Test errors**: Verify error handling
6. **Isolated tests**: No shared state between tests
7. **Fast tests**: Unit tests should run in milliseconds

## Output Checklist

- [ ] Test file created matching source structure
- [ ] AAA pattern used consistently
- [ ] Happy path cases covered
- [ ] Edge cases identified and tested
- [ ] Error cases tested
- [ ] Async functions tested with proper awaits
- [ ] Mocks used for dependencies
- [ ] Coverage notes documented
- [ ] Descriptive test names
- [ ] Setup/teardown hooks used appropriately

© patricio0312rev · MIT · patricio0312rev/skills

Похожие скиллы

Все скиллы по теме «Тесты и QA» →
improve-codebase-architecture Matt Pocock Скилл ищет архитектурные узкие места в кодовой базе и показывает варианты углубления модулей в HTML-отчёте. Подойдёт для ревью перед рефакторингом. 1 163 035установок design-taste-frontend Leonxlnx Скилл задаёт арт-дирекцию для публичных страниц и рабочих экранов, а затем проверяет результат в браузере по скриншотам. Нужен, когда интерфейс выглядит шаблонным или недоработанным. CX Codex +1 678 700установок tdd Matt Pocock Скилл управляет циклом красный-зелёный в TDD: проверяет, что тесты пишутся до кода, держит их на уровне публичных интерфейсов и не завязывает на внутреннюю структуру. 1 141 062установок skill-creator официальный anthropics Помогает создать новый скилл, доработать существующий и проверить результат через тестовые промпты и оценки. 414 180установок agent-browser vercel-labs Скилл управляет браузером через CLI: открывает страницы, заполняет формы, нажимает кнопки и проверяет веб-приложения. Подходит для задач, где агенту нужно действовать в интерфейсе. CX Codex +1 1 200 000установок prototype Matt Pocock Создаёт временный прототип, чтобы проверить логику, модель состояния или вид интерфейса до работы над основным кодом. 986 201установок

Из того же набора36

nginx-config-optimizer Скилл собирает конфиги Nginx: reverse proxy, балансировка, кэш, SSL и лимиты запросов. Пригодится, когда нужно поднять или переписать конфиг сервера. CX Codex +1 433установок multi-tenant-safety-checker Настраивает изоляцию данных между арендаторами в PostgreSQL через Row Level Security, проверяет запросы в Prisma и создаёт тесты на утечки. CX Codex +1 423установок api-docs-generator Помогает составить спецификацию OpenAPI и развернуть интерактивную документацию API по кодовой базе. Пригодится при описании эндпоинтов, схем данных и подготовке клиентских SDK. CX Codex +1 396установок codebase-summarizer Скилл сканирует репозиторий и собирает ARCHITECTURE.md: точки входа, связи модулей, диаграммы и подсказки, куда лезть за правкой. CX Codex +1 394установок redis-patterns Скилл для ИИ-агента, который помогает писать код на Redis: кэширование, сессии, лимиты запросов, pub/sub и распределённые блокировки. CX Codex +1 361установок i18n-frontend-implementer Скилл добавляет i18n-инфраструктуру во фронтенд: переводы, форматирование дат и валют, множественные формы и переключение языка. CX Codex +1 359установок accessibility-auditor Скилл проверяет вёрстку на доступность и правит её: семантика, ARIA, клавиатура, контраст. Пригодится перед релизом формы или модалки. CX Codex +1 355установок ai-agent-orchestrator Скилл помогает собрать систему из нескольких ИИ-агентов: роли, передача задач, общая память и супервизор. CX Codex +1 352установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой