Asserting on a private property in a test usually means reflection, four lines of it, repeated — and there is a shorter route that is occasionally the right one.
$peek = Closure::bind(
function () { return $this->attempts; },
$job,
Job::class
);
$this->assertSame(3, $peek());
// and the same idea to CALL something private, which is worse:
Closure::bind(fn() => $this->reset(), $job, Job::class)();
Reaching into a private property from a test asserts on the implementation rather than the behaviour, and every such test breaks on a refactor that changed nothing observable. The legitimate uses are narrow: a test double for a class you do not own, or an integration point in a library that genuinely has no public surface. Where it appears more than twice, the class is telling you it needs a public accessor or a different test.