Training ยท Intermediate

Agentic PHPUnit

The agent writes the tests, you are responsible for them

Your coding agent writes tests in seconds. Learn what good PHPUnit tests look like, instruct the agent properly, give it guardrails, and review its tests against the requirements so that they test the right thing and last.

  • Scope 6 sessions of 2 hours
  • Format Live via Zoom
  • Language German
  • Price per person โ‚ฌ1,199

What it is about

For a long time, writing tests was tedious. Today a coding agent does it in seconds: it reads your code and generates dozens of tests, all green, and coverage goes up. But the agent writes down what your code does, not what it is supposed to do. If your code has a bug, the tests confirm it. And because the agent checks every implementation detail, its tests break during the next refactoring, even though nothing about the behaviour has changed.

A generated test is therefore not yet a reviewed test. It has been reviewed against the requirements only once a person has judged that its expectation is right. For that, you need to know what a good PHPUnit test looks like, and you need to read it against the requirements, not against the code.

In this training you work with your own coding agent. It writes the tests, you judge them. You learn to instruct it with an AGENTS.md and the right tasks, and you give it guardrails that stop weak tests before you have to read them. Above all, you learn the difference between tests that merely pin down the current behaviour and domain tests that last.

Write little, read a lot

Every exercise runs with your own agent, on your own or in pairs. Because agents do not answer deterministically, the results differ, and that is intended: in the joint review you compare what the agents have produced, and you learn more from several test suites than from one. You write little test code yourself, but you read, judge, and correct a lot of it. That is how you learn what a PHPUnit test has to look like.

You practise on a shopping basket with discounts, shipping, and inventory. It comes with a written specification of its requirements, so that you can check tests against the requirements instead of against the code. You see the configuration of individual agents in demonstrations, because it changes quickly. The training teaches the mechanisms behind it: instruction, environment, and guardrail.

Programme

  1. Session 1: The agent writes, you check

    What is a test worth that you did not write yourself?

    • What a PHPUnit test has to look like: test class and test method, names as sentences, arrange, act, assert, assertSame(), expectException(), data providers with named data sets, createStub() and createMock()
    • Typical weaknesses of generated tests: assertions that cannot check anything, logic rebuilt in the test, assertTrue(true) as a fig leaf, mocks for everything, tests for private details
    • The check: breaking the production code on purpose, because a test that stays green is worthless, even at 100 % coverage
    • Keep, rewrite, delete: criteria for the verdict

    Result: A verdict on every test your agent wrote for Discount without any rules, checked by breaking the code on purpose, and one weak test that you rewrote yourself

  2. Session 2: Generated is not reviewed

    How does the agent know what is right?

    • Where the expectation comes from: an agent that reads the code takes the code as its oracle, and its tests pin down the current behaviour as characterisation tests, bugs included
    • What characterisation tests are good for: a short-lived safety net before a refactoring, but not a specification
    • Reviewed against the requirements means that a person has judged, based on the requirements, that the expectation is right
    • Reviewing with test names as statements about the domain, --testdox as a report that domain experts can read too, and edge cases from the requirements instead of from the code
    • Having the agent test from the requirements instead of from the code
    • Making the status visible, for example with separate test suites or groups, so that nobody mistakes an unreviewed test for a specification

    Result: The tests your agent wrote for Shipping, reviewed against the specification, the test that pinned down a hidden bug with its expectation corrected and therefore red, and a comparison with tests the agent wrote from the specification alone

  3. Session 3: Instructing the agent

    What does the agent need to know to write the right tests?

    • The agent forgets what is only in the prompt: AGENTS.md and agent-specific files such as CLAUDE.md keep it
    • What goes in: the test rules of the team, an exemplary test, the commands for running the tests, and prohibitions, for example against changing production code during a testing task, weakening existing tests, or using test doubles for value objects
    • Context budget: concise and concrete rather than complete
    • Tasks that lead to better tests: small, with the requirements instead of the code as their source, and with a test list of test cases and edge cases that you review before the agent writes the tests
    • Stopping the agent when it drifts off

    Result: An AGENTS.md with test rules for the exercise project, based on your test guideline from โ€œTests by Designโ€ or on a template, and tests for Discount that the agent wrote from a test list this time, compared with those from the first session

  4. Session 4: Setting guardrails

    What stops a bad test before you read it?

    • Three mechanisms: instruction in AGENTS.md, an environment of Composer scripts, environment variables, and approved commands, and guardrails that prevent a violation technically
    • A strict PHPUnit configuration: failing on risky tests and warnings, random order, requiring a statement of the covered code
    • PHPStan with the PHPUnit extension on the test code
    • Mutation testing with Infection on the changed code, so that tests that check nothing stand out
    • Hooks that reject changes to production code or to existing tests during a testing task
    • One entry point for all checks, and CI as the final authority

    Result: Guardrails of PHPUnit configuration, static analysis, mutation testing, and hooks under which your agent works in the exercise project, and which reject a worthless test before you have to read it

  5. Session 5: Tests that last

    Which tests survive the next refactoring?

    • Why generated tests are fragile: instead of behaviour they check private details, call order, and data structures, and they replace every dependency
    • Domain tests: through public interfaces and use cases, in the language of the domain, with stubs instead of mocks where no communication is verified, and decoupled from framework, database, and HTTP as far as possible
    • Domain-specific assertions and builders for test data
    • Short-lived and long-lived: using generated characterisation tests deliberately as a net for a refactoring, then deleting them or promoting them to domain tests

    Result: Domain tests in place of the fragile generated ones, green before and after a prepared refactoring that changes no behaviour, and red when you break the code on purpose

  6. Session 6: On your own code

    Which tests do you keep, which do you promote, which do you delete?

    • The way of working at a glance: instruct, let it write, judge, review against the requirements, decouple
    • When generated tests are enough, when a test has to be reviewed against the requirements, and who does that
    • Keep, promote, delete: rules for the generated tests already in your code base
    • Reviewing tests in a pull request: what a review of generated tests looks for

    Result: Tests your agent wrote with AGENTS.md and guardrails for the code you brought along, reviewed against the requirements and partly promoted, and a draft agreement for your team

Who it is for

  • PHP developers who let coding agents write tests or plan to
  • Teams with many generated tests of which nobody knows which ones are any good
  • Developers who want to learn PHPUnit for working with agents

Prerequisites

  • Experience with object-oriented programming in PHP
  • The concepts of testing as taught in โ€œUnderstanding Testingโ€; โ€œGetting Started with PHPUnitโ€ and โ€œTests by Designโ€ are helpful as well
  • A coding agent of your choice, such as Claude Code, Copilot Agent Mode, Cursor, or Junie, that is allowed to write files and run commands in the project; you cover the cost and usage quota of your agent yourself; if you are not allowed to use an agent at your company, you work together with another participant
  • For the exercises, PHP 8.5, Composer, Git, and an IDE, or alternatively Podman or Docker with the Containerfile provided
  • For the sixth session, a manageable piece of your own code, such as a class with domain logic

What you take away

  • You recognise good and bad PHPUnit tests, even when they are generated
  • You check tests by breaking the code instead of trusting the green output
  • You review generated tests against the requirements and find the bugs they have pinned down
  • You instruct your agent with an AGENTS.md and give it guardrails, so that it writes tests according to the rules of your team
  • You turn fragile generated tests into domain tests that survive the next refactoring
  • You define for your team how you tell generated tests from tests that have been reviewed against the requirements

Your trainer

Sebastian Bergmann answering a participant's question at the laptop
Photo: Filip Kovaฤeviฤ‡

Sebastian Bergmann is the creator of PHPUnit, the de facto standard for testing PHP code. Since 2001 he has been helping teams build maintainable and well-tested software as a consultant and trainer, since 2009 as a co-founder of thePHP.cc.

You learn PHPUnit first-hand here: from the person who wrote it.