UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

393 lines 15.4 kB
/** * TestCaseGenerator - Generate test cases from use case scenarios * * Transforms parsed use case scenarios into structured test case specifications * following TC-XXX format with test steps, expected results, and data requirements. * * @module src/testing/generators/test-case-generator * @implements @.aiwg/requirements/use-cases/UC-009-generate-test-artifacts.md * @architecture @.aiwg/architecture/software-architecture-doc.md - Section 5.3 TestGenerator * @nfr @.aiwg/requirements/nfr-modules/performance.md - NFR-TEST-01 (<10min for 100 reqs) * @tests @test/unit/testing/test-case-generator.test.ts * @depends @src/testing/generators/use-case-parser.ts * @agent @agentic/code/frameworks/sdlc-complete/agents/test-engineer.md */ // =========================== // TestCaseGenerator Class // =========================== export class TestCaseGenerator { options; testCounter = 0; constructor(options = {}) { this.options = { includeNegativeTests: true, includeBoundaryTests: true, includeErrorTests: true, testLevels: ['unit', 'integration', 'e2e'], maxTestsPerScenario: 10, generateDataRequirements: true, ...options }; } /** * Generate test suite from a use case document * * @param document - Parsed use case document * @returns Generation result with test suite */ generate(document) { const startTime = Date.now(); const warnings = []; const testCases = []; this.testCounter = 0; try { // Generate tests for main scenario const mainTests = this.generateScenarioTests(document.mainScenario, document, 'main'); testCases.push(...mainTests); // Generate tests for extensions for (const extension of document.extensions) { const extTests = this.generateScenarioTests(extension, document, 'extension'); testCases.push(...extTests); } // Generate tests for exceptions for (const exception of document.exceptions) { const excTests = this.generateScenarioTests(exception, document, 'exception'); testCases.push(...excTests); } // Generate negative tests if enabled if (this.options.includeNegativeTests) { const negativeTests = this.generateNegativeTests(document); testCases.push(...negativeTests); } // Generate boundary tests if enabled if (this.options.includeBoundaryTests) { const boundaryTests = this.generateBoundaryTests(document); testCases.push(...boundaryTests); } const generationTimeMs = Date.now() - startTime; const suite = { id: `TS-${document.id}`, name: `Test Suite: ${document.title}`, description: `Auto-generated test suite for ${document.id}`, useCaseId: document.id, testCases, coverage: { mainScenario: true, extensionsCovered: document.extensions.length, exceptionsCovered: document.exceptions.length, total: testCases.length }, generatedAt: new Date().toISOString() }; return { success: true, suite, errors: [], warnings, stats: { totalTestCases: testCases.length, positiveTests: testCases.filter(t => t.type === 'positive').length, negativeTests: testCases.filter(t => t.type === 'negative').length, boundaryTests: testCases.filter(t => t.type === 'boundary').length, errorTests: testCases.filter(t => t.type === 'error').length, generationTimeMs } }; } catch (error) { return { success: false, errors: [`Generation failed: ${error.message}`], warnings, stats: { totalTestCases: 0, positiveTests: 0, negativeTests: 0, boundaryTests: 0, errorTests: 0, generationTimeMs: Date.now() - startTime } }; } } /** * Generate test cases from multiple use case documents * * @param documents - Array of parsed use case documents * @returns Map of use case ID to generation result */ generateBatch(documents) { const results = new Map(); for (const doc of documents) { results.set(doc.id, this.generate(doc)); } return results; } // =========================== // Private Generation Methods // =========================== generateScenarioTests(scenario, document, scenarioType) { const testCases = []; // Generate one positive test for the happy path const positiveTest = this.createPositiveTest(scenario, document); testCases.push(positiveTest); // For extensions and exceptions, generate error handling tests if (scenarioType === 'exception' && this.options.includeErrorTests) { const errorTest = this.createErrorTest(scenario, document); testCases.push(errorTest); } return testCases; } createPositiveTest(scenario, document) { const testId = this.nextTestId(document.id); const level = this.determineTestLevel(scenario); const steps = scenario.steps.map((step, index) => ({ number: index + 1, action: this.actionToTestAction(step), expectedResult: this.inferExpectedResult(step, scenario.steps[index + 1]), testData: this.inferTestData(step) })); const testData = this.options.generateDataRequirements ? this.extractDataRequirements(scenario) : []; return { id: testId, name: `${scenario.name} - Happy Path`, description: `Verify ${scenario.name} completes successfully`, priority: document.priority, type: 'positive', level, preconditions: [...document.preconditions, ...scenario.preconditions], steps, postconditions: [...document.postconditions, ...scenario.postconditions], testData, traceability: { useCaseId: document.id, scenarioId: scenario.id, nfrs: document.nfrs }, tags: [document.id, scenario.type, level], estimatedDuration: this.estimateDuration(steps.length, level) }; } createErrorTest(scenario, document) { const testId = this.nextTestId(document.id); const level = this.determineTestLevel(scenario); const steps = [{ number: 1, action: `Trigger ${scenario.name} condition`, expectedResult: 'System handles error gracefully' }, { number: 2, action: 'Verify error handling', expectedResult: scenario.steps[0]?.action || 'Error is handled appropriately' }]; return { id: testId, name: `${scenario.name} - Error Handling`, description: `Verify system handles ${scenario.name} correctly`, priority: document.priority, type: 'error', level, preconditions: document.preconditions, steps, postconditions: [], testData: [], traceability: { useCaseId: document.id, scenarioId: scenario.id, nfrs: document.nfrs }, tags: [document.id, 'error', level], estimatedDuration: this.estimateDuration(steps.length, level) }; } generateNegativeTests(document) { const tests = []; const mainScenario = document.mainScenario; // Generate negative test for invalid input if (mainScenario.steps.some(s => s.actor === 'user')) { const testId = this.nextTestId(document.id); tests.push({ id: testId, name: 'Invalid Input Handling', description: 'Verify system rejects invalid input', priority: 'high', type: 'negative', level: 'unit', preconditions: document.preconditions, steps: [{ number: 1, action: 'Provide invalid input', expectedResult: 'System displays validation error' }, { number: 2, action: 'Verify system state unchanged', expectedResult: 'No data corruption or state change' }], postconditions: ['System remains in valid state'], testData: [{ name: 'invalidInput', type: 'string', constraints: ['null', 'empty', 'malformed'], examples: ['null', '""', '<script>alert(1)</script>'] }], traceability: { useCaseId: document.id, scenarioId: `${document.id}-NEG`, nfrs: document.nfrs }, tags: [document.id, 'negative', 'validation'], estimatedDuration: 1000 }); } return tests; } generateBoundaryTests(document) { const tests = []; // Generate boundary test for data limits const testId = this.nextTestId(document.id); tests.push({ id: testId, name: 'Boundary Value Testing', description: 'Verify system handles boundary values correctly', priority: 'medium', type: 'boundary', level: 'unit', preconditions: document.preconditions, steps: [{ number: 1, action: 'Test with minimum valid value', expectedResult: 'System accepts minimum value' }, { number: 2, action: 'Test with maximum valid value', expectedResult: 'System accepts maximum value' }, { number: 3, action: 'Test with value just below minimum', expectedResult: 'System rejects value' }, { number: 4, action: 'Test with value just above maximum', expectedResult: 'System rejects value' }], postconditions: [], testData: [{ name: 'boundaryValues', type: 'number', constraints: ['min', 'max', 'min-1', 'max+1'], examples: ['0', '100', '-1', '101'] }], traceability: { useCaseId: document.id, scenarioId: `${document.id}-BND`, nfrs: document.nfrs }, tags: [document.id, 'boundary', 'validation'], estimatedDuration: 2000 }); return tests; } // =========================== // Helper Methods // =========================== nextTestId(useCaseId) { this.testCounter++; return `TC-${useCaseId.replace('UC-', '')}-${String(this.testCounter).padStart(3, '0')}`; } determineTestLevel(scenario) { const stepCount = scenario.steps.length; const hasExternalActor = scenario.steps.some(s => s.actor === 'external'); if (hasExternalActor) return 'e2e'; if (stepCount > 5) return 'integration'; return 'unit'; } actionToTestAction(step) { const action = step.action; // Convert use case language to test language if (step.actor === 'user') { return action.replace(/^user\s*/i, 'User performs: '); } else if (step.actor === 'system') { return `Verify system ${action.toLowerCase()}`; } return action; } inferExpectedResult(step, nextStep) { if (step.expectedResult) { return step.expectedResult; } // Infer from action const action = step.action.toLowerCase(); if (action.includes('display') || action.includes('show')) { return 'Correct information is displayed'; } if (action.includes('save') || action.includes('store')) { return 'Data is persisted successfully'; } if (action.includes('validate') || action.includes('verify')) { return 'Validation passes'; } if (action.includes('navigate') || action.includes('redirect')) { return 'User is redirected to correct page'; } if (action.includes('send') || action.includes('notify')) { return 'Notification is sent successfully'; } // Default based on next step if (nextStep) { return `System is ready for: ${nextStep.action}`; } return 'Action completes successfully'; } inferTestData(step) { const action = step.action.toLowerCase(); if (action.includes('enter') || action.includes('input')) { return 'Valid test input'; } if (action.includes('select') || action.includes('choose')) { return 'Valid selection'; } if (action.includes('upload')) { return 'Valid test file'; } return undefined; } extractDataRequirements(scenario) { const requirements = []; for (const step of scenario.steps) { if (step.actor === 'user') { const action = step.action.toLowerCase(); if (action.includes('enter') || action.includes('input')) { requirements.push({ name: 'userInput', type: 'string', constraints: ['non-empty', 'valid format'], examples: ['test@example.com', 'Test User'] }); } if (action.includes('select') || action.includes('choose')) { requirements.push({ name: 'selection', type: 'option', constraints: ['valid option'], examples: ['option1', 'option2'] }); } } } return requirements; } estimateDuration(stepCount, level) { const baseTime = { unit: 100, integration: 500, e2e: 2000 }; return baseTime[level] * stepCount; } } //# sourceMappingURL=test-case-generator.js.map