en un clic
testo-data-driven
Parameterize Testo tests with
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
Parameterize Testo tests with
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Migrate an existing PHPUnit (or Pest) test suite to Testo — via the Rector bridge, via AI-agent rewriting, or a combination. Use when the user says "migrate from PHPUnit", "port phpunit tests", "convert TestCase to Testo", or has files extending PHPUnit\Framework\TestCase that need to move to Testo's attribute-based style.
Set up or edit `testo.php` — the Testo application config. Use when the user is bootstrapping a project (including running `vendor/bin/testo init`), adding/removing a suite, scoping a finder, wiring an application-wide plugin (coverage, JUnit), or asking "where do I configure Testo" / "how do I initialize Testo".
Report and raise line coverage on a Testo project — collect coverage once, aggregate the Clover report into a ranked, LLM-friendly work-list, then optionally write tests for the least-covered files with subagents. Use both for read-only coverage questions and for improving coverage. Triggers are "increase coverage", "improve test coverage", "cover untested code", "find uncovered lines", "raise the coverage percentage", "what isn't tested", "what's our test coverage", "coverage status/report", "how well tested is X", "is X covered".
Write or modify tests in a project that uses the Testo PHP testing framework. Use when adding a
Author a Testo plugin — interceptors (middleware), event listeners, container bindings, custom attributes, or full test-environment provisioning. Use when the user wants to extend how Testo runs tests (wrap every test, provision a database/service, custom reporters, attribute-driven behaviour, integrating an external system) rather than writing a single test.
Write or tune Testo performance benchmarks with
Basé sur la classification professionnelle SOC
| name | testo-data-driven |
| description | Parameterize Testo tests with |
Testo offers five parameterization attributes. Pick by the shape of the data, not by habit.
| Attribute | When to use |
|---|---|
#[DataSet] | A handful of fixed cases, written inline next to the test method. One attribute = one case. |
#[DataProvider] | A larger or computed set, produced by a static method (iterable). |
#[DataUnion] | Concatenate several providers/datasets into one logical axis. Primarily a building block for DataCross / DataZip. |
#[DataZip] | Pair N providers by index (1st with 1st, 2nd with 2nd, …). Arguments from each axis are concatenated per row. |
#[DataCross] | Cartesian product across N providers. Each provider contributes its arguments to one slice of the final argument list; combinations multiply. |
Always fetch https://php-testo.github.io/llms.txt before writing — the attribute namespaces and
constructor signatures live there and may evolve.
#[DataSet] — inline casesuse Testo\Data\DataSet;
use Testo\Test;
use Testo\Assert;
#[Test]
final class AgeValidatorTest
{
#[DataSet([12, false], 'below minimum')]
#[DataSet([18, true], 'within range')]
#[DataSet([65, false], 'above maximum')]
public function isValid(int $age, bool $expected): void
{
Assert::same(AgeValidator::isValid($age), $expected);
}
}
The second argument is the case label — surface it for every case; it is what shows up in failure output.
#[DataProvider] — method-based casesuse Testo\Data\DataProvider;
#[DataProvider('emailScenarios')]
public function validatesEmail(string $email, bool $valid): void
{
Assert::same(EmailValidator::check($email), $valid);
}
public static function emailScenarios(): iterable
{
yield 'valid' => ['test@example.com', true];
yield 'invalid' => ['not-email', false];
yield 'empty' => ['', false];
}
Rules:
public static and return iterable.yield 'label' => [...] over numeric keys — labels appear in output.#[DataProvider(Other::class, 'method')] is supported by the version in use (verify against llms-full.txt).#[DataZip] — index-aligned pairingUse when two/more datasets are already aligned positionally and you do not want their cartesian product.
#[DataZip(
new DataProvider('credentials'),
new DataProvider('expectedPermissions'),
)]
public function loginYieldsPermissions(string $user, string $pass, array $perms): void
{
Assert::same((new Auth())->login($user, $pass)->permissions(), $perms);
}
If providers have different lengths, the shorter wins — surface that constraint to the user when designing the data.
#[DataCross] — cartesian productMental model. DataCross(P1, P2, …, Pn) takes n providers. For every combination of one row from each provider, it concatenates their arguments into the final call. So:
|P1| × |P2| × … × |Pn|.arity(P1) + arity(P2) + … + arity(Pn).Each #[DataSet] attribute is one row of one argument-set. If you want several rows on the same axis, use a DataProvider, or wrap several DataSets in DataUnion.
#[DataCross(
new DataProvider('browsers'),
new DataProvider('viewports'),
)]
public function layoutRenders(string $browser, array $size): void
{
// 2 browsers × 2 viewports = 4 combinations
}
public static function browsers(): iterable
{
yield 'chrome' => ['chrome'];
yield 'firefox' => ['firefox'];
}
public static function viewports(): iterable
{
yield 'desktop' => [[1920, 1080]];
yield 'tablet' => [[768, 1024]];
}
DataUnion of DataSets per axisWhen the data is tiny and you don't want to write provider methods, wrap each axis in a DataUnion:
use Testo\Data\{DataCross, DataSet, DataUnion};
#[DataCross(
new DataUnion(
new DataSet(['chrome'], 'chrome'),
new DataSet(['firefox'], 'firefox'),
),
new DataUnion(
new DataSet([[1920, 1080]], 'desktop'),
new DataSet([[768, 1024]], 'tablet'),
),
)]
public function layoutRenders(string $browser, array $size): void
{
// 2 × 2 = 4 combinations
}
// WRONG — 4 axes × 1 row each = 1 run with 4 concatenated arguments,
// but the method takes only 2 parameters → arity mismatch.
#[DataCross(
new DataSet(['chrome'], 'chrome'),
new DataSet(['firefox'], 'firefox'),
new DataSet([[1920, 1080]], 'desktop'),
new DataSet([[768, 1024]], 'tablet'),
)]
public function layoutRenders(string $browser, array $size): void { /* broken */ }
Each argument position of DataCross is an axis, not a row.
Each provider can contribute more than one argument. They are concatenated in declaration order:
#[DataCross(
new DataProvider('numbers'), // yields [int, int]
new DataProvider('letters'), // yields [string, string]
)]
public function combined(int $a, int $b, string $c, string $d): void { }
Cardinality explodes quickly — warn the user if any axis exceeds ~5 entries.
#[DataSet].#[DataProvider].#[DataZip] (each axis = one provider, or a DataUnion of DataSets).#[DataCross] (same rule for axes).#[DataUnion] and pass the union as a single axis to DataCross / DataZip.When parameterizing numeric or sized inputs, include: 0, 1, n-1, n, n+1, null, the type's lower/upper limit.
Surface this checklist when the user asks for "parameterize this test" — don't only port the existing examples.
#[DataProvider] returning array of arrays without labels — failure output becomes unreadable.#[DataSet] = one row, not one axis. Putting several bare DataSets into DataCross / DataZip adds axes, not rows. To add rows to one axis, use DataProvider or wrap the DataSets in DataUnion.DataCross / DataZip, not the count of providers.#[DataCross] inside another #[DataCross] — flatten by listing all axes once.#[BeforeTest] hook.