- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
350 lines
12 KiB
PHP
350 lines
12 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* TDD Test Suite Runner for Desk-Moloni v3.0
|
|
*
|
|
* Runs tests in strict TDD order: Contract → Integration → Security → Performance → Unit → E2E
|
|
* Ensures all tests fail initially before any implementation begins.
|
|
*/
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
|
|
class TDDTestRunner
|
|
{
|
|
private array $testSuites = [
|
|
'contract' => [
|
|
'name' => 'Contract Tests',
|
|
'description' => 'API endpoint validation and database schema contracts',
|
|
'required' => true,
|
|
'order' => 1
|
|
],
|
|
'integration' => [
|
|
'name' => 'Integration Tests',
|
|
'description' => 'Synchronization workflows and external service integration',
|
|
'required' => true,
|
|
'order' => 2
|
|
],
|
|
'security' => [
|
|
'name' => 'Security Tests',
|
|
'description' => 'Encryption, authentication, and vulnerability testing',
|
|
'required' => true,
|
|
'order' => 3
|
|
],
|
|
'performance' => [
|
|
'name' => 'Performance Tests',
|
|
'description' => 'Queue processing, rate limiting, and benchmark testing',
|
|
'required' => true,
|
|
'order' => 4
|
|
],
|
|
'unit' => [
|
|
'name' => 'Unit Tests',
|
|
'description' => 'Business logic validation and isolated component testing',
|
|
'required' => true,
|
|
'order' => 5
|
|
],
|
|
'e2e' => [
|
|
'name' => 'End-to-End Tests',
|
|
'description' => 'Complete user workflow validation',
|
|
'required' => true,
|
|
'order' => 6
|
|
],
|
|
'database' => [
|
|
'name' => 'Database Tests',
|
|
'description' => 'Database schema and constraint validation',
|
|
'required' => true,
|
|
'order' => 0
|
|
]
|
|
];
|
|
|
|
private array $config;
|
|
private bool $strictTDD;
|
|
private bool $continueOnFailure;
|
|
|
|
public function __construct(array $config = [])
|
|
{
|
|
$this->config = array_merge([
|
|
'strict_tdd' => true,
|
|
'continue_on_failure' => false,
|
|
'coverage_threshold' => 100,
|
|
'performance_benchmarks' => true,
|
|
'security_audits' => true,
|
|
'real_api_testing' => true,
|
|
'parallel_execution' => false
|
|
], $config);
|
|
|
|
$this->strictTDD = $this->config['strict_tdd'];
|
|
$this->continueOnFailure = $this->config['continue_on_failure'];
|
|
}
|
|
|
|
public function runFullSuite(): array
|
|
{
|
|
echo "\n" . str_repeat("=", 80) . "\n";
|
|
echo "DESK-MOLONI v3.0 TDD TEST SUITE\n";
|
|
echo "Following Test-Driven Development Methodology\n";
|
|
echo str_repeat("=", 80) . "\n\n";
|
|
|
|
if ($this->strictTDD) {
|
|
echo "🔴 STRICT TDD MODE: All tests MUST FAIL initially\n";
|
|
echo "🟢 Implementation only begins after RED phase\n\n";
|
|
}
|
|
|
|
$results = [
|
|
'suites' => [],
|
|
'overall_success' => true,
|
|
'total_tests' => 0,
|
|
'total_failures' => 0,
|
|
'total_errors' => 0,
|
|
'total_skipped' => 0,
|
|
'execution_time' => 0,
|
|
'coverage_percentage' => 0,
|
|
'tdd_compliance' => true
|
|
];
|
|
|
|
$startTime = microtime(true);
|
|
|
|
// Sort test suites by order
|
|
uasort($this->testSuites, function($a, $b) {
|
|
return $a['order'] <=> $b['order'];
|
|
});
|
|
|
|
foreach ($this->testSuites as $suite => $config) {
|
|
echo "🧪 Running {$config['name']}...\n";
|
|
echo " {$config['description']}\n";
|
|
|
|
$suiteResult = $this->runTestSuite($suite);
|
|
$results['suites'][$suite] = $suiteResult;
|
|
|
|
// Aggregate results
|
|
$results['total_tests'] += $suiteResult['tests'];
|
|
$results['total_failures'] += $suiteResult['failures'];
|
|
$results['total_errors'] += $suiteResult['errors'];
|
|
$results['total_skipped'] += $suiteResult['skipped'];
|
|
|
|
// Check TDD compliance
|
|
if ($this->strictTDD && $suiteResult['success']) {
|
|
echo " ⚠️ WARNING: Tests should FAIL in TDD red phase!\n";
|
|
$results['tdd_compliance'] = false;
|
|
}
|
|
|
|
if (!$suiteResult['success']) {
|
|
$results['overall_success'] = false;
|
|
|
|
if (!$this->continueOnFailure && $config['required']) {
|
|
echo " ❌ Critical test suite failed. Stopping execution.\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->displaySuiteResults($suiteResult);
|
|
echo "\n";
|
|
}
|
|
|
|
$results['execution_time'] = microtime(true) - $startTime;
|
|
|
|
// Generate coverage report
|
|
if ($this->config['coverage_threshold'] > 0) {
|
|
$results['coverage_percentage'] = $this->generateCoverageReport();
|
|
}
|
|
|
|
$this->displayOverallResults($results);
|
|
|
|
return $results;
|
|
}
|
|
|
|
private function runTestSuite(string $suite): array
|
|
{
|
|
$command = sprintf(
|
|
'cd %s && vendor/bin/phpunit --testsuite %s --log-junit reports/%s-junit.xml',
|
|
escapeshellarg(dirname(__DIR__)),
|
|
escapeshellarg($suite),
|
|
escapeshellarg($suite)
|
|
);
|
|
|
|
if ($this->config['coverage_threshold'] > 0) {
|
|
$command .= sprintf(' --coverage-clover reports/%s-coverage.xml', escapeshellarg($suite));
|
|
}
|
|
|
|
$output = [];
|
|
$returnCode = 0;
|
|
|
|
exec($command . ' 2>&1', $output, $returnCode);
|
|
|
|
return $this->parseTestOutput($output, $returnCode);
|
|
}
|
|
|
|
private function parseTestOutput(array $output, int $returnCode): array
|
|
{
|
|
$result = [
|
|
'success' => $returnCode === 0,
|
|
'tests' => 0,
|
|
'failures' => 0,
|
|
'errors' => 0,
|
|
'skipped' => 0,
|
|
'time' => 0.0,
|
|
'output' => implode("\n", $output)
|
|
];
|
|
|
|
foreach ($output as $line) {
|
|
// Parse PHPUnit output
|
|
if (preg_match('/Tests: (\d+), Assertions: \d+/', $line, $matches)) {
|
|
$result['tests'] = (int)$matches[1];
|
|
}
|
|
|
|
if (preg_match('/Failures: (\d+)/', $line, $matches)) {
|
|
$result['failures'] = (int)$matches[1];
|
|
}
|
|
|
|
if (preg_match('/Errors: (\d+)/', $line, $matches)) {
|
|
$result['errors'] = (int)$matches[1];
|
|
}
|
|
|
|
if (preg_match('/Skipped: (\d+)/', $line, $matches)) {
|
|
$result['skipped'] = (int)$matches[1];
|
|
}
|
|
|
|
if (preg_match('/Time: ([0-9.]+)/', $line, $matches)) {
|
|
$result['time'] = (float)$matches[1];
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function displaySuiteResults(array $result): void
|
|
{
|
|
$status = $result['success'] ? '✅ PASSED' : '❌ FAILED';
|
|
$testsInfo = "Tests: {$result['tests']}, Failures: {$result['failures']}, Errors: {$result['errors']}";
|
|
|
|
if ($result['skipped'] > 0) {
|
|
$testsInfo .= ", Skipped: {$result['skipped']}";
|
|
}
|
|
|
|
echo " {$status} - {$testsInfo} (Time: {$result['time']}s)\n";
|
|
|
|
if (!$result['success']) {
|
|
$errorLines = array_filter(explode("\n", $result['output']), function($line) {
|
|
return strpos($line, 'FAIL') !== false || strpos($line, 'ERROR') !== false;
|
|
});
|
|
|
|
foreach (array_slice($errorLines, 0, 3) as $errorLine) {
|
|
echo " 💥 " . trim($errorLine) . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
private function generateCoverageReport(): float
|
|
{
|
|
echo "📊 Generating code coverage report...\n";
|
|
|
|
$command = sprintf(
|
|
'cd %s && vendor/bin/phpunit --testsuite unit,integration --coverage-html coverage --coverage-text',
|
|
escapeshellarg(dirname(__DIR__))
|
|
);
|
|
|
|
$output = [];
|
|
exec($command . ' 2>&1', $output);
|
|
|
|
// Parse coverage percentage from output
|
|
$coveragePercentage = 0;
|
|
foreach ($output as $line) {
|
|
if (preg_match('/Lines:\s+([0-9.]+)%/', $line, $matches)) {
|
|
$coveragePercentage = (float)$matches[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $coveragePercentage;
|
|
}
|
|
|
|
private function displayOverallResults(array $results): void
|
|
{
|
|
echo str_repeat("=", 80) . "\n";
|
|
echo "OVERALL TEST RESULTS\n";
|
|
echo str_repeat("=", 80) . "\n";
|
|
|
|
$status = $results['overall_success'] ? '✅ PASSED' : '❌ FAILED';
|
|
echo "Status: {$status}\n";
|
|
echo "Total Tests: {$results['total_tests']}\n";
|
|
echo "Failures: {$results['total_failures']}\n";
|
|
echo "Errors: {$results['total_errors']}\n";
|
|
echo "Skipped: {$results['total_skipped']}\n";
|
|
echo "Execution Time: " . number_format($results['execution_time'], 2) . "s\n";
|
|
|
|
if ($results['coverage_percentage'] > 0) {
|
|
$coverageStatus = $results['coverage_percentage'] >= $this->config['coverage_threshold'] ? '✅' : '❌';
|
|
echo "Code Coverage: {$coverageStatus} {$results['coverage_percentage']}% (Target: {$this->config['coverage_threshold']}%)\n";
|
|
}
|
|
|
|
// TDD Compliance Check
|
|
if ($this->strictTDD) {
|
|
$tddStatus = $results['tdd_compliance'] ? '❌ NOT COMPLIANT' : '✅ COMPLIANT';
|
|
echo "TDD Compliance: {$tddStatus}\n";
|
|
|
|
if (!$results['tdd_compliance']) {
|
|
echo "\n⚠️ TDD VIOLATION: Some tests passed when they should fail in RED phase\n";
|
|
echo "🔴 All tests must FAIL before implementation begins\n";
|
|
} else {
|
|
echo "\n🔴 Perfect! All tests failed as expected in TDD RED phase\n";
|
|
echo "🟢 Now proceed with implementation to make tests pass\n";
|
|
}
|
|
}
|
|
|
|
echo "\n";
|
|
$this->displayNextSteps($results);
|
|
}
|
|
|
|
private function displayNextSteps(array $results): void
|
|
{
|
|
echo "NEXT STEPS:\n";
|
|
echo str_repeat("-", 40) . "\n";
|
|
|
|
if ($this->strictTDD && $results['tdd_compliance']) {
|
|
echo "1. ✅ RED phase complete - All tests are failing as expected\n";
|
|
echo "2. 🟢 Begin GREEN phase - Implement minimal code to make tests pass\n";
|
|
echo "3. 🔵 REFACTOR phase - Improve code quality while keeping tests green\n";
|
|
echo "4. 🔄 Repeat TDD cycle for next feature\n";
|
|
} elseif (!$results['overall_success']) {
|
|
// Analyze which implementations are needed
|
|
$failedSuites = array_filter($results['suites'], function($suite) {
|
|
return !$suite['success'];
|
|
});
|
|
|
|
echo "Required implementations based on failing tests:\n";
|
|
foreach ($failedSuites as $suiteName => $suite) {
|
|
echo " • {$this->testSuites[$suiteName]['name']}\n";
|
|
}
|
|
} else {
|
|
echo "1. ✅ All tests are passing\n";
|
|
echo "2. 📊 Review code coverage report\n";
|
|
echo "3. 🔍 Consider additional edge cases\n";
|
|
echo "4. 📝 Update documentation\n";
|
|
}
|
|
|
|
echo "\nTest reports available at:\n";
|
|
echo " • HTML Coverage: " . dirname(__DIR__) . "/coverage/index.html\n";
|
|
echo " • JUnit Reports: " . dirname(__DIR__) . "/tests/reports/\n";
|
|
}
|
|
}
|
|
|
|
// Run the test suite
|
|
if (basename(__FILE__) === basename($_SERVER['SCRIPT_NAME'])) {
|
|
$config = [
|
|
'strict_tdd' => isset($_SERVER['argv']) && in_array('--strict-tdd', $_SERVER['argv']),
|
|
'continue_on_failure' => isset($_SERVER['argv']) && in_array('--continue', $_SERVER['argv']),
|
|
'coverage_threshold' => 100,
|
|
'real_api_testing' => !isset($_SERVER['argv']) || !in_array('--no-api', $_SERVER['argv'])
|
|
];
|
|
|
|
$runner = new TDDTestRunner($config);
|
|
$results = $runner->runFullSuite();
|
|
|
|
// Exit with appropriate code
|
|
exit($results['overall_success'] ? 0 : 1);
|
|
} |