Skip to content

Refresh the testing stack#40

Merged
GaryJones merged 6 commits into
mainfrom
GaryJones/testing-refresh
Jun 11, 2026
Merged

Refresh the testing stack#40
GaryJones merged 6 commits into
mainfrom
GaryJones/testing-refresh

Conversation

@GaryJones

@GaryJones GaryJones commented Jun 11, 2026

Copy link
Copy Markdown
Owner

This refreshes the testing toolchain while honouring one deliberate ceiling: PHPUnit stays at ^9.6 because the integration suite extends WP_UnitTestCase from the WordPress core test framework, which still targets phpunit-polyfills 1.x and PHPUnit 9.6 or earlier.

The headline find is that mutation testing has been silently useless. Brain Monkey loads Patchwork on the first Monkey\setUp() call, and Patchwork's stream wrapper permanently replaces Infection's include interceptor, so every mutant escaped — CI stayed green only because no thresholds were set. The unit bootstrap now eagerly loads the src classes while Infection's interceptor is still in place, after which all three generated mutants are killed. The measured MSI and covered MSI are both 100%, so minMsi and minCoveredMsi are gated at exactly that.

Elsewhere, the Composer scripts adopt the standard names used across plugins (cs-fix, test:unit, test:integration, plus new multisite, coverage, and Rector entries), each with a description so composer list is self-documenting. A new bin/wp-env-phpunit helper derives the wp-env mount path from the clone directory name instead of hardcoding plugin-slug. PHP syntax linting moves to php-parallel-lint, replacing a hand-rolled script whose grep pattern was a character class and could never match a parse error, and bin/xml-lint gains strict mode plus schema validation of phpunit.xml.dist. Mockery is now an explicit dependency since the base TestCase uses it directly, roave/security-advisories tracks its renamed dev-latest branch (letting minimum-stability rise to stable), and composer.json is normalised via ergebnis/composer-normalize. Two notes on that: a script named normalize would be skipped by Composer in favour of the plugin's own command, so none is defined — composer normalize works directly; and yoast/wp-test-utils was considered but skipped, as its latest release (1.2.1) pins phpunit-polyfills ^1.1.5 against this repo's ^4, so the hand-rolled TestCase stays.

The example tests gain @covers annotations (docblock style, deliberately not attributes, per the PHPUnit ceiling), making the existing beStrictAboutCoversAnnotation setting enforceable, and the bootstrap now recognises the --testsuite=integration single-token form. Finally, wp-env pins the dev environment to the supported minimums (WordPress 6.9, PHP 8.4) and the tests environment to the latest (WordPress 7.0, PHP 8.5); both refs exist as tags on the WordPress/WordPress mirror.

The CI workflow still calls composer unit, composer integration, and bin/php-lint; those references are being updated separately.

During the transition the old CI entry points keep working: unit and integration remain as deprecated aliases and bin/php-lint becomes a thin shim over parallel-lint, all removed by #35 when the workflow switches to the new names.

Cap PHPUnit at ^9.6 deliberately: the integration suite extends
WP_UnitTestCase from the WordPress core test framework, which still
targets yoast/phpunit-polyfills 1.x and PHPUnit 9.6 or earlier. Track
roave/security-advisories' renamed dev-latest branch, which also lets
minimum-stability rise to stable. Require mockery/mockery explicitly,
as the base TestCase uses MockeryPHPUnitIntegration directly but the
package only arrived transitively via Brain Monkey.

Replace the hand-rolled bin/php-lint with php-parallel-lint; its grep
pattern was a character class rather than the intended alternation, so
it could never match parse errors anyway. Rewrite bin/xml-lint with
strict mode so a failed xmllint can no longer exit 0, and validate
phpunit.xml.dist against its bundled schema too. Add a wp-env-phpunit
helper that derives the plugin mount path from the clone directory
name, instead of hardcoding plugin-slug, so integration tests work
whatever the repository is cloned as.

Rename the scripts to the standard names used across plugins (cs-fix,
test:unit, test:integration), add multisite, coverage, and Rector
entries, and describe each one so composer list is self-documenting.
Normalise composer.json with the newly added ergebnis/composer-normalize.
Add @Covers annotations to both FooTest classes so the existing
beStrictAboutCoversAnnotation setting actually enforces something,
and use assertSame for the string comparison so the type is checked
as well as the value. Correct the base TestCase @SInCE tags to match
the plugin's actual 0.1.0 version.

Explain in phpunit.xml.dist why it stays on the 9.6 schema (the
WordPress core test framework still targets PHPUnit 9.6 or earlier
via phpunit-polyfills 1.x), drop the dead logging block, and write a
Clover report alongside the text coverage output for tooling to
consume.

Teach the bootstrap to recognise the --testsuite=integration
single-token form as well as the two-token form, and tolerate a
missing argv, documenting the contract in the header.
Every mutant escaped, so mutation testing has been silently useless:
Brain Monkey loads Patchwork on the first Monkey\setUp() call, and
Patchwork's stream wrapper permanently replaces Infection's include
interceptor, meaning classes autoloaded during a test received the
original code rather than the mutant. CI never noticed because no MSI
thresholds were set.

Eagerly load the src classes in the unit bootstrap, which runs after
Infection enables its interceptor but before Patchwork loads, so the
mutated code is already in memory when tests execute. With the example
tests now killing all three generated mutants, gate minMsi and
minCoveredMsi at the measured 100%, and add the schema reference so
editors can validate the config.
Pin the development environment to the supported minimums (WordPress
6.9 on PHP 8.4) and the tests environment to the latest (WordPress 7.0
on PHP 8.5), so day-to-day work happens against what the plugin
guarantees while tests give early warning of compatibility issues.
Both core refs exist as tags on the WordPress/WordPress mirror.
The Rector php84 set arriving in the code-standards branch flags the
wrapped ( new Testee() ) form, so move to the native syntax now and
keep the combined rector dry-run clean.
The pre-refresh workflow calls composer unit, composer integration and
bin/php-lint directly, so renaming the scripts would leave this branch
red until the CI refresh lands. Deprecated aliases and a thin php-lint
shim keep every merge step green; the CI refresh removes them when it
switches the workflow to the new names.
@GaryJones GaryJones merged commit 2073d00 into main Jun 11, 2026
3 checks passed
@GaryJones GaryJones deleted the GaryJones/testing-refresh branch June 11, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant