unit-testing-test-generate について
既存コードを分析し、保守しやすいユニットテストやモック、カバレッジ報告の作成を支援するコーディングエージェント向けスキルです。Python、JavaScript/TypeScript、Reactの例を含み、実際の契約に基づくアサーションを重視します。
主な機能
- 01コードとテスト不足の分析
- 02pytestテスト作成の指針
- 03Jestテスト作成の指針
- 04モックとフィクスチャの作成
- 05カバレッジ不足の確認
活用シーン
- 01既存コードへのユニットテスト追加
- 02境界条件と入力契約の確認
- 03カバレッジ不足の整理
- 04依存関係のモック作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill unit-testing-test-generateインストール後、Claude Code で/unit-testing-test-generateを入力して読み込まれたか確認します。
詳細
自動ユニットテスト生成
複数の言語やフレームワークにわたって、包括的で保守しやすいユニットテストの生成を専門とするテスト自動化のエキスパートです。カバレッジを最大化し、エッジケースを検出し、アサーションの品質とテストの構成に関するベストプラクティスに従うテストを作成してください。
このスキルを使用する場面
- 既存のコードにユニットテストが必要な場合
- テストの構成とカバレッジを統一したい場合
- モック、フィクスチャ、エッジケースの検証が必要な場合
このスキルを使用しない場面
- 統合テストまたは E2E テストだけが必要な場合
- テスト対象のソースコードにアクセスできない場合
- コンプライアンス上の理由でテストを手書きする必要がある場合
コンテキスト
ユーザーは、コード構造を分析してテストシナリオを特定し、適切なモック、アサーション、エッジケースの網羅を備えた高品質なユニットテストを作成する自動テスト生成を必要としています。フレームワーク固有のパターンと保守しやすいテストスイートに重点を置いてください。
要件
$ARGUMENTS
手順
以下のジェネレータクラスは説明用のひな型であり、複数言語に対応したジェネレータを同梱または完全に実装したものではありません。ヘルパーメソッドは省略されています。期待値、有効/無効な入力、例外の型は実際の契約から導き出してください。not None、広範な Exception アサーション、推測した UI ロールでは、正しさを証明できません。存在しないコンポーネントの props やメソッドに対するテストは生成しないでください。
1. テスト生成のためにコードを分析する
コードベースをスキャンして未テストのコードを特定し、包括的なテストスイートを生成します。
import ast
from pathlib import Path
from typing import Dict, List, Any
class TestGenerator:
def __init__(self, language: str):
self.language = language
self.framework_map = {
'python': 'pytest',
'javascript': 'jest',
'typescript': 'jest',
'java': 'junit',
'go': 'testing'
}
def analyze_file(self, file_path: str) -> Dict[str, Any]:
"""Extract testable units from source file"""
if self.language == 'python':
return self._analyze_python(file_path)
elif self.language in ['javascript', 'typescript']:
return self._analyze_javascript(file_path)
def _analyze_python(self, file_path: str) -> Dict:
with open(file_path) as f:
tree = ast.parse(f.read())
functions = []
classes = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
functions.append({
'name': node.name,
'args': [arg.arg for arg in node.args.args],
'returns': ast.unparse(node.returns) if node.returns else None,
'decorators': [ast.unparse(d) for d in node.decorator_list],
'docstring': ast.get_docstring(node),
'complexity': self._calculate_complexity(node)
})
elif isinstance(node, ast.ClassDef):
methods = [n.name for n in node.body if isinstance(n, ast.FunctionDef)]
classes.append({
'name': node.name,
'methods': methods,
'bases': [ast.unparse(base) for base in node.bases]
})
return {'functions': functions, 'classes': classes, 'file': file_path}
2. pytest で Python テストを生成する
def generate_pytest_tests(self, analysis: Dict) -> str:
"""Generate pytest test file from code analysis"""
tests = ['import pytest', 'from unittest.mock import Mock, patch', '']
module_name = Path(analysis['file']).stem
tests.append(f"from {module_name} import *\n")
for func in analysis['functions']:
if func['name'].startswith('_'):
continue
test_class = self._generate_function_tests(func)
tests.append(test_class)
for cls in analysis['classes']:
test_class = self._generate_class_tests(cls)
tests.append(test_class)
return '\n'.join(tests)
def _generate_function_tests(self, func: Dict) -> str:
"""Generate test cases for a function"""
func_name = func['name']
tests = [f"\n\nclass Test{func_name.title()}:"]
# Happy path test
tests.append(f" def test_{func_name}_success(self):")
tests.append(f" result = {func_name}({self._generate_mock_args(func['args'])})")
tests.append(f" assert result is not None\n")
# Edge case tests
if len(func['args']) > 0:
tests.append(f" def test_{func_name}_with_empty_input(self):")
tests.append(f" with pytest.raises((ValueError, TypeError)):")
tests.append(f" {func_name}({self._generate_empty_args(func['args'])})\n")
# Exception handling test
tests.append(f" def test_{func_name}_handles_errors(self):")
tests.append(f" with pytest.raises(Exception):")
tests.append(f" {func_name}({self._generate_invalid_args(func['args'])})\n")
return '\n'.join(tests)
def _generate_class_tests(self, cls: Dict) -> str:
"""Generate test cases for a class"""
tests = [f"\n\nclass Test{cls['name']}:"]
tests.append(f" @pytest.fixture")
tests.append(f" def instance(self):")
tests.append(f" return {cls['name']}()\n")
for method in cls['methods']:
if method.startswith('_') and method != '__init__':
continue
tests.append(f" def test_{method}(self, instance):")
tests.append(f" result = instance.{method}()")
tests.append(f" assert result is not None\n")
return '\n'.join(tests)
3. Jest で JavaScript/TypeScript テストを生成する
interface TestCase {
name: string;
setup?: string;
execution: string;
assertions: string[];
}
class JestTestGenerator {
generateTests(functionName: string, params: string[]): string {
const tests: TestCase[] = [
{
name: `${functionName} returns expected result with valid input`,
execution: `${functionName}(${this.generateMockParams(params)})`,
assertions: ['expect(execution).toBeDefined()', 'expect(execution).not.toBeNull()']
},
{
name: `${functionName} handles null input gracefully`,
execution: `${functionName}(null)`,
assertions: ['expect(execution).toBeDefined()']
},
{
name: `${functionName} throws error for invalid input`,
execution: `() => ${functionName}(undefined)`,
assertions: ['expect(execution).toThrow()']
}
];
return this.formatJestSuite(functionName, tests);
}
formatJestSuite(name: string, cases: TestCase[]): string {
let output = `describe('${name}', () => {\n`;
for (const testCase of cases) {
output += ` it('${testCase.name}', () => {\n`;
if (testCase.setup) {
output += ` ${testCase.setup}\n`;
}
output += ` const execution = ${testCase.execution};\n`;
for (const assertion of testCase.assertions) {
output += ` ${assertion};\n`;
}
output += ` });\n\n`;
}
output += '});\n';
return output;
}
generateMockParams(params: string[]): string {
return params.map(p => `mock${p.charAt(0).toUpperCase() + p.slice(1)}`).join(', ');
}
}
4. React コンポーネントテストを生成する
function generateReactComponentTest(componentName: string): string {
return `
import { render, screen, fireEvent } from '@testing-library/react';
import { ${componentName} } from './${componentName}';
describe('${componentName}', () => {
it('renders without crashing', () => {
render(<${componentName} />);
expect(screen.getByRole('main')).toBeInTheDocument();
});
it('displays correct initial state', () => {
render(<${componentName} />);
const element = screen.getByTestId('${componentName.toLowerCase()}');
expect(element).toBeVisible();
});
it('handles user interaction', () => {
render(<${componentName} />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(screen.getByText(/clicked/i)).toBeInTheDocument();
});
it('updates props correctly', () => {
const { rerender } = render(<${componentName} value="initial" />);
expect(screen.getByText('initial')).toBeInTheDocument();
rerender(<${componentName} value="updated" />);
expect(screen.getByText('updated')).toBeInTheDocument();
});
});
`;
}
5. カバレッジ分析と不足箇所の検出
import subprocess
import json
class CoverageAnalyzer:
def analyze_coverage(self, test_command: str) -> Dict:
"""Run tests with coverage and identify gaps"""
result = subprocess.run(
[test_command, '--coverage', '--json'],
capture_output=True,
text=True
)
coverage_data = json.loads(result.stdout)
gaps = self.identify_coverage_gaps(coverage_data)
return {
'overall_coverage': coverage_data.get('totals', {}).get('percent_covered', 0),
'uncovered_lines': gaps,
'files_below_threshold': self.find_low_coverage_files(coverage_data, 80)
}
def identify_coverage_gaps(self, coverage: Dict) -> List[Dict]:
"""Find specific lines/functions without test coverage"""
gaps = []
for file_path, data in coverage.get('files', {}).items():
missing_lines = data.get('missing_lines', [])
if missing_lines:
gaps.append({
'file': file_path,
'lines': missing_lines,
'functions': data.get('excluded_lines', [])
})
return gaps
def generate_tests_for_gaps(self, gaps: List[Dict]) -> str:
"""Generate tests specifically for uncovered code"""
tests = []
for gap in gaps:
test_code = self.create_targeted_test(gap)
tests.append(test_code)
return '\n\n'.join(tests)
6. モックの生成
def generate_mock_objects(self, dependencies: List[str]) -> str:
"""Generate mock objects for external dependencies"""
mocks = ['from unittest.mock import Mock, MagicMock, patch\n']
for dep in dependencies:
mocks.append(f"@pytest.fixture")
mocks.append(f"def mock_{dep}():")
mocks.append(f" mock = Mock(spec={dep})")
mocks.append(f" mock.method.return_value = 'mocked_result'")
mocks.append(f" return mock\n")
return '\n'.join(mocks)
出力形式
- テストファイル:すぐに実行できる完全なテストスイート
- カバレッジレポート:現在のカバレッジと特定した不足箇所
- モックオブジェクト:外部依存関係のフィクスチャ
- テストドキュメント:テストシナリオの説明
- CI 統合:パイプラインでテストを実行するコマンド
早期にバグを検出し、コード変更への信頼性を高める、保守しやすく包括的なテストの生成に重点を置いてください。
制限事項
- このスキルは、タスクが上記の対象範囲に明確に合致する場合にのみ使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を止めて確認してください。