-
Notifications
You must be signed in to change notification settings - Fork 1
Testing
PerformanceMeter has 100 % line, method, and class coverage in its own test suite. This page covers (1) how the package is tested upstream, and (2) how to write tests for application code that uses PerformanceMeter.
Run from a checkout of the repository:
composer install
composer test # PHPUnit
composer phpstan # static analysis (level: max)
composer cs-check # coding standards
composer qa # all threeCI runs the matrix on PHP 8.1, 8.2, 8.3, and 8.4, with both highest and lowest installable dependencies, plus separate static-analysis and coding-standards jobs.
The test file is tests/PerformanceMeterTest.php. Notable patterns:
-
setUp()callsPerformanceMeter::reset()so static state cannot bleed across cases. - Memory assertions use
assertGreaterThanOrEqual/assertStringEndsWithrather than exact-value comparisons, because per-PHP-version allocator behaviour varies. - Time assertions assert lower bounds (
>= 0.005sfor a 5 ms sleep), never upper bounds, to avoid flaky failures on slow CI runners. - A dedicated regression test (
testMemoryUsageWithNegativeMultiMegabyteDeltaUsesMBSuffix) locks the v1 → v2 bug fix in place.
PerformanceMeter is a static class holding shared mutable state. Three principles keep your tests honest:
Without this, a test that records 'boot' leaves that checkpoint visible to the next test. Combined with PHPUnit's executionOrder="random", that produces results that change between runs.
use InitPHP\PerformanceMeter\PerformanceMeter;
use PHPUnit\Framework\TestCase;
abstract class ProfiledTestCase extends TestCase
{
protected function setUp(): void
{
PerformanceMeter::reset();
}
}Extending a base class keeps the boilerplate to one line per suite.
Do not assert on exact elapsed times or memory deltas. They depend on:
- The PHP version
- The OS and machine speed
- Whether Xdebug / pcov / observability extensions are loaded
- Other processes on the box
Assert what you actually care about:
public function testWorkBlockProducesMeasurableDuration(): void
{
PerformanceMeter::setPointer('s');
$this->doWork();
PerformanceMeter::setPointer('e');
self::assertGreaterThan(0.0, PerformanceMeter::elapsedTime('s', 'e'));
}If your test asserts that an operation took at least 100ms, sleep at least 100ms inside it — but never assert that it took at most anything you care about, because CI runners run on shared infrastructure.
When your code consumes the output of memoryUsage() / peakMemoryUsage(), write a small parser at the boundary and test against the parser's output, not the raw "0.13KB" string. PerformanceMeter's formatter is fixed and well-tested upstream — testing it again in your suite is redundant and brittle.
public function testWorkBlockAllocatesLessThanFiveMegabytes(): void
{
PerformanceMeter::setPointer('s');
$this->doWork();
PerformanceMeter::setPointer('e');
$output = PerformanceMeter::memoryUsage('s', 'e');
$bytes = $this->parseMemoryString($output);
self::assertLessThan(5 * 1024 * 1024, $bytes);
}
private function parseMemoryString(string $value): float
{
if (str_ends_with($value, 'MB')) {
return (float) substr($value, 0, -2) * 1024 * 1024;
}
if (str_ends_with($value, 'KB')) {
return (float) substr($value, 0, -2) * 1024;
}
self::fail("unrecognised memory string: {$value}");
}Because the API is static, classic dependency-injection mocking does not work directly. Two pragmatic options:
If you need to assert that your code records a checkpoint, wrap PerformanceMeter behind a small interface and inject the wrapper:
interface ProfilerInterface
{
public function mark(string $name): void;
public function elapsed(string $start, ?string $end = null): float;
}
final class PerformanceMeterProfiler implements ProfilerInterface
{
public function mark(string $name): void
{
PerformanceMeter::mark($name);
}
public function elapsed(string $start, ?string $end = null): float
{
return PerformanceMeter::elapsedTime($start, $end);
}
}Production code depends on the interface; tests pass an in-memory fake.
Don't mock at all. Let your code call the real static API, then assert on PerformanceMeter::has() / PerformanceMeter::getPointers() to verify the expected checkpoints were recorded:
public function testServiceRecordsRequestBoundaries(): void
{
$this->service->handle($request);
self::assertTrue(PerformanceMeter::has('request:start'));
self::assertTrue(PerformanceMeter::has('request:end'));
}This is usually the lower-friction option for tests that simply care that the probes are present.
- Tests using
usleep()should sleep long enough to defeat scheduler noise —usleep(5_000)(5 ms) is a reasonable floor for "non-zero" assertions. -
memory_get_usage()figures include the small per-call cost of PHPUnit's own machinery; do not assert specific byte counts. - PHPUnit 10 dropped
@dataProviderannotations in favour of the#[DataProvider]attribute. The package's own suite uses the attribute form throughout — copy that pattern if you target PHPUnit 10+.
initphp/performance-meter · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Changelog · Contributing · Security Policy
Getting Started
Reference
Recipes
Migration & Help