
Tdd Guide
- 127 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Practice red-green-refactor while adding features or fixing bugs so tests drive design and catch regressions before merge or release.
About
Test-driven development guide for disciplined red-green-refactor loops: write failing tests first, implement the smallest passing code, then refactor with confidence across units, APIs, and CLI tools.
- Enforces red-green-refactor micro-cycles
- Writes minimal tests that express behavior specs
- Keeps production code driven by failing tests first
- Refactors safely with regression coverage intact
- Chooses appropriate test doubles and boundaries
Tdd Guide by the numbers
- 127 all-time installs (skills.sh)
- Ranked #930 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill tdd-guideAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 127 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Practice red-green-refactor while adding features or fixing bugs so tests drive design and catch regressions before merge or release.
Files
TDD Guide
The agent guides red-green-refactor TDD workflows, generates framework-specific test stubs from requirements, parses coverage reports to identify prioritized gaps, and calculates test quality metrics including smell detection and assertion density. Supports Jest, Pytest, JUnit, Vitest, and Mocha.
Quick Start
# Generate test cases from requirements (Python API)
from test_generator import TestGenerator, TestFramework
gen = TestGenerator(framework=TestFramework.PYTEST, language="python")
cases = gen.generate_from_requirements(requirements)
# Analyze coverage gaps from LCOV report
from coverage_analyzer import CoverageAnalyzer
analyzer = CoverageAnalyzer()
analyzer.parse_coverage_report(content, "lcov")
gaps = analyzer.identify_gaps(threshold=80.0)
# Guide TDD cycle
from tdd_workflow import TDDWorkflow
wf = TDDWorkflow()
wf.start_cycle("User can reset password via email")---
Core Workflows
Workflow 1: TDD a New Feature
1. Write a failing test for the feature requirement (RED phase) 2. Call validate_red_phase() -- confirms test exists and fails 3. Write minimal code to make the test pass (GREEN phase) 4. Call validate_green_phase() -- confirms all tests pass 5. Refactor while keeping tests green (REFACTOR phase) 6. Call validate_refactor_phase() -- confirms tests still pass after cleanup 7. Validation checkpoint: Each cycle completes in under 10 minutes; zero test smells introduced
Workflow 2: Analyze Coverage Gaps
1. Generate coverage report: npm test -- --coverage or pytest --cov 2. Detect format with detect_format() and parse with parse_coverage_report() 3. Run identify_gaps(threshold=80.0) to get prioritized file list (P0/P1/P2) 4. Generate test stubs for P0 files (business-critical, lowest coverage) 5. Validation checkpoint: Line coverage >= 80%; branch coverage >= 70%; zero P0 gaps in critical paths
Workflow 3: Generate Tests from Requirements
1. Structure requirements as user stories with acceptance criteria 2. Call generate_from_requirements() with target framework 3. Review generated test cases for completeness (happy path, error, edge cases) 4. Generate test file with generate_test_file() 5. Validation checkpoint: Each acceptance criterion has at least one test; all tests compile
---
Tools
| Tool | Purpose |
|---|---|
test_generator.py | Generate test cases from requirements/specs |
coverage_analyzer.py | Parse LCOV/JSON/XML reports, find gaps |
tdd_workflow.py | Guide red-green-refactor cycles |
framework_adapter.py | Convert tests between frameworks |
fixture_generator.py | Generate test data and mocks with seeds |
metrics_calculator.py | Calculate complexity and test quality |
format_detector.py | Auto-detect language and framework |
output_formatter.py | Format output for CLI/desktop/CI |
---
Anti-Patterns
- Tests that pass immediately -- a test with no real assertion or
assert Trueskips the RED phase; every test must fail before implementation - Testing implementation details -- coupling tests to internal method names makes refactoring break tests; test behavior and outputs, not internals
- Non-deterministic fixtures -- random data without a seed produces different failures across CI runs; always pass
seed=<int>toFixtureGenerator - Skipping the refactor phase -- GREEN code that works but is messy accumulates; refactoring is not optional in TDD
- Coverage theater -- writing tests that hit lines without meaningful assertions; use
metrics_calculator.pyto detect low assertion density - Conditional test logic --
if/elseinside tests masks failures; each test should have a single clear path
---
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Generated tests pass immediately (no RED phase) | Test has no real assertion or asserts a trivially true value | Ensure every test contains an assertion against the actual unit under test; remove placeholder assert True stubs before running |
| Coverage report fails to parse | Report format does not match the expected LCOV, JSON, or XML structure | Run format_detector.py first to verify the detected format; convert non-standard reports (e.g., Clover) to Cobertura XML |
| Framework adapter produces wrong import style | Source and target framework were swapped, or language/framework mismatch | Verify the framework and language arguments match your project; use detect_framework() on existing test code to auto-detect |
| Fixture generator produces non-deterministic data | No random seed was supplied, so each run yields different values | Pass seed=<int> to FixtureGenerator() for reproducible fixtures across CI runs |
| Metrics calculator reports 0 test functions | Test code uses an unsupported naming convention (e.g., spec_ prefix) | Rename tests to follow test_* / it() / @Test conventions, or extend the regex patterns in _count_test_functions() |
| TDD workflow validates GREEN phase but tests still fail locally | Test result dict passed to validate_green_phase() has status not set to "passed" | Ensure your test runner output is normalized to {"status": "passed"} or {"status": "failed"} before passing it in |
| Coverage gaps list is empty despite low overall coverage | All individual files meet the threshold even though the aggregate does not | Lower the threshold argument in identify_gaps() or inspect per-file coverage with get_file_coverage() |
---
Success Criteria
- Test-first ratio above 80% -- at least 4 out of every 5 features begin with a failing test before any implementation code is written.
- Red-green-refactor cycle under 10 minutes -- each TDD micro-cycle (write failing test, make it pass, refactor) completes within a single focused interval.
- Line coverage at or above 80% -- measured by
coverage_analyzer.pyagainst LCOV/JSON/XML reports, with branch coverage at or above 70%. - Test quality score at or above 75/100 -- as reported by
metrics_calculator.py, combining assertion density, isolation, naming quality, and absence of test smells. - Zero P0 coverage gaps in critical paths -- business-critical modules (auth, payments, data persistence) have no files flagged P0 by
identify_gaps(). - Test smell count of zero for high-severity items -- no
missing_assertions,sleepy_test, orconditional_test_logicsmells detected at high severity. - Fixture reproducibility across CI -- all generated fixtures use a fixed seed and produce identical output on every pipeline run.
---
Scope & Limitations
This skill covers:
- Unit test generation, scaffolding, and stub creation for Jest, Pytest, JUnit, Vitest, and Mocha
- Static coverage report parsing (LCOV, JSON/Istanbul, XML/Cobertura) with gap identification and prioritized recommendations
- Red-green-refactor workflow guidance with phase validation and cycle tracking
- Test quality assessment including complexity analysis, isolation scoring, naming quality, and test smell detection
This skill does NOT cover:
- Integration, end-to-end, or performance test generation -- see
senior-qafor E2E patterns andsenior-devopsfor load testing - Runtime test execution or live coverage measurement -- scripts perform static analysis only; you must run your test suite externally
- Visual/snapshot testing or browser-based test workflows -- use Playwright, Cypress, or Storybook for UI-level testing
- Security-focused test generation (fuzz testing, penetration testing) -- see
senior-securityandsenior-secopsskills
---
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
senior-qa | Generated test stubs feed into QA review workflows; QA coverage standards inform threshold settings | test_generator.py output → QA review → approved test suite |
code-reviewer | Metrics calculator output provides quantitative data for code review checklists | metrics_calculator.py quality report → code review scoring |
senior-fullstack | Scaffolded projects include test infrastructure; TDD guide generates tests for scaffolded modules | project_scaffolder.py output → test_generator.py input |
senior-devops | Coverage reports from CI pipelines are parsed by coverage analyzer; recommendations feed back into pipeline gates | CI coverage artifact → coverage_analyzer.py → pass/fail gate |
senior-security | Edge-case fixtures for auth and API scenarios complement security-focused test plans | fixture_generator.py auth/API edge cases → security test plan |
tech-stack-evaluator | Framework detection informs stack evaluation; test quality metrics feed into technology assessment | format_detector.py analysis → stack evaluation input |
---
Tool Reference
1. test_generator.py
Purpose: Generate test cases from requirements, user stories, and API specs, then produce framework-specific test stubs and complete test files.
Module: TestGenerator class
Usage:
from test_generator import TestGenerator, TestFramework, TestType
gen = TestGenerator(framework=TestFramework.PYTEST, language="python")
cases = gen.generate_from_requirements(requirements, test_type=TestType.UNIT)
stub = gen.generate_test_stub(cases[0])
file_content = gen.generate_test_file("my_module", cases)
suggestions = gen.suggest_missing_scenarios(existing_tests, code_analysis)Constructor Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
framework | TestFramework | Yes | Target framework: JEST, VITEST, PYTEST, JUNIT, MOCHA |
language | str | Yes | Programming language: typescript, javascript, python, java |
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
generate_from_requirements(requirements, test_type) | requirements: dict with user_stories, acceptance_criteria, api_specs; test_type: TestType enum (default UNIT) | List[Dict] of test case specs |
generate_test_stub(test_case) | test_case: single test case dict | str -- framework-specific test stub code |
generate_test_file(module_name, test_cases) | module_name: str; test_cases: optional list (uses stored cases if omitted) | str -- complete test file with imports |
suggest_missing_scenarios(existing_tests, code_analysis) | existing_tests: list of test name strings; code_analysis: dict with error_handlers, conditional_branches, input_validation | List[Dict] of suggested test scenarios |
Output Formats: Python dict/list (test case specifications), string (generated code).
Example:
requirements = {
"user_stories": [{"action": "login", "given": ["valid credentials"], "when": "submit form", "then": "redirect to dashboard"}],
"api_specs": [{"method": "POST", "path": "/auth/login", "requires_auth": False, "required_params": ["email", "password"]}]
}
gen = TestGenerator(framework=TestFramework.JEST, language="typescript")
cases = gen.generate_from_requirements(requirements)
print(gen.generate_test_file("auth_service", cases))---
2. coverage_analyzer.py
Purpose: Parse coverage reports in LCOV, JSON (Istanbul/nyc), and XML (Cobertura) formats. Calculate summary metrics, identify files below threshold, and generate prioritized recommendations.
Module: CoverageAnalyzer class
Usage:
from coverage_analyzer import CoverageAnalyzer
analyzer = CoverageAnalyzer()
data = analyzer.parse_coverage_report(report_content, format_type="lcov")
summary = analyzer.calculate_summary()
gaps = analyzer.identify_gaps(threshold=80.0)
recs = analyzer.generate_recommendations()
file_detail = analyzer.get_file_coverage("src/auth.ts")
detected = analyzer.detect_format(raw_content)Constructor Parameters: None.
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
parse_coverage_report(report_content, format_type) | report_content: str; format_type: "lcov", "json", "xml", "cobertura" | Dict of per-file coverage data |
calculate_summary() | None | Dict with line_coverage, branch_coverage, function_coverage, totals |
identify_gaps(threshold) | threshold: float (default 80.0) | List[Dict] of files below threshold with priority P0/P1/P2 |
generate_recommendations() | None | List[Dict] of prioritized recommendations |
get_file_coverage(file_path) | file_path: str | Dict with per-file line/branch/function coverage |
detect_format(content) | content: str | str -- "lcov", "json", or "xml" |
Output Formats: Python dict/list. Use output_formatter.py for terminal/markdown/JSON rendering.
Example:
with open("coverage/lcov.info") as f:
content = f.read()
analyzer = CoverageAnalyzer()
fmt = analyzer.detect_format(content)
analyzer.parse_coverage_report(content, fmt)
summary = analyzer.calculate_summary()
# {'line_coverage': 76.5, 'branch_coverage': 62.3, ...}
gaps = analyzer.identify_gaps(threshold=80.0)
# [{'file': 'src/auth.ts', 'line_coverage': 45.0, 'priority': 'P0', ...}]---
3. tdd_workflow.py
Purpose: Guide users through red-green-refactor TDD cycles with phase validation, workflow state tracking, and refactoring suggestions.
Module: TDDWorkflow class
Usage:
from tdd_workflow import TDDWorkflow
wf = TDDWorkflow()
guidance = wf.start_cycle("User can reset password via email")
red_result = wf.validate_red_phase(test_code, test_result={"status": "failed"})
green_result = wf.validate_green_phase(impl_code, {"status": "passed"})
refactor_result = wf.validate_refactor_phase(original, refactored, {"status": "passed"})
phase_guide = wf.get_phase_guidance()
summary = wf.generate_workflow_summary()Constructor Parameters: None.
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
start_cycle(requirement) | requirement: str -- user story or feature description | Dict with phase, instruction, checklist, tips |
validate_red_phase(test_code, test_result) | test_code: str; test_result: optional dict with status key | Dict with phase_complete, validations, next instruction |
validate_green_phase(implementation_code, test_result) | implementation_code: str; test_result: dict with status key | Dict with phase_complete, validations, refactoring_suggestions |
validate_refactor_phase(original_code, refactored_code, test_result) | original_code: str; refactored_code: str; test_result: dict with status key | Dict with phase_complete, cycle_complete, next steps |
get_phase_guidance(phase) | phase: optional TDDPhase enum (uses current phase if omitted) | Dict with goal, steps, common mistakes, tips |
generate_workflow_summary() | None | str -- markdown summary of current state and completed cycles |
Output Formats: Python dict (validation results), string (summary).
Example:
wf = TDDWorkflow()
wf.start_cycle("Add email validation to signup form")
result = wf.validate_red_phase("def test_invalid_email():\n assert validate('bad') == False", {"status": "failed"})
# {'phase_complete': True, 'next_phase': 'GREEN', ...}---
4. framework_adapter.py
Purpose: Provide multi-framework support with adapters for Jest, Vitest, Pytest, unittest, JUnit, TestNG, Mocha, and Jasmine. Generate framework-specific imports, test suites, test functions, assertions, and setup/teardown hooks.
Module: FrameworkAdapter class
Usage:
from framework_adapter import FrameworkAdapter, Framework, Language
adapter = FrameworkAdapter(framework=Framework.JEST, language=Language.TYPESCRIPT)
imports = adapter.generate_imports()
suite = adapter.generate_test_suite_wrapper("AuthService", test_content)
test_fn = adapter.generate_test_function("should reject invalid email", body, "Validates email format")
assertion = adapter.generate_assertion("result", "true", "true")
hooks = adapter.generate_setup_teardown(setup_code="db = create_test_db()", teardown_code="db.close()")
detected = adapter.detect_framework(existing_code)Constructor Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
framework | Framework | Yes | JEST, VITEST, PYTEST, UNITTEST, JUNIT, TESTNG, MOCHA, JASMINE |
language | Language | Yes | TYPESCRIPT, JAVASCRIPT, PYTHON, JAVA |
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
generate_imports() | None | str -- framework-specific import statements |
generate_test_suite_wrapper(suite_name, test_content) | suite_name: str; test_content: str | str -- complete test suite wrapping content |
generate_test_function(test_name, test_body, description) | test_name: str; test_body: str; description: str (default "") | str -- complete test function |
generate_assertion(actual, expected, assertion_type) | actual: str; expected: str; assertion_type: "equals", "not_equals", "true", "false", "throws" (default "equals") | str -- assertion statement |
generate_setup_teardown(setup_code, teardown_code) | setup_code: str (default ""); teardown_code: str (default "") | str -- setup/teardown hooks |
detect_framework(code) | code: str | Framework enum or None |
Output Formats: String (generated code).
Example:
adapter = FrameworkAdapter(Framework.PYTEST, Language.PYTHON)
print(adapter.generate_imports())
# import pytest
print(adapter.generate_assertion("calculate_total(items)", "150.0", "equals"))
# assert calculate_total(items) == 150.0---
5. fixture_generator.py
Purpose: Generate realistic test data, boundary values, edge-case scenarios, and mock objects for various domains (auth, payment, form, API, file upload).
Module: FixtureGenerator class
Usage:
from fixture_generator import FixtureGenerator
gen = FixtureGenerator(seed=42)
boundaries = gen.generate_boundary_values("int", {"min": 0, "max": 255})
edge_cases = gen.generate_edge_cases("auth")
mocks = gen.generate_mock_data(schema, count=5)
fixture_content = gen.generate_fixture_file("users", mocks, format="json")Constructor Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
seed | int or None | No | Random seed for reproducible output (default None) |
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
generate_boundary_values(data_type, constraints) | data_type: "int", "string", "array", "date", "email", "url"; constraints: optional dict (min, max, min_length, max_length, min_size, max_size) | List of boundary values |
generate_edge_cases(scenario, context) | scenario: "auth", "payment", "form", "api", "file_upload"; context: optional dict (required for "form" with fields key) | List[Dict] of edge case scenarios |
generate_mock_data(schema, count) | schema: dict mapping field names to {"type": ...} defs; count: int (default 1) | List[Dict] of mock objects |
generate_fixture_file(fixture_name, data, format) | fixture_name: str; data: any; format: "json", "python", "yaml" (default "json") | str -- fixture file content |
Supported Schema Field Types: string, int, float, bool, email, date, array.
Output Formats: Python list/dict (data), string (file content in JSON/Python/YAML).
Example:
gen = FixtureGenerator(seed=123)
schema = {
"id": {"type": "int", "min": 1, "max": 9999},
"email": {"type": "email"},
"active": {"type": "bool"}
}
users = gen.generate_mock_data(schema, count=3)
print(gen.generate_fixture_file("test_users", users, format="json"))---
6. metrics_calculator.py
Purpose: Calculate comprehensive test and code quality metrics including cyclomatic/cognitive complexity, testability scoring, test quality assessment (assertions, isolation, naming, smells), and execution analysis.
Module: MetricsCalculator class
Usage:
from metrics_calculator import MetricsCalculator
calc = MetricsCalculator()
all_metrics = calc.calculate_all_metrics(source_code, test_code, coverage_data, execution_data)
complexity = calc.calculate_complexity(source_code)
test_quality = calc.calculate_test_quality(test_code)
execution = calc.analyze_execution_metrics(execution_data)
summary = calc.generate_metrics_summary()Constructor Parameters: None.
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
calculate_all_metrics(source_code, test_code, coverage_data, execution_data) | source_code: str; test_code: str; coverage_data: optional dict; execution_data: optional dict | Dict with complexity, test_quality, coverage, execution |
calculate_complexity(code) | code: str | Dict with cyclomatic_complexity, cognitive_complexity, testability_score, assessment |
calculate_test_quality(test_code) | test_code: str | Dict with total_tests, total_assertions, avg_assertions_per_test, isolation_score, naming_quality, test_smells, quality_score |
analyze_execution_metrics(execution_data) | execution_data: dict with tests list (each having duration, status, optional failure_rate) | Dict with total_tests, timing stats, slow_tests, flaky_tests, pass_rate |
generate_metrics_summary() | None | str -- human-readable markdown summary |
Output Formats: Python dict (metrics data), string (markdown summary).
Example:
calc = MetricsCalculator()
complexity = calc.calculate_complexity(open("src/auth.py").read())
# {'cyclomatic_complexity': 8, 'cognitive_complexity': 12, 'testability_score': 82.0, 'assessment': 'Medium complexity - moderately testable'}
quality = calc.calculate_test_quality(open("tests/test_auth.py").read())
# {'quality_score': 78.5, 'test_smells': [], ...}---
7. format_detector.py
Purpose: Automatically detect programming language, testing framework, coverage report format, and project structure from code content or file paths.
Module: FormatDetector class
Usage:
from format_detector import FormatDetector
detector = FormatDetector()
language = detector.detect_language(code)
framework = detector.detect_test_framework(test_code)
cov_format = detector.detect_coverage_format(report_content)
input_info = detector.detect_input_format(raw_input)
file_info = detector.extract_file_info("/src/auth.service.ts")
test_name = detector.suggest_test_file_name("auth.service.ts", "jest")
patterns = detector.identify_test_patterns(test_code)
project = detector.analyze_project_structure(file_path_list)
env = detector.detect_environment()Constructor Parameters: None.
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
detect_language(code) | code: str | str -- "typescript", "javascript", "python", "java", "unknown" |
detect_test_framework(code) | code: str | str -- "jest", "vitest", "pytest", "unittest", "junit", "mocha", "unknown" |
detect_coverage_format(content) | content: str | str -- "lcov", "json", "xml", "unknown" |
detect_input_format(input_data) | input_data: str | Dict with format, language, framework, content_type |
extract_file_info(file_path) | file_path: str | Dict with file_name, extension, language, is_test, purpose |
suggest_test_file_name(source_file, framework) | source_file: str; framework: str | str -- suggested test file name |
identify_test_patterns(code) | code: str | List[str] of detected patterns (AAA, Given-When-Then, etc.) |
analyze_project_structure(file_paths) | file_paths: list of str | Dict with primary_language, test_ratio, suggested_framework |
detect_environment() | None | Dict with environment, output_preference |
Output Formats: String (detection result), Python dict (detailed analysis).
Example:
detector = FormatDetector()
print(detector.detect_language("const add = (a: number, b: number): number => a + b;"))
# "typescript"
print(detector.suggest_test_file_name("UserService.java", "junit"))
# "UserserviceTest.java"
print(detector.identify_test_patterns("// Arrange\nsetup()\n// Act\nresult = run()\n// Assert\nassert result"))
# ['AAA (Arrange-Act-Assert)']---
8. output_formatter.py
Purpose: Context-aware output formatting for different environments (Desktop/markdown, CLI/terminal, API/JSON). Supports progressive disclosure, token-efficient summary reports, and output truncation.
Module: OutputFormatter class
Usage:
from output_formatter import OutputFormatter
fmt = OutputFormatter(environment="cli", verbose=False)
cov_output = fmt.format_coverage_summary(summary, detailed=True)
rec_output = fmt.format_recommendations(recommendations, max_items=5)
test_output = fmt.format_test_results(results, show_details=True)
report = fmt.create_summary_report(coverage, metrics, recommendations)
should_detail = fmt.should_show_detailed(data_size=50)
truncated = fmt.truncate_output(long_text, max_lines=30)Constructor Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
environment | str | No | Target environment: "desktop", "cli", "api" (default "cli") |
verbose | bool | No | Include detailed output (default False) |
Key Methods:
| Method | Parameters | Returns |
|---|---|---|
format_coverage_summary(summary, detailed) | summary: dict; detailed: bool (default False) | str -- formatted coverage (markdown/terminal/JSON based on environment) |
format_recommendations(recommendations, max_items) | recommendations: list of dicts; max_items: optional int | str -- formatted recommendations grouped by priority |
format_test_results(results, show_details) | results: dict with total_tests, passed, failed, skipped, failed_tests; show_details: bool (default False) | str -- formatted test results |
create_summary_report(coverage, metrics, recommendations) | coverage: dict; metrics: dict; recommendations: list | str -- token-efficient summary (<200 tokens) |
should_show_detailed(data_size) | data_size: int | bool -- whether to show detailed output |
truncate_output(text, max_lines) | text: str; max_lines: int (default 50) | str -- truncated text with remaining-lines indicator |
Output Formats: String in markdown (desktop), plain text (CLI), or JSON (API) depending on environment setting.
Example:
fmt = OutputFormatter(environment="desktop", verbose=True)
print(fmt.format_coverage_summary({"line_coverage": 82.5, "branch_coverage": 71.0, "function_coverage": 90.0}))
# ## Test Coverage Summary
# ### Overall Metrics
# - **Line Coverage**: 82.5%
# ...{
"test_generation": {
"generated_tests": [
{
"name": "should_validate_password_length_successfully",
"type": "happy_path",
"priority": "P0",
"framework": "jest",
"code": "it('should validate password with sufficient length', () => {\n const validator = new PasswordValidator();\n const result = validator.validate('Test@123');\n expect(result).toBe(true);\n});"
},
{
"name": "should_handle_too_short_password",
"type": "error_case",
"priority": "P0",
"framework": "jest",
"code": "it('should reject password shorter than 8 characters', () => {\n const validator = new PasswordValidator();\n const result = validator.validate('Test@1');\n expect(result).toBe(false);\n});"
}
],
"test_file": "password-validator.test.ts",
"total_tests_generated": 8
},
"coverage_analysis": {
"summary": {
"line_coverage": 100.0,
"branch_coverage": 100.0,
"function_coverage": 100.0,
"total_lines": 20,
"covered_lines": 20,
"total_branches": 12,
"covered_branches": 12
},
"gaps": [],
"assessment": "Excellent coverage - all paths tested"
},
"metrics": {
"complexity": {
"cyclomatic_complexity": 6,
"cognitive_complexity": 8,
"testability_score": 85.0,
"assessment": "Medium complexity - moderately testable"
},
"test_quality": {
"total_tests": 8,
"total_assertions": 16,
"avg_assertions_per_test": 2.0,
"isolation_score": 95.0,
"naming_quality": 87.5,
"quality_score": 88.0,
"test_smells": []
}
},
"recommendations": [
{
"priority": "P1",
"type": "edge_case_coverage",
"message": "Consider adding boundary value tests",
"action": "Add tests for exact boundary conditions (7 vs 8 characters)",
"impact": "medium"
},
{
"priority": "P2",
"type": "test_organization",
"message": "Group related tests using describe blocks",
"action": "Organize tests by feature (length validation, complexity validation)",
"impact": "low"
}
],
"tdd_workflow": {
"current_phase": "GREEN",
"status": "Tests passing, ready for refactoring",
"next_steps": [
"Review code for duplication",
"Consider extracting validation rules",
"Commit changes"
]
}
}
TN:
SF:src/auth/password-validator.ts
FN:3,(anonymous_0)
FN:4,validate
FNDA:10,(anonymous_0)
FNDA:25,validate
FNF:2
FNH:2
DA:1,1
DA:2,1
DA:3,1
DA:4,25
DA:5,25
DA:6,10
DA:7,20
DA:8,8
DA:9,15
DA:10,5
DA:11,12
DA:12,3
LF:12
LH:12
BRDA:5,0,0,10
BRDA:5,0,1,15
BRDA:7,1,0,8
BRDA:7,1,1,12
BRDA:9,2,0,5
BRDA:9,2,1,10
BRDA:11,3,0,3
BRDA:11,3,1,9
BRF:8
BRH:8
end_of_record
TN:
SF:src/utils/discount-calculator.ts
FN:1,calculateDiscount
FNDA:15,calculateDiscount
FNF:1
FNH:1
DA:1,1
DA:2,15
DA:3,15
DA:4,2
DA:5,13
DA:6,1
DA:8,12
DA:9,12
LF:8
LH:8
BRDA:3,0,0,2
BRDA:3,0,1,13
BRDA:5,1,0,1
BRDA:5,1,1,12
BRF:4
BRH:4
end_of_record
{
"language": "python",
"framework": "pytest",
"source_code": "def calculate_discount(price: float, discount_percent: float) -> float:\n \"\"\"Calculate discounted price.\"\"\"\n if price < 0:\n raise ValueError(\"Price cannot be negative\")\n if discount_percent < 0 or discount_percent > 100:\n raise ValueError(\"Discount must be between 0 and 100\")\n \n discount_amount = price * (discount_percent / 100)\n return round(price - discount_amount, 2)",
"requirements": {
"user_stories": [
{
"description": "Calculate discounted price for valid inputs",
"action": "calculate_discount",
"given": ["Price is 100", "Discount is 20%"],
"when": "Discount is calculated",
"then": "Return 80.00",
"error_conditions": [
{
"condition": "negative_price",
"description": "Price is negative",
"error_type": "ValueError"
},
{
"condition": "invalid_discount",
"description": "Discount is out of range",
"error_type": "ValueError"
}
],
"edge_cases": [
{
"scenario": "zero_discount",
"description": "Discount is 0%"
},
{
"scenario": "full_discount",
"description": "Discount is 100%"
}
]
}
]
},
"coverage_threshold": 90
}
{
"language": "typescript",
"framework": "jest",
"source_code": "export class PasswordValidator {\n validate(password: string): boolean {\n if (password.length < 8) return false;\n if (!/[A-Z]/.test(password)) return false;\n if (!/[a-z]/.test(password)) return false;\n if (!/[0-9]/.test(password)) return false;\n if (!/[!@#$%^&*]/.test(password)) return false;\n return true;\n }\n}",
"requirements": {
"user_stories": [
{
"description": "Password must be at least 8 characters long",
"action": "validate_password_length",
"given": ["User provides password"],
"when": "Password is validated",
"then": "Reject if less than 8 characters"
},
{
"description": "Password must contain uppercase, lowercase, number, and special character",
"action": "validate_password_complexity",
"given": ["User provides password"],
"when": "Password is validated",
"then": "Reject if missing any character type"
}
],
"acceptance_criteria": [
{
"id": "AC1",
"description": "Valid password: 'Test@123'",
"verification_steps": ["Call validate with 'Test@123'", "Should return true"]
},
{
"id": "AC2",
"description": "Invalid password: 'test' (too short)",
"verification_steps": ["Call validate with 'test'", "Should return false"]
}
]
},
"coverage_threshold": 80
}
How to Use the TDD Guide Skill
The TDD Guide skill helps engineering teams implement Test Driven Development with intelligent test generation, coverage analysis, and workflow guidance.
Basic Usage
Generate Tests from Requirements
@tdd-guide
I need to implement a user registration feature. Generate test cases for:
- Email validation
- Password strength checking
- Duplicate email detection
Language: TypeScript
Framework: JestAnalyze Test Coverage
@tdd-guide
Analyze test coverage for my authentication module.
Coverage report: coverage/lcov.info
Source code: src/auth/
Identify gaps and prioritize improvements.Get TDD Workflow Guidance
@tdd-guide
Guide me through TDD for implementing a shopping cart feature.
Requirements:
- Add items to cart
- Update quantities
- Calculate totals
- Apply discount codes
Framework: PytestExample Invocations
Example 1: Generate Tests from Code
@tdd-guide
Generate comprehensive tests for this function:
export function calculateTax(amount: number, rate: number): number { if (amount < 0) throw new Error('Amount cannot be negative'); if (rate < 0 || rate > 1) throw new Error('Rate must be between 0 and 1'); return Math.round(amount rate 100) / 100; }
Include:
- Happy path tests
- Error cases
- Boundary values
- Edge casesExample 2: Improve Coverage
@tdd-guide
My coverage is at 65%. Help me get to 80%.
Coverage report:
[paste LCOV or JSON coverage data]
Source files:
- src/services/payment-processor.ts
- src/services/order-validator.ts
Prioritize critical paths.Example 3: Review Test Quality
@tdd-guide
Review the quality of these tests:
def test_login(): result = login("user", "pass") assert result is not None assert result.status == "success" assert result.token != "" assert len(result.permissions) > 0
def test_login_fails(): result = login("bad", "wrong") assert result is None
Suggest improvements for:
- Test isolation
- Assertion quality
- Naming conventions
- Test organizationExample 4: Framework Migration
@tdd-guide
Convert these Jest tests to Pytest:
describe('Calculator', () => { it('should add two numbers', () => { const result = add(2, 3); expect(result).toBe(5); });
it('should handle negative numbers', () => { const result = add(-2, 3); expect(result).toBe(1); }); });
Maintain test structure and coverage.Example 5: Generate Test Fixtures
@tdd-guide
Generate realistic test fixtures for:
Entity: User
Fields:
- id (UUID)
- email (valid format)
- age (18-100)
- role (admin, user, guest)
Generate 5 fixtures with edge cases:
- Minimum age boundary
- Maximum age boundary
- Special characters in emailWhat to Provide
For Test Generation
- Source code (TypeScript, JavaScript, Python, or Java)
- Requirements (user stories, API specs, or business rules)
- Testing framework preference (Jest, Pytest, JUnit, Vitest)
- Specific scenarios to cover (optional)
For Coverage Analysis
- Coverage report (LCOV, JSON, or XML format)
- Source code files (optional, for context)
- Coverage threshold target (e.g., 80%)
For TDD Workflow
- Feature requirements
- Current phase (RED, GREEN, or REFACTOR)
- Test code and implementation (for validation)
For Quality Review
- Existing test code
- Specific quality concerns (isolation, naming, assertions)
What You'll Get
Test Generation Output
- Complete test files with proper structure
- Test stubs with arrange-act-assert pattern
- Framework-specific imports and syntax
- Coverage for happy paths, errors, and edge cases
Coverage Analysis Output
- Overall coverage summary (line, branch, function)
- Identified gaps with file/line numbers
- Prioritized recommendations (P0, P1, P2)
- Visual coverage indicators
TDD Workflow Output
- Step-by-step guidance for current phase
- Validation of RED/GREEN/REFACTOR completion
- Refactoring suggestions
- Next steps in TDD cycle
Quality Review Output
- Test quality score (0-100)
- Detected test smells
- Isolation and naming analysis
- Specific improvement recommendations
Tips for Best Results
Test Generation
1. Be specific: "Generate tests for password validation" is better than "generate tests" 2. Provide context: Include edge cases and error conditions you want covered 3. Specify framework: Mention Jest, Pytest, JUnit, etc., for correct syntax
Coverage Analysis
1. Use recent reports: Coverage data should match current codebase 2. Provide thresholds: Specify your target coverage percentage 3. Focus on critical code: Prioritize coverage for business logic
TDD Workflow
1. Start with requirements: Clear requirements lead to better tests 2. One cycle at a time: Complete RED-GREEN-REFACTOR before moving on 3. Validate each phase: Run tests and share results for accurate guidance
Quality Review
1. Share full context: Include test setup/teardown and helper functions 2. Ask specific questions: "Is my isolation good?" gets better answers than "review this" 3. Iterative improvement: Implement suggestions incrementally
Advanced Usage
Multi-Language Projects
@tdd-guide
Analyze coverage across multiple languages:
- Frontend: TypeScript (Jest) - src/frontend/
- Backend: Python (Pytest) - src/backend/
- API: Java (JUnit) - src/api/
Provide unified coverage report and recommendations.CI/CD Integration
@tdd-guide
Generate coverage report for CI pipeline.
Input: coverage/coverage-final.json
Output format: JSON
Include:
- Pass/fail based on 80% threshold
- Changed files coverage
- Trend comparison with main branchParameterized Test Generation
@tdd-guide
Generate parameterized tests for:
Function: validateEmail(email: string): boolean
Test cases:
- valid@example.com → true
- invalid.email → false
- @example.com → false
- user@domain.co.uk → true
Framework: Jest (test.each)Related Commands
/code-review- Review code quality and suggest improvements/test- Run tests and analyze results/refactor- Get refactoring suggestions while keeping tests green
Troubleshooting
Issue: Generated tests don't match my framework syntax
- Solution: Explicitly specify framework (e.g., "using Pytest" or "with Jest")
Issue: Coverage analysis shows 0% coverage
- Solution: Verify coverage report format (LCOV, JSON, XML) and try including raw content
Issue: TDD workflow validation fails
- Solution: Ensure you're providing test results (passed/failed status) along with code
Issue: Too many recommendations
- Solution: Ask for "top 3 P0 recommendations only" for focused output
Version Support
- Node.js: 16+ (Jest 29+, Vitest 0.34+)
- Python: 3.8+ (Pytest 7+)
- Java: 11+ (JUnit 5.9+)
- TypeScript: 4.5+
Feedback
If you encounter issues or have suggestions, please mention:
- Language and framework used
- Type of operation (generation, analysis, workflow)
- Expected vs. actual behavior
TDD Guide - Test Driven Development Skill
Version: 1.0.0 Last Updated: November 5, 2025 Author: Claude Skills Factory
A comprehensive Test Driven Development skill for Claude Code that provides intelligent test generation, coverage analysis, framework integration, and TDD workflow guidance across multiple languages and testing frameworks.
Table of Contents
- Overview
- Features
- Installation
- Quick Start
- Python Modules
- Usage Examples
- Configuration
- Supported Frameworks
- Output Formats
- Best Practices
- Troubleshooting
- Contributing
- License
Overview
The TDD Guide skill transforms how engineering teams implement Test Driven Development by providing:
- Intelligent Test Generation: Convert requirements into executable test cases
- Coverage Analysis: Parse LCOV, JSON, XML reports and identify gaps
- Multi-Framework Support: Jest, Pytest, JUnit, Vitest, and more
- TDD Workflow Guidance: Step-by-step red-green-refactor guidance
- Quality Metrics: Comprehensive test and code quality analysis
- Context-Aware Output: Optimized for Desktop, CLI, or API usage
Features
Test Generation (3 capabilities)
1. Generate Test Cases from Requirements - User stories → Test cases 2. Create Test Stubs - Proper scaffolding with framework patterns 3. Generate Test Fixtures - Realistic test data and boundary values
TDD Workflow (3 capabilities)
1. Red-Green-Refactor Guidance - Phase-by-phase validation 2. Suggest Missing Scenarios - Identify untested edge cases 3. Review Test Quality - Isolation, assertions, naming analysis
Coverage & Metrics (6 categories)
1. Test Coverage - Line/branch/function with gap analysis 2. Code Complexity - Cyclomatic/cognitive complexity 3. Test Quality - Assertions, isolation, naming scoring 4. Test Data - Boundary values, edge cases 5. Test Execution - Timing, slow tests, flakiness 6. Missing Tests - Uncovered paths and error handlers
Framework Integration (4 capabilities)
1. Multi-Framework Adapters - Jest, Pytest, JUnit, Vitest, Mocha 2. Generate Boilerplate - Proper imports and test structure 3. Configure Runners - Setup and coverage configuration 4. Framework Detection - Automatic framework identification
Installation
Claude Code (Desktop)
1. Download the skill folder:
# Option A: Clone from repository
git clone https://github.com/your-org/tdd-guide-skill.git
# Option B: Download ZIP and extract2. Install to Claude skills directory:
# Project-level (recommended for team projects)
cp -r tdd-guide /path/to/your/project/.claude/skills/
# User-level (available for all projects)
cp -r tdd-guide ~/.claude/skills/3. Verify installation:
ls ~/.claude/skills/tdd-guide/
# Should show: SKILL.md, *.py files, samplesClaude Apps (Browser)
1. Use the skill-creator skill to import the ZIP file 2. Or manually upload files through the skills interface
Claude API
# Upload skill via API
import anthropic
client = anthropic.Anthropic(api_key="your-api-key")
# Create skill with files
skill = client.skills.create(
name="tdd-guide",
files=["tdd-guide/SKILL.md", "tdd-guide/*.py"]
)Quick Start
1. Generate Tests from Requirements
@tdd-guide
Generate tests for password validation function:
- Min 8 characters
- At least 1 uppercase, 1 lowercase, 1 number, 1 special char
Language: TypeScript
Framework: Jest2. Analyze Coverage
@tdd-guide
Analyze coverage from: coverage/lcov.info
Target: 80% coverage
Prioritize recommendations3. TDD Workflow
@tdd-guide
Guide me through TDD for implementing user authentication.
Requirements: Email/password login, session management
Framework: PytestPython Modules
The skill includes 8 Python modules organized by functionality:
Core Modules (7 files)
1. test_generator.py (450 lines)
- Generate test cases from requirements
- Create test stubs with proper structure
- Suggest missing scenarios based on code analysis
- Support for multiple test types (unit, integration, e2e)
2. coverage_analyzer.py (380 lines)
- Parse LCOV, JSON, XML coverage reports
- Calculate line/branch/function coverage
- Identify coverage gaps with prioritization
- Generate actionable recommendations
3. metrics_calculator.py (420 lines)
- Cyclomatic and cognitive complexity analysis
- Test quality scoring (isolation, assertions, naming)
- Test smell detection
- Execution metrics analysis
4. framework_adapter.py (480 lines)
- Multi-framework adapters (Jest, Pytest, JUnit, Vitest, Mocha)
- Generate framework-specific imports and structure
- Assertion syntax translation
- Setup/teardown hook generation
5. tdd_workflow.py (380 lines)
- Red-Green-Refactor phase guidance
- Phase validation and progression
- Refactoring suggestions
- Workflow state management
6. fixture_generator.py (340 lines)
- Boundary value generation
- Edge case scenario creation
- Mock data generation from schemas
- Fixture file export (JSON, YAML, Python)
7. format_detector.py (280 lines)
- Automatic language detection
- Testing framework identification
- Coverage format detection
- Project structure analysis
Utility Modules (1 file)
8. output_formatter.py (260 lines)
- Context-aware formatting (Desktop, CLI, API)
- Progressive disclosure
- Token-efficient reporting
- Summary and detailed views
Total Lines of Code
- Python: ~3,400 lines
- Documentation: ~1,200 lines
- Samples: ~200 lines
- Total: ~4,400 lines
Usage Examples
Example 1: Generate Tests from User Story
Input:
@tdd-guide
User Story: As a user, I want to add items to my shopping cart
Acceptance Criteria:
- Add single item with quantity
- Update quantity if item already exists
- Validate quantity is positive
- Return updated cart total
Language: Python
Framework: PytestOutput:
import pytest
from shopping_cart import ShoppingCart
class TestShoppingCart:
"""Test suite for shopping cart functionality."""
def test_add_single_item_successfully(self):
"""Should add single item with quantity."""
cart = ShoppingCart()
cart.add_item('apple', quantity=3, price=1.50)
assert len(cart.items) == 1
assert cart.items['apple']['quantity'] == 3
assert cart.get_total() == 4.50
def test_update_quantity_for_existing_item(self):
"""Should update quantity if item already exists."""
cart = ShoppingCart()
cart.add_item('apple', quantity=3, price=1.50)
cart.add_item('apple', quantity=2, price=1.50)
assert cart.items['apple']['quantity'] == 5
assert cart.get_total() == 7.50
def test_reject_negative_quantity(self):
"""Should validate quantity is positive."""
cart = ShoppingCart()
with pytest.raises(ValueError, match="Quantity must be positive"):
cart.add_item('apple', quantity=-1, price=1.50)Example 2: Coverage Analysis with Recommendations
Input:
@tdd-guide
Analyze coverage:
[Coverage report data]
SF:src/auth/login.ts
DA:1,5
DA:2,5
DA:3,3
DA:4,0
DA:5,0
...
Target: 80% coverageOutput:
## Coverage Summary
- Line Coverage: 65.0%
- Branch Coverage: 58.3%
- Function Coverage: 75.0%
⚠️ **65.0%** coverage (yellow) - Below 80% threshold
## Recommendations
### 🔴 Critical (P0)
1. **Error handling paths not covered**
- Action: Add tests for authentication failure scenarios
- File: `src/auth/login.ts`
- Lines: 45-52, 67-70
2. **Branch coverage gap in validation**
- Action: Test all conditional branches in input validation
- File: `src/auth/login.ts`
- Lines: 23-28
### 🟡 Important (P1)
1. **Edge cases missing**
- Action: Add boundary tests for password length (7, 8, 9 chars)
- Impact: MediumExample 3: TDD Red-Green-Refactor Guidance
Input:
@tdd-guide
Guide TDD for email validation.
Requirement: Email must contain @ and valid domain
Phase: RED (write failing test)Output:
## TDD Phase: RED
### Goal
Write a failing test that describes desired email validation behavior
### Steps
1. ✅ Read requirement: Email must contain @ and valid domain
2. ✅ Think about expected behavior
3. ⏳ Write test that verifies this behavior
4. ⏳ Run test and ensure it fails
5. ⏳ Verify failure reason is correct (not syntax error)
### Test Template (Jest)
describe('EmailValidator', () => { it('should accept valid email format', () => { // Arrange const validator = new EmailValidator();
// Act const result = validator.validate('user@example.com');
// Assert expect(result).toBe(true); });
it('should reject email without @ symbol', () => { const validator = new EmailValidator(); const result = validator.validate('userexample.com'); expect(result).toBe(false); }); });
### Next Steps
1. Run the test (it should fail)
2. Verify failure: "EmailValidator is not defined" or similar
3. Share test result to proceed to GREEN phaseExample 4: Test Quality Review
Input:
@tdd-guide
Review test quality:
def test_user_login():
result = login("admin", "password123")
assert result
assert result["status"] == "success"
assert result["token"]
assert result["user"]["id"] > 0
assert result["user"]["role"] == "admin"Output:
## Test Quality Analysis
**Overall Score**: 62/100
### Issues Detected
1. **Assertion Roulette** (High Severity)
- 5 assertions in single test
- **Fix**: Split into multiple focused tests
2. **Poor Test Isolation** (Medium Severity)
- Test depends on database state ("admin" user existing)
- **Fix**: Use fixtures or mocks for user data
3. **Weak Assertions** (Medium Severity)
- `assert result["token"]` just checks truthy (could be any string)
- **Fix**: `assert len(result["token"]) > 20` or use regex
4. **Missing Naming Context** (Low Severity)
- Test name doesn't describe what's being tested
- **Fix**: `test_successful_login_returns_valid_token_and_user_data`
### Recommended Refactoring
@pytest.fixture def admin_user(): return {"username": "admin", "password": "password123"}
def test_successful_login_returns_success_status(admin_user): """Test that valid credentials return success status.""" result = login(admin_user["username"], admin_user["password"]) assert result["status"] == "success"
def test_successful_login_returns_valid_jwt_token(admin_user): """Test that successful login returns a JWT token.""" result = login(admin_user["username"], admin_user["password"]) assert len(result["token"]) > 20 assert result["token"].count('.') == 2 # JWT format
def test_successful_login_returns_user_data(admin_user): """Test that successful login includes user information.""" result = login(admin_user["username"], admin_user["password"]) assert result["user"]["id"] > 0 assert result["user"]["role"] == "admin"
### Quality Improvements
- Isolation Score: 55 → 90 (with fixtures)
- Naming Quality: 40 → 85 (descriptive names)
- Test Focus: 30 → 95 (one assertion per test)Configuration
Environment Variables
# Set preferred testing framework
export TDD_DEFAULT_FRAMEWORK="jest"
# Set coverage threshold
export TDD_COVERAGE_THRESHOLD=80
# Set output verbosity
export TDD_VERBOSE=true
# Set output format
export TDD_OUTPUT_FORMAT="markdown" # or "json", "terminal"Skill Configuration (Optional)
Create .tdd-guide.json in project root:
{
"framework": "jest",
"language": "typescript",
"coverage_threshold": 80,
"test_directory": "tests/",
"quality_rules": {
"max_assertions_per_test": 3,
"require_descriptive_names": true,
"enforce_isolation": true
},
"output": {
"format": "markdown",
"verbose": false,
"max_recommendations": 10
}
}Supported Frameworks
JavaScript/TypeScript
- Jest 29+ (recommended for React, Node.js)
- Vitest 0.34+ (recommended for Vite projects)
- Mocha 10+ with Chai
- Jasmine 4+
Python
- Pytest 7+ (recommended)
- unittest (Python standard library)
- nose2 0.12+
Java
- JUnit 5 5.9+ (recommended)
- TestNG 7+
- Mockito 5+ (mocking support)
Coverage Tools
- Istanbul/nyc (JavaScript)
- c8 (JavaScript, V8 native)
- coverage.py (Python)
- pytest-cov (Python)
- JaCoCo (Java)
- Cobertura (multi-language)
Output Formats
Markdown (Claude Desktop)
- Rich formatting with headers, tables, code blocks
- Visual indicators (✅, ⚠️, ❌)
- Progressive disclosure (summary first, details on demand)
- Syntax highlighting for code examples
Terminal (Claude Code CLI)
- Concise, text-based output
- Clear section separators
- Minimal formatting for readability
- Quick scanning for key information
JSON (API/CI Integration)
- Structured data for automated processing
- Machine-readable metrics
- Suitable for CI/CD pipelines
- Easy integration with other tools
Best Practices
Test Generation
1. Start with requirements - Clear specs lead to better tests 2. Cover the happy path first - Then add error and edge cases 3. One behavior per test - Focused tests are easier to maintain 4. Use descriptive names - Tests are documentation
Coverage Analysis
1. Aim for 80%+ coverage - Balance between safety and effort 2. Prioritize critical paths - Not all code needs 100% coverage 3. Branch coverage matters - Line coverage alone is insufficient 4. Track trends - Coverage should improve over time
TDD Workflow
1. Small iterations - Write one test, make it pass, refactor 2. Run tests frequently - Fast feedback loop is essential 3. Commit often - Each green phase is a safe checkpoint 4. Refactor with confidence - Tests are your safety net
Test Quality
1. Isolate tests - No shared state between tests 2. Fast execution - Unit tests should be <100ms each 3. Deterministic - Same input always produces same output 4. Clear failures - Good error messages save debugging time
Troubleshooting
Common Issues
Issue: Generated tests have wrong syntax for my framework
Solution: Explicitly specify framework
Example: "Generate tests using Pytest" or "Framework: Jest"Issue: Coverage report not recognized
Solution: Verify format (LCOV, JSON, XML)
Try: Paste raw coverage data instead of file path
Check: File exists and is readableIssue: Too many recommendations, overwhelmed
Solution: Ask for prioritized output
Example: "Show only P0 (critical) recommendations"
Limit: "Top 5 recommendations only"Issue: Test quality score seems wrong
Check: Ensure complete test context (setup/teardown included)
Verify: Test file contains actual test code, not just stubs
Context: Quality depends on isolation, assertions, namingIssue: Framework detection incorrect
Solution: Specify framework explicitly
Example: "Using JUnit 5" or "Framework: Vitest"
Check: Ensure imports are present in codeFile Structure
tdd-guide/
├── SKILL.md # Skill definition (YAML + documentation)
├── README.md # This file
├── HOW_TO_USE.md # Usage examples
│
├── test_generator.py # Test generation core
├── coverage_analyzer.py # Coverage parsing and analysis
├── metrics_calculator.py # Quality metrics calculation
├── framework_adapter.py # Multi-framework support
├── tdd_workflow.py # Red-green-refactor guidance
├── fixture_generator.py # Test data and fixtures
├── format_detector.py # Automatic format detection
├── output_formatter.py # Context-aware output
│
├── sample_input_typescript.json # TypeScript example
├── sample_input_python.json # Python example
├── sample_coverage_report.lcov # LCOV coverage example
└── expected_output.json # Expected output structureContributing
We welcome contributions! To contribute:
1. Fork the repository 2. Create a feature branch (git checkout -b feature/improvement) 3. Make your changes 4. Add tests for new functionality 5. Run validation: python -m pytest tests/ 6. Commit changes (git commit -m "Add: feature description") 7. Push to branch (git push origin feature/improvement) 8. Open a Pull Request
Development Setup
# Clone repository
git clone https://github.com/your-org/tdd-guide-skill.git
cd tdd-guide-skill
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests
pytest tests/ -v
# Run linter
pylint *.py
# Run type checker
mypy *.pyVersion History
v1.0.0 (November 5, 2025)
- Initial release
- Support for TypeScript, JavaScript, Python, Java
- Jest, Pytest, JUnit, Vitest framework adapters
- LCOV, JSON, XML coverage parsing
- TDD workflow guidance (red-green-refactor)
- Test quality metrics and analysis
- Context-aware output formatting
- Comprehensive documentation
License
MIT License - See LICENSE file for details
Support
- Documentation: See HOW_TO_USE.md for detailed examples
- Issues: Report bugs via GitHub issues
- Questions: Ask in Claude Code community forum
- Updates: Check repository for latest version
Acknowledgments
Built with Claude Skills Factory toolkit, following Test Driven Development best practices and informed by:
- Kent Beck's "Test Driven Development: By Example"
- Martin Fowler's refactoring catalog
- xUnit Test Patterns by Gerard Meszaros
- Growing Object-Oriented Software, Guided by Tests
---
Ready to improve your testing workflow? Install the TDD Guide skill and start generating high-quality tests today!
CI/CD Integration Guide
Integrating test coverage and quality gates into CI pipelines.
---
Table of Contents
---
Coverage in CI
Coverage Report Flow
1. Run tests with coverage enabled 2. Generate report in machine-readable format (LCOV, JSON, XML) 3. Parse report for threshold validation 4. Upload to coverage service (Codecov, Coveralls) 5. Fail build if below threshold
Report Formats by Tool
| Tool | Command | Output Format |
|---|---|---|
| Jest | jest --coverage --coverageReporters=lcov | LCOV |
| Pytest | pytest --cov-report=xml | Cobertura XML |
| JUnit/JaCoCo | mvn jacoco:report | JaCoCo XML |
| Vitest | vitest --coverage | LCOV/JSON |
---
GitHub Actions Examples
Node.js (Jest)
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test -- --coverage
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
- uses: codecov/codecov-action@v4
with:
file: coverage/lcov.infoPython (Pytest)
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install pytest pytest-cov
- run: pytest --cov=src --cov-report=xml --cov-fail-under=80
- uses: codecov/codecov-action@v4
with:
file: coverage.xmlJava (Maven + JaCoCo)
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- run: mvn test jacoco:check
- uses: codecov/codecov-action@v4
with:
file: target/site/jacoco/jacoco.xml---
Quality Gates
Threshold Configuration
Jest (package.json):
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}Pytest (pyproject.toml):
[tool.coverage.report]
fail_under = 80JaCoCo (pom.xml):
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>PR Coverage Checks
- Block merge if coverage drops
- Show coverage diff in PR comments
- Require coverage for changed files
- Allow exceptions with justification
---
Trend Tracking
Metrics to Track
| Metric | Purpose | Alert Threshold |
|---|---|---|
| Overall line coverage | Baseline health | < 80% |
| Branch coverage | Logic completeness | < 70% |
| Coverage delta | Regression detection | < -2% per PR |
| Test execution time | Performance | > 5 min |
| Flaky test count | Reliability | > 0 |
Coverage Services
| Service | Features | Integration |
|---|---|---|
| Codecov | PR comments, badges, graphs | GitHub, GitLab, Bitbucket |
| Coveralls | History, trends, badges | GitHub, GitLab |
| SonarCloud | Full code quality suite | Multiple CI platforms |
Badge Generation
<!-- README.md -->
[](https://codecov.io/gh/org/repo)Testing Framework Guide
Language and framework selection, configuration, and patterns.
---
Table of Contents
---
Framework Selection
| Language | Recommended | Alternatives | Best For |
|---|---|---|---|
| TypeScript/JS | Jest | Vitest, Mocha | React, Node.js, Next.js |
| Python | Pytest | unittest, nose2 | Django, Flask, FastAPI |
| Java | JUnit 5 | TestNG | Spring, Android |
| Vite projects | Vitest | Jest | Modern Vite-based apps |
---
TypeScript/JavaScript
Jest Configuration
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
coverageThreshold: {
global: { branches: 80, lines: 80 }
}
};Jest Test Pattern
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
it('should add two numbers', () => {
expect(calc.add(2, 3)).toBe(5);
});
it('should throw on invalid input', () => {
expect(() => calc.add(null, 3)).toThrow('Invalid input');
});
});Vitest Configuration
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: { provider: 'c8' }
}
});Coverage Tools
- Istanbul/nyc: Traditional coverage
- c8: Native V8 coverage (faster)
- Vitest built-in: Integrated with test runner
---
Python
Pytest Configuration
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = --cov=src --cov-report=term-missingPytest Test Pattern
import pytest
from calculator import Calculator
class TestCalculator:
@pytest.fixture
def calc(self):
return Calculator()
def test_add_positive_numbers(self, calc):
assert calc.add(2, 3) == 5
def test_add_raises_on_invalid_input(self, calc):
with pytest.raises(ValueError, match="Invalid input"):
calc.add(None, 3)
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
])
def test_add_various_inputs(self, calc, a, b, expected):
assert calc.add(a, b) == expectedCoverage Tools
- coverage.py: Standard Python coverage
- pytest-cov: Pytest plugin wrapper
- Report formats: HTML, XML, LCOV
---
Java
JUnit 5 Configuration (Maven)
<!-- pom.xml -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
</plugin>JUnit 5 Test Pattern
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator();
}
@Test
@DisplayName("should add two positive numbers")
void testAddPositive() {
assertEquals(5, calc.add(2, 3));
}
@Test
@DisplayName("should throw on null input")
void testAddThrowsOnNull() {
assertThrows(IllegalArgumentException.class,
() -> calc.add(null, 3));
}
@ParameterizedTest
@CsvSource({"1,2,3", "-1,1,0", "0,0,0"})
void testAddVarious(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
}Coverage Tools
- JaCoCo: Standard Java coverage
- Cobertura: Alternative XML format
- Report formats: HTML, XML, CSV
---
Version Requirements
| Tool | Minimum Version | Notes |
|---|---|---|
| Node.js | 16+ | Required for Jest 29+ |
| Jest | 29+ | Modern async support |
| Vitest | 0.34+ | Stable API |
| Python | 3.8+ | f-strings, async support |
| Pytest | 7+ | Modern fixtures |
| Java | 11+ | JUnit 5 support |
| JUnit | 5.9+ | ParameterizedTest improvements |
| TypeScript | 4.5+ | Strict mode features |
TDD Best Practices
Guidelines for effective test-driven development workflows.
---
Table of Contents
---
Red-Green-Refactor Cycle
RED Phase
1. Write a failing test before any implementation 2. Test should fail for the right reason (not compilation errors) 3. Name tests as specifications describing expected behavior 4. Keep tests small and focused on single behaviors
GREEN Phase
1. Write minimal code to make the test pass 2. Avoid over-engineering at this stage 3. Duplicate code is acceptable temporarily 4. Focus on correctness, not elegance
REFACTOR Phase
1. Improve code structure while keeping tests green 2. Remove duplication introduced in GREEN phase 3. Apply design patterns where appropriate 4. Run tests after each small refactoring
Cycle Discipline
- Complete one cycle before starting the next
- Commit after each successful GREEN phase
- Small iterations lead to better designs
- Resist temptation to write implementation first
---
Test Generation Guidelines
Behavior Focus
- Test what code does, not how it does it
- Avoid coupling tests to implementation details
- Tests should survive internal refactoring
- Focus on observable outcomes
Naming Conventions
- Use descriptive names that read as specifications
- Format:
should_<expected>_when_<condition> - Examples:
should_return_zero_when_cart_is_emptyshould_reject_negative_amountsshould_apply_discount_for_members
Test Structure
- Follow Arrange-Act-Assert (AAA) pattern
- Keep setup minimal and relevant
- One logical assertion per test
- Extract shared setup to fixtures
Coverage Scope
- Happy path: Normal expected usage
- Error cases: Invalid inputs, failures
- Edge cases: Boundaries, empty states
- Exceptional cases: Timeouts, nulls
---
Test Quality Principles
Independence
- Each test runs in isolation
- No shared mutable state between tests
- Tests can run in any order
- Parallel execution should work
Speed
- Unit tests under 100ms each
- Avoid I/O in unit tests
- Mock external dependencies
- Use in-memory databases for integration
Determinism
- Same inputs produce same results
- No dependency on system time or random values
- Controlled test data
- No flaky tests allowed
Clarity
- Failure messages explain what went wrong
- Test code is as clean as production code
- Avoid clever tricks that obscure intent
- Comments explain non-obvious setup
---
Coverage Goals
Thresholds by Type
| Type | Target | Rationale |
|---|---|---|
| Line coverage | 80%+ | Baseline for most projects |
| Branch coverage | 70%+ | More meaningful than line |
| Function coverage | 90%+ | Public APIs should be tested |
Critical Path Rules
- Authentication: 100% coverage required
- Payment processing: 100% coverage required
- Data validation: 100% coverage required
- Error handlers: Must test all paths
Avoiding Coverage Theater
- High coverage != good tests
- Focus on meaningful assertions
- Test behaviors, not lines
- Code review test quality, not just metrics
Coverage Analysis Workflow
1. Generate coverage report after test run 2. Identify uncovered critical paths (P0) 3. Review medium-priority gaps (P1) 4. Document accepted low-priority gaps (P2) 5. Set threshold gates in CI pipeline
"""
Coverage analysis module.
Parse and analyze test coverage reports in multiple formats (LCOV, JSON, XML).
Identify gaps, calculate metrics, and provide actionable recommendations.
"""
from typing import Dict, List, Any, Optional, Tuple
import json
import xml.etree.ElementTree as ET
class CoverageFormat:
"""Supported coverage report formats."""
LCOV = "lcov"
JSON = "json"
XML = "xml"
COBERTURA = "cobertura"
class CoverageAnalyzer:
"""Analyze test coverage reports and identify gaps."""
def __init__(self):
"""Initialize coverage analyzer."""
self.coverage_data = {}
self.gaps = []
self.summary = {}
def parse_coverage_report(
self,
report_content: str,
format_type: str
) -> Dict[str, Any]:
"""
Parse coverage report in various formats.
Args:
report_content: Raw coverage report content
format_type: Format (lcov, json, xml, cobertura)
Returns:
Parsed coverage data
"""
if format_type == CoverageFormat.LCOV:
return self._parse_lcov(report_content)
elif format_type == CoverageFormat.JSON:
return self._parse_json(report_content)
elif format_type in [CoverageFormat.XML, CoverageFormat.COBERTURA]:
return self._parse_xml(report_content)
else:
raise ValueError(f"Unsupported format: {format_type}")
def _parse_lcov(self, content: str) -> Dict[str, Any]:
"""Parse LCOV format coverage report."""
files = {}
current_file = None
file_data = {}
for line in content.split('\n'):
line = line.strip()
if line.startswith('SF:'):
# Source file
current_file = line[3:]
file_data = {
'lines': {},
'functions': {},
'branches': {}
}
elif line.startswith('DA:'):
# Line coverage data (line_number,hit_count)
parts = line[3:].split(',')
line_num = int(parts[0])
hit_count = int(parts[1])
file_data['lines'][line_num] = hit_count
elif line.startswith('FNDA:'):
# Function coverage (hit_count,function_name)
parts = line[5:].split(',', 1)
hit_count = int(parts[0])
func_name = parts[1] if len(parts) > 1 else 'unknown'
file_data['functions'][func_name] = hit_count
elif line.startswith('BRDA:'):
# Branch coverage (line,block,branch,hit_count)
parts = line[5:].split(',')
branch_id = f"{parts[0]}:{parts[1]}:{parts[2]}"
hit_count = 0 if parts[3] == '-' else int(parts[3])
file_data['branches'][branch_id] = hit_count
elif line == 'end_of_record':
if current_file:
files[current_file] = file_data
current_file = None
file_data = {}
self.coverage_data = files
return files
def _parse_json(self, content: str) -> Dict[str, Any]:
"""Parse JSON format coverage report (Istanbul/nyc)."""
try:
data = json.loads(content)
files = {}
for file_path, file_data in data.items():
lines = {}
functions = {}
branches = {}
# Line coverage
if 's' in file_data: # Statement map
statement_map = file_data['s']
for stmt_id, hit_count in statement_map.items():
# Map statement to line number
if 'statementMap' in file_data:
stmt_info = file_data['statementMap'].get(stmt_id, {})
line_num = stmt_info.get('start', {}).get('line')
if line_num:
lines[line_num] = hit_count
# Function coverage
if 'f' in file_data:
func_map = file_data['f']
func_names = file_data.get('fnMap', {})
for func_id, hit_count in func_map.items():
func_info = func_names.get(func_id, {})
func_name = func_info.get('name', f'func_{func_id}')
functions[func_name] = hit_count
# Branch coverage
if 'b' in file_data:
branch_map = file_data['b']
for branch_id, locations in branch_map.items():
for idx, hit_count in enumerate(locations):
branch_key = f"{branch_id}:{idx}"
branches[branch_key] = hit_count
files[file_path] = {
'lines': lines,
'functions': functions,
'branches': branches
}
self.coverage_data = files
return files
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON coverage report: {e}")
def _parse_xml(self, content: str) -> Dict[str, Any]:
"""Parse XML/Cobertura format coverage report."""
try:
root = ET.fromstring(content)
files = {}
# Handle Cobertura format
for package in root.findall('.//package'):
for cls in package.findall('classes/class'):
filename = cls.get('filename', cls.get('name', 'unknown'))
lines = {}
branches = {}
for line in cls.findall('lines/line'):
line_num = int(line.get('number', 0))
hit_count = int(line.get('hits', 0))
lines[line_num] = hit_count
# Branch info
branch = line.get('branch', 'false')
if branch == 'true':
condition_coverage = line.get('condition-coverage', '0% (0/0)')
# Parse "(covered/total)"
if '(' in condition_coverage:
branch_info = condition_coverage.split('(')[1].split(')')[0]
covered, total = map(int, branch_info.split('/'))
branches[f"{line_num}:branch"] = covered
files[filename] = {
'lines': lines,
'functions': {},
'branches': branches
}
self.coverage_data = files
return files
except ET.ParseError as e:
raise ValueError(f"Invalid XML coverage report: {e}")
def calculate_summary(self) -> Dict[str, Any]:
"""
Calculate overall coverage summary.
Returns:
Summary with line, branch, and function coverage percentages
"""
total_lines = 0
covered_lines = 0
total_branches = 0
covered_branches = 0
total_functions = 0
covered_functions = 0
for file_path, file_data in self.coverage_data.items():
# Lines
for line_num, hit_count in file_data.get('lines', {}).items():
total_lines += 1
if hit_count > 0:
covered_lines += 1
# Branches
for branch_id, hit_count in file_data.get('branches', {}).items():
total_branches += 1
if hit_count > 0:
covered_branches += 1
# Functions
for func_name, hit_count in file_data.get('functions', {}).items():
total_functions += 1
if hit_count > 0:
covered_functions += 1
summary = {
'line_coverage': self._safe_percentage(covered_lines, total_lines),
'branch_coverage': self._safe_percentage(covered_branches, total_branches),
'function_coverage': self._safe_percentage(covered_functions, total_functions),
'total_lines': total_lines,
'covered_lines': covered_lines,
'total_branches': total_branches,
'covered_branches': covered_branches,
'total_functions': total_functions,
'covered_functions': covered_functions
}
self.summary = summary
return summary
def _safe_percentage(self, covered: int, total: int) -> float:
"""Safely calculate percentage."""
if total == 0:
return 0.0
return round((covered / total) * 100, 2)
def identify_gaps(self, threshold: float = 80.0) -> List[Dict[str, Any]]:
"""
Identify coverage gaps below threshold.
Args:
threshold: Minimum acceptable coverage percentage
Returns:
List of files with coverage gaps
"""
gaps = []
for file_path, file_data in self.coverage_data.items():
file_gaps = self._analyze_file_gaps(file_path, file_data, threshold)
if file_gaps:
gaps.append(file_gaps)
self.gaps = gaps
return gaps
def _analyze_file_gaps(
self,
file_path: str,
file_data: Dict[str, Any],
threshold: float
) -> Optional[Dict[str, Any]]:
"""Analyze coverage gaps for a single file."""
lines = file_data.get('lines', {})
branches = file_data.get('branches', {})
functions = file_data.get('functions', {})
# Calculate file coverage
total_lines = len(lines)
covered_lines = sum(1 for hit in lines.values() if hit > 0)
line_coverage = self._safe_percentage(covered_lines, total_lines)
total_branches = len(branches)
covered_branches = sum(1 for hit in branches.values() if hit > 0)
branch_coverage = self._safe_percentage(covered_branches, total_branches)
# Find uncovered lines
uncovered_lines = [line_num for line_num, hit in lines.items() if hit == 0]
uncovered_branches = [branch_id for branch_id, hit in branches.items() if hit == 0]
# Only report if below threshold
if line_coverage < threshold or branch_coverage < threshold:
return {
'file': file_path,
'line_coverage': line_coverage,
'branch_coverage': branch_coverage,
'uncovered_lines': sorted(uncovered_lines),
'uncovered_branches': uncovered_branches,
'priority': self._calculate_priority(line_coverage, branch_coverage, threshold)
}
return None
def _calculate_priority(
self,
line_coverage: float,
branch_coverage: float,
threshold: float
) -> str:
"""Calculate priority based on coverage gap severity."""
gap = threshold - min(line_coverage, branch_coverage)
if gap >= 40:
return 'P0' # Critical - less than 40% coverage
elif gap >= 20:
return 'P1' # Important - 60-80% coverage
else:
return 'P2' # Nice to have - 80%+ coverage
def get_file_coverage(self, file_path: str) -> Dict[str, Any]:
"""
Get detailed coverage information for a specific file.
Args:
file_path: Path to file
Returns:
Detailed coverage data for file
"""
if file_path not in self.coverage_data:
return {}
file_data = self.coverage_data[file_path]
lines = file_data.get('lines', {})
branches = file_data.get('branches', {})
functions = file_data.get('functions', {})
total_lines = len(lines)
covered_lines = sum(1 for hit in lines.values() if hit > 0)
total_branches = len(branches)
covered_branches = sum(1 for hit in branches.values() if hit > 0)
total_functions = len(functions)
covered_functions = sum(1 for hit in functions.values() if hit > 0)
return {
'file': file_path,
'line_coverage': self._safe_percentage(covered_lines, total_lines),
'branch_coverage': self._safe_percentage(covered_branches, total_branches),
'function_coverage': self._safe_percentage(covered_functions, total_functions),
'lines': lines,
'branches': branches,
'functions': functions
}
def generate_recommendations(self) -> List[Dict[str, Any]]:
"""
Generate prioritized recommendations for improving coverage.
Returns:
List of recommendations with priority and actions
"""
recommendations = []
# Check overall coverage
summary = self.summary or self.calculate_summary()
if summary['line_coverage'] < 80:
recommendations.append({
'priority': 'P0',
'type': 'overall_coverage',
'message': f"Overall line coverage ({summary['line_coverage']}%) is below 80% threshold",
'action': 'Focus on adding tests for critical paths and business logic',
'impact': 'high'
})
if summary['branch_coverage'] < 70:
recommendations.append({
'priority': 'P0',
'type': 'branch_coverage',
'message': f"Branch coverage ({summary['branch_coverage']}%) is below 70% threshold",
'action': 'Add tests for conditional logic and error handling paths',
'impact': 'high'
})
# File-specific recommendations
for gap in self.gaps:
if gap['priority'] == 'P0':
recommendations.append({
'priority': 'P0',
'type': 'file_coverage',
'file': gap['file'],
'message': f"Critical coverage gap in {gap['file']}",
'action': f"Add tests for lines: {gap['uncovered_lines'][:10]}",
'impact': 'high'
})
# Sort by priority
priority_order = {'P0': 0, 'P1': 1, 'P2': 2}
recommendations.sort(key=lambda x: priority_order.get(x['priority'], 3))
return recommendations
def detect_format(self, content: str) -> str:
"""
Automatically detect coverage report format.
Args:
content: Raw coverage report content
Returns:
Detected format (lcov, json, xml)
"""
content_stripped = content.strip()
# Check for LCOV format
if content_stripped.startswith('TN:') or 'SF:' in content_stripped[:100]:
return CoverageFormat.LCOV
# Check for JSON format
if content_stripped.startswith('{') or content_stripped.startswith('['):
try:
json.loads(content_stripped)
return CoverageFormat.JSON
except:
pass
# Check for XML format
if content_stripped.startswith('<?xml') or content_stripped.startswith('<coverage'):
return CoverageFormat.XML
raise ValueError("Unable to detect coverage report format")
"""
Fixture and test data generation module.
Generates realistic test data, mock objects, and fixtures for various scenarios.
"""
from typing import Dict, List, Any, Optional
import json
import random
class FixtureGenerator:
"""Generate test fixtures and mock data."""
def __init__(self, seed: Optional[int] = None):
"""
Initialize fixture generator.
Args:
seed: Random seed for reproducible fixtures
"""
if seed is not None:
random.seed(seed)
def generate_boundary_values(
self,
data_type: str,
constraints: Optional[Dict[str, Any]] = None
) -> List[Any]:
"""
Generate boundary values for testing.
Args:
data_type: Type of data (int, string, array, date, etc.)
constraints: Constraints like min, max, length
Returns:
List of boundary values
"""
constraints = constraints or {}
if data_type == "int":
return self._integer_boundaries(constraints)
elif data_type == "string":
return self._string_boundaries(constraints)
elif data_type == "array":
return self._array_boundaries(constraints)
elif data_type == "date":
return self._date_boundaries(constraints)
elif data_type == "email":
return self._email_boundaries()
elif data_type == "url":
return self._url_boundaries()
else:
return []
def _integer_boundaries(self, constraints: Dict[str, Any]) -> List[int]:
"""Generate integer boundary values."""
min_val = constraints.get('min', 0)
max_val = constraints.get('max', 100)
boundaries = [
min_val, # Minimum
min_val + 1, # Just above minimum
max_val - 1, # Just below maximum
max_val, # Maximum
]
# Add special values
if min_val <= 0 <= max_val:
boundaries.append(0) # Zero
if min_val < 0:
boundaries.append(-1) # Negative
return sorted(set(boundaries))
def _string_boundaries(self, constraints: Dict[str, Any]) -> List[str]:
"""Generate string boundary values."""
min_len = constraints.get('min_length', 0)
max_len = constraints.get('max_length', 100)
boundaries = [
"", # Empty string
"a" * min_len, # Minimum length
"a" * (min_len + 1) if min_len < max_len else "", # Just above minimum
"a" * (max_len - 1) if max_len > 1 else "a", # Just below maximum
"a" * max_len, # Maximum length
"a" * (max_len + 1), # Exceeds maximum (invalid)
]
# Add special characters
if max_len >= 10:
boundaries.append("test@#$%^&*()") # Special characters
boundaries.append("unicode: 你好") # Unicode
return [b for b in boundaries if b is not None]
def _array_boundaries(self, constraints: Dict[str, Any]) -> List[List[Any]]:
"""Generate array boundary values."""
min_size = constraints.get('min_size', 0)
max_size = constraints.get('max_size', 10)
boundaries = [
[], # Empty array
[1] * min_size, # Minimum size
[1] * max_size, # Maximum size
[1] * (max_size + 1), # Exceeds maximum (invalid)
]
return boundaries
def _date_boundaries(self, constraints: Dict[str, Any]) -> List[str]:
"""Generate date boundary values."""
return [
"1900-01-01", # Very old date
"1970-01-01", # Unix epoch
"2000-01-01", # Y2K
"2025-11-05", # Today (example)
"2099-12-31", # Far future
"invalid-date", # Invalid format
]
def _email_boundaries(self) -> List[str]:
"""Generate email boundary values."""
return [
"valid@example.com", # Valid
"user.name+tag@example.co.uk", # Valid with special chars
"invalid", # Missing @
"@example.com", # Missing local part
"user@", # Missing domain
"user@.com", # Invalid domain
"", # Empty
]
def _url_boundaries(self) -> List[str]:
"""Generate URL boundary values."""
return [
"https://example.com", # Valid HTTPS
"http://example.com", # Valid HTTP
"ftp://example.com", # Different protocol
"//example.com", # Protocol-relative
"example.com", # Missing protocol
"", # Empty
"not a url", # Invalid
]
def generate_edge_cases(
self,
scenario: str,
context: Optional[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
"""
Generate edge case test scenarios.
Args:
scenario: Type of scenario (auth, payment, form, api, etc.)
context: Additional context for scenario
Returns:
List of edge case test scenarios
"""
if scenario == "auth":
return self._auth_edge_cases()
elif scenario == "payment":
return self._payment_edge_cases()
elif scenario == "form":
return self._form_edge_cases(context or {})
elif scenario == "api":
return self._api_edge_cases()
elif scenario == "file_upload":
return self._file_upload_edge_cases()
else:
return []
def _auth_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate authentication edge cases."""
return [
{
'name': 'empty_credentials',
'input': {'username': '', 'password': ''},
'expected': 'validation_error'
},
{
'name': 'sql_injection_attempt',
'input': {'username': "admin' OR '1'='1", 'password': 'password'},
'expected': 'authentication_failed'
},
{
'name': 'very_long_password',
'input': {'username': 'user', 'password': 'a' * 1000},
'expected': 'validation_error_or_success'
},
{
'name': 'special_chars_username',
'input': {'username': 'user@#$%', 'password': 'password'},
'expected': 'depends_on_validation'
},
{
'name': 'unicode_credentials',
'input': {'username': '用户', 'password': 'пароль'},
'expected': 'should_handle_unicode'
}
]
def _payment_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate payment processing edge cases."""
return [
{
'name': 'zero_amount',
'input': {'amount': 0, 'currency': 'USD'},
'expected': 'validation_error'
},
{
'name': 'negative_amount',
'input': {'amount': -10, 'currency': 'USD'},
'expected': 'validation_error'
},
{
'name': 'very_large_amount',
'input': {'amount': 999999999.99, 'currency': 'USD'},
'expected': 'should_handle_or_reject'
},
{
'name': 'precision_test',
'input': {'amount': 10.999, 'currency': 'USD'},
'expected': 'should_round_to_10.99'
},
{
'name': 'invalid_currency',
'input': {'amount': 10, 'currency': 'XXX'},
'expected': 'validation_error'
}
]
def _form_edge_cases(self, context: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate form validation edge cases."""
fields = context.get('fields', [])
edge_cases = []
for field in fields:
field_name = field.get('name', 'field')
field_type = field.get('type', 'text')
edge_cases.append({
'name': f'{field_name}_empty',
'input': {field_name: ''},
'expected': 'validation_error_if_required'
})
if field_type in ['text', 'email', 'password']:
edge_cases.append({
'name': f'{field_name}_very_long',
'input': {field_name: 'a' * 1000},
'expected': 'validation_error_or_truncate'
})
return edge_cases
def _api_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate API edge cases."""
return [
{
'name': 'missing_required_field',
'request': {'optional_field': 'value'},
'expected': 400
},
{
'name': 'invalid_json',
'request': 'not valid json{',
'expected': 400
},
{
'name': 'empty_body',
'request': {},
'expected': 400
},
{
'name': 'very_large_payload',
'request': {'data': 'x' * 1000000},
'expected': '413_or_400'
},
{
'name': 'invalid_method',
'method': 'INVALID',
'expected': 405
}
]
def _file_upload_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate file upload edge cases."""
return [
{
'name': 'empty_file',
'file': {'name': 'test.txt', 'size': 0},
'expected': 'validation_error'
},
{
'name': 'very_large_file',
'file': {'name': 'test.txt', 'size': 1000000000},
'expected': 'size_limit_error'
},
{
'name': 'invalid_extension',
'file': {'name': 'test.exe', 'size': 1000},
'expected': 'validation_error'
},
{
'name': 'no_extension',
'file': {'name': 'testfile', 'size': 1000},
'expected': 'depends_on_validation'
},
{
'name': 'special_chars_filename',
'file': {'name': 'test@#$%.txt', 'size': 1000},
'expected': 'should_sanitize'
}
]
def generate_mock_data(
self,
schema: Dict[str, Any],
count: int = 1
) -> List[Dict[str, Any]]:
"""
Generate mock data based on schema.
Args:
schema: Schema definition with field types
count: Number of mock objects to generate
Returns:
List of mock data objects
"""
mock_objects = []
for _ in range(count):
mock_obj = {}
for field_name, field_def in schema.items():
field_type = field_def.get('type', 'string')
mock_obj[field_name] = self._generate_field_value(field_type, field_def)
mock_objects.append(mock_obj)
return mock_objects
def _generate_field_value(self, field_type: str, field_def: Dict[str, Any]) -> Any:
"""Generate value for a single field."""
if field_type == "string":
options = field_def.get('options')
if options:
return random.choice(options)
return f"test_string_{random.randint(1, 1000)}"
elif field_type == "int":
min_val = field_def.get('min', 0)
max_val = field_def.get('max', 100)
return random.randint(min_val, max_val)
elif field_type == "float":
min_val = field_def.get('min', 0.0)
max_val = field_def.get('max', 100.0)
return round(random.uniform(min_val, max_val), 2)
elif field_type == "bool":
return random.choice([True, False])
elif field_type == "email":
return f"user{random.randint(1, 1000)}@example.com"
elif field_type == "date":
return f"2025-{random.randint(1, 12):02d}-{random.randint(1, 28):02d}"
elif field_type == "array":
item_type = field_def.get('items', {}).get('type', 'string')
size = random.randint(1, 5)
return [self._generate_field_value(item_type, field_def.get('items', {}))
for _ in range(size)]
else:
return None
def generate_fixture_file(
self,
fixture_name: str,
data: Any,
format: str = "json"
) -> str:
"""
Generate fixture file content.
Args:
fixture_name: Name of fixture
data: Fixture data
format: Output format (json, yaml, python)
Returns:
Fixture file content as string
"""
if format == "json":
return json.dumps(data, indent=2)
elif format == "python":
return f"""# {fixture_name} fixture
{fixture_name.upper()} = {repr(data)}
"""
elif format == "yaml":
# Simple YAML generation (for basic structures)
return self._dict_to_yaml(data)
else:
return str(data)
def _dict_to_yaml(self, data: Any, indent: int = 0) -> str:
"""Simple YAML generator."""
lines = []
indent_str = " " * indent
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, (dict, list)):
lines.append(f"{indent_str}{key}:")
lines.append(self._dict_to_yaml(value, indent + 1))
else:
lines.append(f"{indent_str}{key}: {value}")
elif isinstance(data, list):
for item in data:
if isinstance(item, dict):
lines.append(f"{indent_str}-")
lines.append(self._dict_to_yaml(item, indent + 1))
else:
lines.append(f"{indent_str}- {item}")
else:
return str(data)
return "\n".join(lines)
"""
Format detection module.
Automatically detects programming language, testing framework, and file formats.
"""
from typing import Dict, List, Any, Optional, Tuple
import re
class FormatDetector:
"""Detect language, framework, and file formats automatically."""
def __init__(self):
"""Initialize format detector."""
self.detected_language = None
self.detected_framework = None
def detect_language(self, code: str) -> str:
"""
Detect programming language from code.
Args:
code: Source code
Returns:
Detected language (typescript, javascript, python, java, unknown)
"""
# TypeScript patterns
if self._is_typescript(code):
self.detected_language = "typescript"
return "typescript"
# JavaScript patterns
if self._is_javascript(code):
self.detected_language = "javascript"
return "javascript"
# Python patterns
if self._is_python(code):
self.detected_language = "python"
return "python"
# Java patterns
if self._is_java(code):
self.detected_language = "java"
return "java"
self.detected_language = "unknown"
return "unknown"
def _is_typescript(self, code: str) -> bool:
"""Check if code is TypeScript."""
ts_patterns = [
r'\binterface\s+\w+', # interface definitions
r':\s*\w+\s*[=;]', # type annotations
r'\btype\s+\w+\s*=', # type aliases
r'<\w+>', # generic types
r'import.*from.*[\'"]', # ES6 imports with types
]
# Must have multiple TypeScript-specific patterns
matches = sum(1 for pattern in ts_patterns if re.search(pattern, code))
return matches >= 2
def _is_javascript(self, code: str) -> bool:
"""Check if code is JavaScript."""
js_patterns = [
r'\bconst\s+\w+', # const declarations
r'\blet\s+\w+', # let declarations
r'=>', # arrow functions
r'function\s+\w+', # function declarations
r'require\([\'"]', # CommonJS require
]
matches = sum(1 for pattern in js_patterns if re.search(pattern, code))
return matches >= 2
def _is_python(self, code: str) -> bool:
"""Check if code is Python."""
py_patterns = [
r'\bdef\s+\w+', # function definitions
r'\bclass\s+\w+', # class definitions
r'import\s+\w+', # import statements
r'from\s+\w+\s+import', # from imports
r'^\s*#.*$', # Python comments
r':\s*$', # Python colons
]
matches = sum(1 for pattern in py_patterns if re.search(pattern, code, re.MULTILINE))
return matches >= 3
def _is_java(self, code: str) -> bool:
"""Check if code is Java."""
java_patterns = [
r'\bpublic\s+class', # public class
r'\bprivate\s+\w+', # private members
r'\bpublic\s+\w+\s+\w+\s*\(', # public methods
r'import\s+java\.', # Java imports
r'\bvoid\s+\w+\s*\(', # void methods
]
matches = sum(1 for pattern in java_patterns if re.search(pattern, code))
return matches >= 2
def detect_test_framework(self, code: str) -> str:
"""
Detect testing framework from test code.
Args:
code: Test code
Returns:
Detected framework (jest, vitest, pytest, junit, mocha, unknown)
"""
# Jest patterns
if 'from \'@jest/globals\'' in code or '@jest/' in code:
self.detected_framework = "jest"
return "jest"
# Vitest patterns
if 'from \'vitest\'' in code or 'import { vi }' in code:
self.detected_framework = "vitest"
return "vitest"
# Pytest patterns
if 'import pytest' in code or 'def test_' in code:
self.detected_framework = "pytest"
return "pytest"
# Unittest patterns
if 'import unittest' in code and 'unittest.TestCase' in code:
self.detected_framework = "unittest"
return "unittest"
# JUnit patterns
if '@Test' in code and 'import org.junit' in code:
self.detected_framework = "junit"
return "junit"
# Mocha patterns
if 'describe(' in code and 'it(' in code:
self.detected_framework = "mocha"
return "mocha"
self.detected_framework = "unknown"
return "unknown"
def detect_coverage_format(self, content: str) -> str:
"""
Detect coverage report format.
Args:
content: Coverage report content
Returns:
Format type (lcov, json, xml, unknown)
"""
content_stripped = content.strip()
# LCOV format
if content_stripped.startswith('TN:') or 'SF:' in content_stripped[:200]:
return "lcov"
# JSON format
if content_stripped.startswith('{'):
try:
import json
json.loads(content_stripped)
return "json"
except:
pass
# XML format
if content_stripped.startswith('<?xml') or content_stripped.startswith('<coverage'):
return "xml"
return "unknown"
def detect_input_format(self, input_data: str) -> Dict[str, Any]:
"""
Detect input format and extract relevant information.
Args:
input_data: Input data (could be code, coverage report, etc.)
Returns:
Detection results with format, language, framework
"""
result = {
'format': 'unknown',
'language': 'unknown',
'framework': 'unknown',
'content_type': 'unknown'
}
# Detect if it's a coverage report
coverage_format = self.detect_coverage_format(input_data)
if coverage_format != "unknown":
result['format'] = coverage_format
result['content_type'] = 'coverage_report'
return result
# Detect if it's source code
language = self.detect_language(input_data)
if language != "unknown":
result['language'] = language
result['content_type'] = 'source_code'
# Detect if it's test code
framework = self.detect_test_framework(input_data)
if framework != "unknown":
result['framework'] = framework
result['content_type'] = 'test_code'
return result
def extract_file_info(self, file_path: str) -> Dict[str, str]:
"""
Extract information from file path.
Args:
file_path: Path to file
Returns:
File information (extension, likely language, likely purpose)
"""
import os
file_name = os.path.basename(file_path)
file_ext = os.path.splitext(file_name)[1].lower()
# Extension to language mapping
ext_to_lang = {
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.py': 'python',
'.java': 'java',
'.kt': 'kotlin',
'.go': 'go',
'.rs': 'rust',
}
# Test file patterns
is_test = any(pattern in file_name.lower()
for pattern in ['test', 'spec', '_test.', '.test.'])
return {
'file_name': file_name,
'extension': file_ext,
'language': ext_to_lang.get(file_ext, 'unknown'),
'is_test': is_test,
'purpose': 'test' if is_test else 'source'
}
def suggest_test_file_name(self, source_file: str, framework: str) -> str:
"""
Suggest test file name for source file.
Args:
source_file: Source file path
framework: Testing framework
Returns:
Suggested test file name
"""
import os
base_name = os.path.splitext(os.path.basename(source_file))[0]
ext = os.path.splitext(source_file)[1]
if framework in ['jest', 'vitest', 'mocha']:
return f"{base_name}.test{ext}"
elif framework in ['pytest', 'unittest']:
return f"test_{base_name}.py"
elif framework in ['junit', 'testng']:
return f"{base_name.capitalize()}Test.java"
else:
return f"{base_name}_test{ext}"
def identify_test_patterns(self, code: str) -> List[str]:
"""
Identify test patterns in code.
Args:
code: Test code
Returns:
List of identified patterns (AAA, Given-When-Then, etc.)
"""
patterns = []
# Arrange-Act-Assert pattern
if any(comment in code.lower() for comment in ['// arrange', '# arrange', '// act', '# act']):
patterns.append('AAA (Arrange-Act-Assert)')
# Given-When-Then pattern
if any(comment in code.lower() for comment in ['given', 'when', 'then']):
patterns.append('Given-When-Then')
# Setup/Teardown pattern
if any(keyword in code for keyword in ['beforeEach', 'afterEach', 'setUp', 'tearDown']):
patterns.append('Setup-Teardown')
# Mocking pattern
if any(keyword in code.lower() for keyword in ['mock', 'stub', 'spy']):
patterns.append('Mocking/Stubbing')
# Parameterized tests
if any(keyword in code for keyword in ['@pytest.mark.parametrize', 'test.each', '@ParameterizedTest']):
patterns.append('Parameterized Tests')
return patterns if patterns else ['No specific pattern detected']
def analyze_project_structure(self, file_paths: List[str]) -> Dict[str, Any]:
"""
Analyze project structure from file paths.
Args:
file_paths: List of file paths in project
Returns:
Project structure analysis
"""
languages = {}
test_frameworks = []
source_files = []
test_files = []
for file_path in file_paths:
file_info = self.extract_file_info(file_path)
# Count languages
lang = file_info['language']
if lang != 'unknown':
languages[lang] = languages.get(lang, 0) + 1
# Categorize files
if file_info['is_test']:
test_files.append(file_path)
else:
source_files.append(file_path)
# Determine primary language
primary_language = max(languages.items(), key=lambda x: x[1])[0] if languages else 'unknown'
return {
'primary_language': primary_language,
'languages': languages,
'source_file_count': len(source_files),
'test_file_count': len(test_files),
'test_ratio': len(test_files) / len(source_files) if source_files else 0,
'suggested_framework': self._suggest_framework(primary_language)
}
def _suggest_framework(self, language: str) -> str:
"""Suggest testing framework based on language."""
framework_map = {
'typescript': 'jest or vitest',
'javascript': 'jest or mocha',
'python': 'pytest',
'java': 'junit',
'kotlin': 'junit',
'go': 'testing package',
'rust': 'cargo test',
}
return framework_map.get(language, 'unknown')
def detect_environment(self) -> Dict[str, str]:
"""
Detect execution environment (CLI, Desktop, API).
Returns:
Environment information
"""
# This is a placeholder - actual detection would use environment variables
# or other runtime checks
return {
'environment': 'cli', # Could be 'desktop', 'api'
'output_preference': 'terminal-friendly' # Could be 'rich-markdown', 'json'
}
"""
Framework adapter module.
Provides multi-framework support with adapters for Jest, Pytest, JUnit, Vitest, and more.
Handles framework-specific patterns, imports, and test structure.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class Framework(Enum):
"""Supported testing frameworks."""
JEST = "jest"
VITEST = "vitest"
PYTEST = "pytest"
UNITTEST = "unittest"
JUNIT = "junit"
TESTNG = "testng"
MOCHA = "mocha"
JASMINE = "jasmine"
class Language(Enum):
"""Supported programming languages."""
TYPESCRIPT = "typescript"
JAVASCRIPT = "javascript"
PYTHON = "python"
JAVA = "java"
class FrameworkAdapter:
"""Adapter for multiple testing frameworks."""
def __init__(self, framework: Framework, language: Language):
"""
Initialize framework adapter.
Args:
framework: Testing framework
language: Programming language
"""
self.framework = framework
self.language = language
def generate_imports(self) -> str:
"""Generate framework-specific imports."""
if self.framework == Framework.JEST:
return self._jest_imports()
elif self.framework == Framework.VITEST:
return self._vitest_imports()
elif self.framework == Framework.PYTEST:
return self._pytest_imports()
elif self.framework == Framework.UNITTEST:
return self._unittest_imports()
elif self.framework == Framework.JUNIT:
return self._junit_imports()
elif self.framework == Framework.TESTNG:
return self._testng_imports()
elif self.framework == Framework.MOCHA:
return self._mocha_imports()
else:
return ""
def _jest_imports(self) -> str:
"""Generate Jest imports."""
return """import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';"""
def _vitest_imports(self) -> str:
"""Generate Vitest imports."""
return """import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';"""
def _pytest_imports(self) -> str:
"""Generate Pytest imports."""
return """import pytest"""
def _unittest_imports(self) -> str:
"""Generate unittest imports."""
return """import unittest"""
def _junit_imports(self) -> str:
"""Generate JUnit imports."""
return """import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;"""
def _testng_imports(self) -> str:
"""Generate TestNG imports."""
return """import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterMethod;
import static org.testng.Assert.*;"""
def _mocha_imports(self) -> str:
"""Generate Mocha imports."""
return """import { describe, it, beforeEach, afterEach } from 'mocha';
import { expect } from 'chai';"""
def generate_test_suite_wrapper(
self,
suite_name: str,
test_content: str
) -> str:
"""
Wrap test content in framework-specific suite structure.
Args:
suite_name: Name of test suite
test_content: Test functions/methods
Returns:
Complete test suite code
"""
if self.framework in [Framework.JEST, Framework.VITEST, Framework.MOCHA]:
return f"""describe('{suite_name}', () => {{
{self._indent(test_content, 2)}
}});"""
elif self.framework == Framework.PYTEST:
return f"""class Test{self._to_class_name(suite_name)}:
\"\"\"Test suite for {suite_name}.\"\"\"
{self._indent(test_content, 4)}"""
elif self.framework == Framework.UNITTEST:
return f"""class Test{self._to_class_name(suite_name)}(unittest.TestCase):
\"\"\"Test suite for {suite_name}.\"\"\"
{self._indent(test_content, 4)}"""
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
return f"""public class {self._to_class_name(suite_name)}Test {{
{self._indent(test_content, 4)}
}}"""
return test_content
def generate_test_function(
self,
test_name: str,
test_body: str,
description: str = ""
) -> str:
"""
Generate framework-specific test function.
Args:
test_name: Name of test
test_body: Test body code
description: Test description
Returns:
Complete test function
"""
if self.framework == Framework.JEST:
return self._jest_test(test_name, test_body, description)
elif self.framework == Framework.VITEST:
return self._vitest_test(test_name, test_body, description)
elif self.framework == Framework.PYTEST:
return self._pytest_test(test_name, test_body, description)
elif self.framework == Framework.UNITTEST:
return self._unittest_test(test_name, test_body, description)
elif self.framework == Framework.JUNIT:
return self._junit_test(test_name, test_body, description)
elif self.framework == Framework.TESTNG:
return self._testng_test(test_name, test_body, description)
elif self.framework == Framework.MOCHA:
return self._mocha_test(test_name, test_body, description)
else:
return ""
def _jest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Jest test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def _vitest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Vitest test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def _pytest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Pytest test."""
func_name = test_name.replace(' ', '_').replace('-', '_')
return f"""def test_{func_name}(self):
\"\"\"
{description or test_name}
\"\"\"
{self._indent(test_body, 4)}"""
def _unittest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate unittest test."""
func_name = self._to_camel_case(test_name)
return f"""def test_{func_name}(self):
\"\"\"
{description or test_name}
\"\"\"
{self._indent(test_body, 4)}"""
def _junit_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate JUnit test."""
method_name = self._to_camel_case(test_name)
return f"""@Test
public void test{method_name}() {{
// {description}
{self._indent(test_body, 4)}
}}"""
def _testng_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate TestNG test."""
method_name = self._to_camel_case(test_name)
return f"""@Test
public void test{method_name}() {{
// {description}
{self._indent(test_body, 4)}
}}"""
def _mocha_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Mocha test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def generate_assertion(
self,
actual: str,
expected: str,
assertion_type: str = "equals"
) -> str:
"""
Generate framework-specific assertion.
Args:
actual: Actual value expression
expected: Expected value expression
assertion_type: Type of assertion (equals, not_equals, true, false, throws)
Returns:
Assertion statement
"""
if self.framework in [Framework.JEST, Framework.VITEST]:
return self._jest_assertion(actual, expected, assertion_type)
elif self.framework in [Framework.PYTEST, Framework.UNITTEST]:
return self._python_assertion(actual, expected, assertion_type)
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
return self._java_assertion(actual, expected, assertion_type)
elif self.framework == Framework.MOCHA:
return self._chai_assertion(actual, expected, assertion_type)
else:
return f"assert {actual} == {expected}"
def _jest_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Jest assertion."""
if assertion_type == "equals":
return f"expect({actual}).toBe({expected});"
elif assertion_type == "not_equals":
return f"expect({actual}).not.toBe({expected});"
elif assertion_type == "true":
return f"expect({actual}).toBe(true);"
elif assertion_type == "false":
return f"expect({actual}).toBe(false);"
elif assertion_type == "throws":
return f"expect(() => {actual}).toThrow();"
else:
return f"expect({actual}).toBe({expected});"
def _python_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Python assertion."""
if assertion_type == "equals":
return f"assert {actual} == {expected}"
elif assertion_type == "not_equals":
return f"assert {actual} != {expected}"
elif assertion_type == "true":
return f"assert {actual} is True"
elif assertion_type == "false":
return f"assert {actual} is False"
elif assertion_type == "throws":
return f"with pytest.raises(Exception):\n {actual}"
else:
return f"assert {actual} == {expected}"
def _java_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Java assertion."""
if assertion_type == "equals":
return f"assertEquals({expected}, {actual});"
elif assertion_type == "not_equals":
return f"assertNotEquals({expected}, {actual});"
elif assertion_type == "true":
return f"assertTrue({actual});"
elif assertion_type == "false":
return f"assertFalse({actual});"
elif assertion_type == "throws":
return f"assertThrows(Exception.class, () -> {actual});"
else:
return f"assertEquals({expected}, {actual});"
def _chai_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Chai assertion."""
if assertion_type == "equals":
return f"expect({actual}).to.equal({expected});"
elif assertion_type == "not_equals":
return f"expect({actual}).to.not.equal({expected});"
elif assertion_type == "true":
return f"expect({actual}).to.be.true;"
elif assertion_type == "false":
return f"expect({actual}).to.be.false;"
elif assertion_type == "throws":
return f"expect(() => {actual}).to.throw();"
else:
return f"expect({actual}).to.equal({expected});"
def generate_setup_teardown(
self,
setup_code: str = "",
teardown_code: str = ""
) -> str:
"""Generate setup and teardown hooks."""
result = []
if self.framework in [Framework.JEST, Framework.VITEST, Framework.MOCHA]:
if setup_code:
result.append(f"""beforeEach(() => {{
{self._indent(setup_code, 2)}
}});""")
if teardown_code:
result.append(f"""afterEach(() => {{
{self._indent(teardown_code, 2)}
}});""")
elif self.framework == Framework.PYTEST:
if setup_code:
result.append(f"""@pytest.fixture(autouse=True)
def setup_method(self):
{self._indent(setup_code, 4)}
yield""")
if teardown_code:
result.append(f"""
{self._indent(teardown_code, 4)}""")
elif self.framework == Framework.UNITTEST:
if setup_code:
result.append(f"""def setUp(self):
{self._indent(setup_code, 4)}""")
if teardown_code:
result.append(f"""def tearDown(self):
{self._indent(teardown_code, 4)}""")
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
annotation = "@BeforeEach" if self.framework == Framework.JUNIT else "@BeforeMethod"
if setup_code:
result.append(f"""{annotation}
public void setUp() {{
{self._indent(setup_code, 4)}
}}""")
annotation = "@AfterEach" if self.framework == Framework.JUNIT else "@AfterMethod"
if teardown_code:
result.append(f"""{annotation}
public void tearDown() {{
{self._indent(teardown_code, 4)}
}}""")
return "\n\n".join(result)
def _indent(self, text: str, spaces: int) -> str:
"""Indent text by number of spaces."""
indent = " " * spaces
lines = text.split('\n')
return '\n'.join(indent + line if line.strip() else line for line in lines)
def _to_camel_case(self, text: str) -> str:
"""Convert text to camelCase."""
words = text.replace('-', ' ').replace('_', ' ').split()
if not words:
return text
return words[0].lower() + ''.join(word.capitalize() for word in words[1:])
def _to_class_name(self, text: str) -> str:
"""Convert text to ClassName."""
words = text.replace('-', ' ').replace('_', ' ').split()
return ''.join(word.capitalize() for word in words)
def detect_framework(self, code: str) -> Optional[Framework]:
"""
Auto-detect testing framework from code.
Args:
code: Test code
Returns:
Detected framework or None
"""
# Jest patterns
if 'from \'@jest/globals\'' in code or '@jest/' in code:
return Framework.JEST
# Vitest patterns
if 'from \'vitest\'' in code or 'import { vi }' in code:
return Framework.VITEST
# Pytest patterns
if 'import pytest' in code or 'def test_' in code and 'pytest.fixture' in code:
return Framework.PYTEST
# Unittest patterns
if 'import unittest' in code and 'unittest.TestCase' in code:
return Framework.UNITTEST
# JUnit patterns
if '@Test' in code and 'import org.junit' in code:
return Framework.JUNIT
# TestNG patterns
if '@Test' in code and 'import org.testng' in code:
return Framework.TESTNG
# Mocha patterns
if 'from \'mocha\'' in code or ('describe(' in code and 'from \'chai\'' in code):
return Framework.MOCHA
return None