Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Add the extension to your `phpunit.xml.dist` or `phpunit.xml` file:
<bootstrap class="Phauthentic\PHPUnit\ExecutionTiming\ExecutionTimeExtension">
<parameter name="topN" value="10"/>
<parameter name="showIndividualTimings" value="false"/>
<parameter name="showFQCN" value="true"/>
<parameter name="warningThreshold" value="1.0"/>
<parameter name="dangerThreshold" value="5.0"/>
</bootstrap>
Expand All @@ -46,6 +47,7 @@ Add the extension to your `phpunit.xml.dist` or `phpunit.xml` file:

- **`topN`** (default: `10`): Number of slowest tests to display in the summary report
- **`showIndividualTimings`** (default: `false`): Whether to display timing for each test as it runs
- **`showFQCN`** (default: `true`): Whether to display fully qualified class names (FQCN) or just the class name. When set to `false`, only the class name without namespace will be shown (e.g., `MyTestClass::testMethod` instead of `Phauthentic\PHPUnit\ExecutionTiming\Tests\MyTestClass::testMethod`)
- **`warningThreshold`** (default: `1.0`): Time in seconds at which tests will be colored yellow (warning). Tests with execution time >= this value will be highlighted.
- **`dangerThreshold`** (default: `5.0`): Time in seconds at which tests will be colored red (danger). Tests with execution time >= this value will be highlighted in red. Tests between `warningThreshold` and `dangerThreshold` will be colored yellow.

Expand Down Expand Up @@ -122,6 +124,22 @@ This configuration will:
- Show yellow for tests taking 0.5 seconds or more
- Show red for tests taking 2.0 seconds or more

### With Short Class Names (showFQCN disabled)

```xml
<phpunit>
<extensions>
<bootstrap class="Phauthentic\PHPUnit\ExecutionTiming\ExecutionTimeExtension">
<parameter name="topN" value="10"/>
<parameter name="showFQCN" value="false"/>
</bootstrap>
</extensions>
</phpunit>
```

This configuration will display only the class name without the full namespace path, making the output more compact:
- `MyTestClass::testMethod` instead of `Phauthentic\PHPUnit\ExecutionTiming\Tests\MyTestClass::testMethod`

## How It Works

The extension subscribes to PHPUnit events:
Expand Down
35 changes: 33 additions & 2 deletions src/ExecutionTimingExtension/ExecutionTimeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class ExecutionTimeExtension implements Extension
private float $testStartTime = 0.0;
private int $topN = 10;
private bool $showIndividualTimings = false;
private bool $showFQCN = true;
private float $warningThreshold = 1.0;
private float $dangerThreshold = 5.0;

Expand All @@ -51,14 +52,15 @@ public function onTestFinished(Finished $event): void
{
$duration = microtime(true) - $this->testStartTime;
$testName = $event->test()->id();
$displayName = $this->formatTestName($testName);
$this->testTimes[] = [
'name' => $testName,
'name' => $displayName,
'time' => $duration,
];

if ($this->showIndividualTimings) {
$timeMs = round($duration * 1000, 2);
printf(" ⏱ %s: %.2f ms\n", $testName, $timeMs);
printf(" ⏱ %s: %.2f ms\n", $displayName, $timeMs);
}
}

Expand Down Expand Up @@ -109,5 +111,34 @@ public function extractConfigurationFromParameters(ParameterCollection $paramete
if ($parameters->has('dangerThreshold')) {
$this->dangerThreshold = (float)$parameters->get('dangerThreshold');
}

if ($parameters->has('showFQCN')) {
$this->showFQCN = filter_var(
$parameters->get('showFQCN'),
FILTER_VALIDATE_BOOLEAN
);
}
}

private function formatTestName(string $testName): string
{
if ($this->showFQCN) {
return $testName;
}

// Extract class name from format: Fully\Qualified\ClassName::methodName
if (str_contains($testName, '::')) {
$parts = explode('::', $testName, 2);
$className = $parts[0];
$methodName = $parts[1] ?? '';

// Get just the class name (last part of namespace)
$classNameParts = explode('\\', $className);
$shortClassName = end($classNameParts);

return $shortClassName . ($methodName !== '' ? '::' . $methodName : '');
}

return $testName;
}
}
135 changes: 135 additions & 0 deletions tests/Unit/ExecutionTimeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,139 @@ public function testOnExecutionFinishedWithNoTests(): void

$this->assertEmpty($output);
}

public function testDefaultShowFQCNIsTrue(): void
{
$reflection = new \ReflectionClass($this->extension);
$property = $reflection->getProperty('showFQCN');
$property->setAccessible(true);

$this->assertTrue($property->getValue($this->extension));
}

public function testExtractConfigurationFromParametersWithShowFQCNTrue(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'true',
]);

$reflection = new \ReflectionClass($this->extension);
$method = $reflection->getMethod('extractConfigurationFromParameters');
$method->setAccessible(true);
$method->invoke($this->extension, $parameters);

$property = $reflection->getProperty('showFQCN');
$property->setAccessible(true);

$this->assertTrue($property->getValue($this->extension));
}

public function testExtractConfigurationFromParametersWithShowFQCNFalse(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'false',
]);

$reflection = new \ReflectionClass($this->extension);
$method = $reflection->getMethod('extractConfigurationFromParameters');
$method->setAccessible(true);
$method->invoke($this->extension, $parameters);

$property = $reflection->getProperty('showFQCN');
$property->setAccessible(true);

$this->assertFalse($property->getValue($this->extension));
}

public function testFormatTestNameWithFQCNEnabled(): void
{
$reflection = new \ReflectionClass($this->extension);
$method = $reflection->getMethod('formatTestName');
$method->setAccessible(true);

$testName = 'Phauthentic\\PHPUnit\\ExecutionTiming\\Tests\\Unit\\MyTestClass::testMethod';
$result = $method->invoke($this->extension, $testName);

$this->assertEquals('Phauthentic\\PHPUnit\\ExecutionTiming\\Tests\\Unit\\MyTestClass::testMethod', $result);
}

public function testFormatTestNameWithFQCNDisabled(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'false',
]);

$reflection = new \ReflectionClass($this->extension);
$extractMethod = $reflection->getMethod('extractConfigurationFromParameters');
$extractMethod->setAccessible(true);
$extractMethod->invoke($this->extension, $parameters);

$formatMethod = $reflection->getMethod('formatTestName');
$formatMethod->setAccessible(true);

$testName = 'Phauthentic\\PHPUnit\\ExecutionTiming\\Tests\\Unit\\MyTestClass::testMethod';
$result = $formatMethod->invoke($this->extension, $testName);

$this->assertEquals('MyTestClass::testMethod', $result);
}

public function testFormatTestNameWithFQCNDisabledAndNoNamespace(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'false',
]);

$reflection = new \ReflectionClass($this->extension);
$extractMethod = $reflection->getMethod('extractConfigurationFromParameters');
$extractMethod->setAccessible(true);
$extractMethod->invoke($this->extension, $parameters);

$formatMethod = $reflection->getMethod('formatTestName');
$formatMethod->setAccessible(true);

$testName = 'MyTestClass::testMethod';
$result = $formatMethod->invoke($this->extension, $testName);

$this->assertEquals('MyTestClass::testMethod', $result);
}

public function testFormatTestNameWithFQCNDisabledAndNoMethodName(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'false',
]);

$reflection = new \ReflectionClass($this->extension);
$extractMethod = $reflection->getMethod('extractConfigurationFromParameters');
$extractMethod->setAccessible(true);
$extractMethod->invoke($this->extension, $parameters);

$formatMethod = $reflection->getMethod('formatTestName');
$formatMethod->setAccessible(true);

$testName = 'Phauthentic\\PHPUnit\\ExecutionTiming\\Tests\\Unit\\MyTestClass';
$result = $formatMethod->invoke($this->extension, $testName);

$this->assertEquals('Phauthentic\\PHPUnit\\ExecutionTiming\\Tests\\Unit\\MyTestClass', $result);
}

public function testFormatTestNameWithFQCNDisabledAndSingleNamespaceLevel(): void
{
$parameters = ParameterCollection::fromArray([
'showFQCN' => 'false',
]);

$reflection = new \ReflectionClass($this->extension);
$extractMethod = $reflection->getMethod('extractConfigurationFromParameters');
$extractMethod->setAccessible(true);
$extractMethod->invoke($this->extension, $parameters);

$formatMethod = $reflection->getMethod('formatTestName');
$formatMethod->setAccessible(true);

$testName = 'MyNamespace\\MyTestClass::testMethod';
$result = $formatMethod->invoke($this->extension, $testName);

$this->assertEquals('MyTestClass::testMethod', $result);
}
}