In a PHPUnit 13 phpunit.xml, what do the <testsuites> element, the bootstrap attribute and the <source> element each configure?
answer
- which tests, what runs first, which code is yours
- directory suffix defaults to Test.php
- bootstrap is included once, usually vendor/autoload.php
- no <source>, no coverage
- phpunit.xml before phpunit.dist.xml
basics
~10 s<testsuites> names sets of test directories or files; bootstrap names a PHP script, usually vendor/autoload.php, included once before tests load; <source> declares your first-party code, which coverage and issue reporting are scoped to.
solid answer
~40 s`<testsuites>` holds named `<testsuite>` elements listing `<directory>` and `<file>` entries (a directory picks up files ending in `Test.php` by default) plus `<exclude>` paths; `--testsuite unit` runs one of them. The root `bootstrap` attribute names a script PHPUnit includes once before loading tests - normally `vendor/autoload.php`, or a small file that requires it and sets up the environment; an exception there aborts the run with "Error in bootstrap script". `<source>` declares first-party code with `<include>` and `<exclude>` directories: it is the filter for code coverage (without it PHPUnit warns "No filter is configured, code coverage will not be processed") and it scopes deprecation, notice and warning reporting. PHPUnit looks for `phpunit.xml`, then `phpunit.dist.xml`, then `phpunit.xml.dist` in the working directory.
code
xml · 23 lines<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
<exclude>
<directory>src/Legacy/Generated</directory>
</exclude>
</source>
</phpunit>go deeper
Recall the three roles: <testsuites> picks the tests, bootstrap loads the autoloader first, <source> marks your own code for coverage.
Explain the Test.php suffix default, per-suite bootstrap, and why coverage is skipped with a warning when <source> is missing.
Show how you structure suites and <source> for a legacy codebase so unit runs stay fast and coverage excludes generated or vendor code.
Treat the configuration as a team contract: a committed dist file, local overrides, and suite boundaries that match how CI stages are split.
## The file and how PHPUnit finds it PHPUnit 13 reads its settings from an **XML configuration file**. Run without `-c`/`--configuration`, it looks in the current directory for `phpunit.xml`, then `phpunit.dist.xml`, then `phpunit.xml.dist`, and uses the first it finds. The usual convention is to commit a `.dist` file and let a developer override it locally with an untracked `phpunit.xml`. `vendor/bin/phpunit --generate-configuration` writes a starter file, and `--validate-configuration` checks one against the schema; a file that fails validation still runs, but PHPUnit emits a warning that results may not be as expected - a PHPUnit warning, so the run fails by default. Three parts of the file answer three questions: **which tests**, **what runs before them**, and **which code is yours**. ## `<testsuites>`: which tests A **test suite** is a named collection of test files. - `<directory>tests/Unit</directory>` collects every file under that path whose name ends in the `suffix` attribute, which defaults to `Test.php`; a `prefix` attribute exists too. - `<file>` adds a single file; `<exclude>` removes a path from the suite. - Several `<testsuite>` elements let you split fast unit tests from slow integration tests. On the command line `--testsuite unit` runs one, `--testsuite unit,integration` runs several, and `--exclude-testsuite` works the other way. The root `defaultTestSuite` attribute picks the suite used when none is named. ## The `bootstrap` attribute: what runs first `bootstrap="vendor/autoload.php"` tells PHPUnit to `include_once` that script before it loads any test class. Its job is to make classes loadable - Composer's autoloader - and optionally to prepare the process: environment variables, a default time zone, constants. - If the file is missing, PHPUnit stops with an error naming it. - If the script throws, PHPUnit aborts with "Error in bootstrap script" plus the exception and its trace. - A `<testsuite>` element can carry its own `bootstrap` attribute; that script is loaded only when the suite is selected, so integration-only setup does not slow unit runs. Keep the bootstrap small. Anything it does happens once per process and is shared by every test, so it is the wrong place for per-test fixtures. ## `<source>`: which code is yours `<source>` declares your **first-party code**: ```xml <source> <include><directory>src</directory></include> <exclude><directory>src/Legacy/Generated</directory></exclude> </source> ``` A `<directory>` here picks up files ending in `.php` by default. PHPUnit uses the declaration for two different jobs: 1. **Code coverage filter.** Only files in `<source>` appear in coverage reports. With no `<source>` at all PHPUnit emits a warning, "No filter is configured, code coverage will not be processed", and writes no report; if the paths match no files, it says so and again skips coverage. Uncovered files are listed at 0 % by default, because the coverage element's `includeUncoveredFiles` attribute defaults to true. 2. **Issue attribution.** Attributes such as `restrictNotices`, `restrictWarnings` and `ignoreIndirectDeprecations` use `<source>` to tell whether a notice or deprecation came from your code or from a dependency in `vendor/`. ## Putting it together | Element or attribute | Answers | Typical value | |---|---|---| | `<testsuites>` | Which test files run | `tests/Unit`, `tests/Integration` | | `bootstrap` | What is loaded before tests | `vendor/autoload.php` | | `<source>` | Which code is first-party | `src` | | `cacheDirectory` | Where run history and caches live | `.phpunit.cache` | ## A legacy project's first configuration When a legacy application arrives without any PHPUnit setup, the order of work matters. Start with one `unit` suite that only contains tests which need no database or network, a bootstrap that loads the autoloader and nothing else, and a `<source>` that points at the application code while excluding generated, vendored or template directories. Add the `integration` suite afterwards, with its own bootstrap for credentials and schema setup. This keeps the first CI job fast and green, and it makes every later step - stricter settings, coverage reports - a change to one well-understood file rather than a rewrite. ## Common mistakes 1. Leaving `<source>` out and wondering why `--coverage-html` produced nothing. 2. Naming test files `UserTests.php` or `user_test.php`: they do not end in `Test.php`, so the directory scan skips them silently. 3. Putting database seeding in the bootstrap, so it runs once for the whole process instead of once per test that needs it. 4. Pointing `<source>` at the project root, which drags `vendor/` and tests into coverage. 5. Keeping an old file whose coverage paths still live under the pre-`<source>` layout; `--migrate-configuration` rewrites older formats.
- In PHPUnit 13, why is a file named InvoiceTests.php under tests/Unit never run?A `<directory>` inside `<testsuite>` only collects files ending in its `suffix`, which defaults to `Test.php`. `InvoiceTests.php` ends in `Tests.php`, so the scan skips it without any message. Rename the file, or add a `<file>` entry or a `suffix` that matches the project's convention.
- With PHPUnit 13, when would you give a single <testsuite> its own bootstrap attribute?When one suite needs setup the others do not - for example integration tests that load database credentials or start a schema migration. The per-suite script is loaded only when that suite is selected, so `--testsuite unit` stays fast and free of integration dependencies. The root bootstrap still runs first.
saying these in an interview costs you the question
- Thinks the bootstrap script runs before every test method
- Believes coverage works without a <source> element
- Assumes any PHP file under the test directory is collected
- Points <source> at the project root including vendor/
- Confuses <source> with the list of test directories