passed++; echo " \e[32m✓\e[0m {$message}\n"; } else { $this->failed++; $this->failures[] = "[{$this->testName}] {$message}"; echo " \e[31m✗\e[0m {$message}\n"; } } protected function assertEquals(mixed $expected, mixed $actual, string $message): void { $this->assert( $expected == $actual, "{$message} (erwartet: " . json_encode($expected) . ", erhalten: " . json_encode($actual) . ")" ); } protected function assertGreaterThan(int $min, mixed $actual, string $message): void { $this->assert($actual > $min, "{$message} (Wert: {$actual}, muss > {$min})"); } protected function assertNotEmpty(mixed $value, string $message): void { $this->assert(!empty($value), $message); } /** * Liefert Konto-ID anhand der Kontonummer oder null wenn nicht gefunden. */ protected function findAccountId(string $kontonummer): ?int { $row = \Steuerkram\Database::fetchOne( 'SELECT id FROM accounts WHERE kontonummer = ?', [$kontonummer] ); return $row ? (int)$row['id'] : null; } public function execute(): array { echo "\n\e[1m{$this->testName}\e[0m\n"; $this->setUp(); try { $this->run(); } catch (\Throwable $e) { $this->failed++; $msg = "[{$this->testName}] Ausnahme: " . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine(); $this->failures[] = $msg; echo " \e[31m✗\e[0m Unerwartete Ausnahme: " . $e->getMessage() . "\n"; } finally { $this->tearDown(); } $status = $this->failed === 0 ? "\e[32mBestanden ({$this->passed}/{$this->passed})\e[0m" : "\e[31m{$this->failed} Fehler, {$this->passed} OK\e[0m"; echo " → {$status}\n"; self::$totalPassed += $this->passed; self::$totalFailed += $this->failed; self::$allFailures = array_merge(self::$allFailures, $this->failures); return $this->failures; } public static function globalSummary(): array { return [ 'passed' => self::$totalPassed, 'failed' => self::$totalFailed, 'failures' => self::$allFailures, ]; } }