Flaky and repeated tests in Testo
SkillDev toolsStabilize flaky Testo tests with #[Retry] or stress-verify with #[Repeat]. Use when the user mentions "flaky test", "intermittent failure", "retry", "rerun", or asks to "verify a fix sticks" by running the test many times. Also use to mark a test flaky for reporting.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Flaky and repeated tests in Testo skill
What this skill tells your AI
The instructions your AI receives, as published by php-testo/testo in skills/testo-flaky-tests/SKILL.md and read by ahel’s review.
Two attributes, two different jobs. Don't mix them up.
| Attribute | Purpose |
|---|---|
#[Retry] | On failure, run again up to maxAttempts total. Pass = the run is green. By default the run is also marked flaky if a retry was needed. |
#[Repeat] | Always run times runs in total. Used to surface flakiness or stress-verify a fix. |
Both should be a last resort — first investigate the root cause (shared global state, time/timezone, ordering, randomness, network). Surface this to the user before reaching for #[Retry].
Fetch https://php-testo.github.io/llms.txt for the current attribute namespaces and parameters.
#[Retry] — make a known-flaky test green-ish
use Testo\Retry;
use Testo\Test;
#[Test]
#[Retry(maxAttempts: 3)]
public function pollsExternalService(): void
{
$response = $this->api->fetch();
Assert::same($response->status, 200);
}
Constructor (verified against plugin/retry/Retry.php):
public function __construct(
public int $maxAttempts = 3,
public bool $markFlaky = true, // ← default ON
) {}
maxAttemptsis the total number of attempts (3 = first run + up to 2 retries).markFlakyis on by default — when a retry was needed, the run is reported flaky even though it eventually passed. Do not disable this unless the user explicitly asks: silent retries are how flakiness rots a suite.- Valid targets: method, function, class (
TARGET_CLASSis allowed). Apply at the class level only when every test in it is independently flaky for the same external reason — that's rare; usually it's a smell.
#[Repeat] — run a test N times unconditionally
use Testo\Repeat;
#[Test]
#[Repeat(times: 50)]
public function concurrentInsertNeverDeadlocks(): void
{
$this->runConcurrentInsert();
}
Constructor (verified against plugin/repeat/Repeat.php):
public function __construct(
public int $times = 2, // total runs, NOT additional repetitions
public int $maxFailures = 0, // failures tolerated before the whole loop fails
public bool $markFlaky = true, // ← default ON, reports flaky if any run failed but stayed within maxFailures
) {}
timesis the total number of runs.#[Repeat(times: 3)]runs the test three times. It is not "additional repetitions on top of one run".maxFailuresdefaults to0— any single failure fails the whole loop.- Combining with
#[Retry]: Repeat runs inside Retry — each retry attempt re-runs the full repeat cycle. Possible, but the semantics are subtle; surface it to the user before suggesting both.
Use cases:
- Verifying a fix for a flaky test actually sticks (
#[Repeat(times: 100)], run locally, remove before merging). - Probabilistic / concurrency / randomness tests where one run is not enough evidence.
Don't ship #[Repeat(times: 50)] long-term on a fast suite — CI cost adds up. Remove or scale down once the fix has been validated.
Decision flow
- Is the test failing intermittently in CI?
- Yes → investigate root cause first. If genuinely external (DNS, third-party API) →
#[Retry(maxAttempts: 3)](markFlakyis already on by default). - No, but I want to verify a fix →
#[Repeat(times: N)], run locally, remove before merging.
- Yes → investigate root cause first. If genuinely external (DNS, third-party API) →
- Is the test deterministic but slow / probabilistic by nature (sampling, fuzz)?
- Use
#[Repeat], never#[Retry].
- Use
- Is the flakiness from shared state inside the suite (ordering)?
- Don't reach for either attribute. Fix isolation (lifecycle hooks, fresh fixtures).
Pitfalls
- A test with
Expect::exception(...)and#[Retry]is almost always wrong — expected exceptions are deterministic by design. - Don't use retries to paper over network calls in unit tests — replace the dependency with a fake instead.
- Throwing
SkipTest/CancelTestfrom the body short-circuits both#[Retry]and#[Repeat]— the loop stops immediately and the result keeps theSkipped/Cancelledstatus. That's intentional (skipping isn't a failure to retry against), but worth knowing when a "flaky" test is actually skipping on some runs.
Signals
- GitHub stars
- 213
- Forks
- 16
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
testo-flaky-tests- Source
- github.com/php-testo/testo