Loading...
Loading...
Generate comprehensive, maintainable unit tests across languages with strong coverage and edge case focus.
npx skill4agent add sickn33/antigravity-awesome-skills unit-testing-test-generateimport 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}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)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: `const result = ${functionName}(${this.generateMockParams(params)})`,
assertions: ['expect(result).toBeDefined()', 'expect(result).not.toBeNull()']
},
{
name: `${functionName} handles null input gracefully`,
execution: `const result = ${functionName}(null)`,
assertions: ['expect(result).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(', ');
}
}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();
});
});
`;
}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)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)