You've written unit tests, mocks, and a functional test — but a test that only lives in your head as "something I should run before deploying" isn't worth much. This lesson closes the loop: how to actually execute your test suite reliably, from the command line, in a way that fits into a real development and deployment workflow.
What you'll learn in this lesson
- Why Drupal 10+ dropped the old browser-based Testing UI entirely
- How to run tests directly with PHPUnit's own command-line runner
- How to scope a run to one group, one class, or one specific method
- How Drush fits into (and doesn't replace) this workflow
There's no more browser-based Testing UI
If you've used Drupal 7, you might remember Administration → Configuration → Development → Testing — a page where you could tick checkboxes next to test classes and click "Run tests" in the browser. That UI is gone as of Drupal 9. Running tests is now exclusively a command-line operation, using PHPUnit's own runner directly:
vendor/bin/phpunit --group phpunit_example
This runs every test class tagged with @group phpunit_example in its docblock — which is exactly why every test class you've seen in this section carries that annotation. Groups are how you scope a run to "everything relevant to this module" without listing every file path by hand.
Scoping a run more narrowly
# Just one test class
vendor/bin/phpunit --filter AddClassTest
# Just one method within a class
vendor/bin/phpunit --filter testAddWithDataProvider
# A whole directory of tests
vendor/bin/phpunit web/modules/contrib/examples/modules/phpunit_example/tests
--filter accepts a regular expression matched against test class or method names, making it the fastest way to re-run just the one test you're actively debugging rather than the entire suite.
Where Drush fits in
Drush itself doesn't run PHPUnit tests directly — there's no drush test:run equivalent to the old browser UI. What Drush does give you is the surrounding workflow: drush cr before a test run to make sure your code changes are picked up, drush sql:drop or drush si if a test database got left in a bad state, and — on many hosting setups — running PHPUnit itself through ddev exec or an equivalent wrapper so it executes inside the same PHP environment and database credentials Drupal itself uses:
ddev exec vendor/bin/phpunit --group phpunit_example
SIMPLETEST_BASE_URL set to a real, reachable URL for the test site (kernel/functional tests actually boot a mini Drupal environment during the run). Pure unit tests like the ones in the previous two lessons don't need it — they never touch Drupal's bootstrap at all — which is one more reason unit tests are the fastest tier to iterate on.See it for yourself
On your own DDEV/local environment, run the full group: ddev exec vendor/bin/phpunit --group phpunit_example. Read the summary line at the end carefully — PHPUnit reports total tests, assertions, and time taken, and a clean run ends with OK plus a count, e.g. OK (5 tests, 8 assertions).
Quick check: your CI pipeline runs `vendor/bin/phpunit --group my_module` on every pull request and it's taking 8 minutes, mostly from functional tests. What's the fastest lever to pull without deleting any tests? Split the run: execute unit tests (fast, no bootstrap) in one CI stage that fails fast, and functional/kernel tests (slow, full bootstrap) in a separate parallel or later stage — so a broken unit test gets caught in seconds instead of waiting behind 8 minutes of functional test setup.
Key takeaways
- Drupal 9+ has no browser-based Testing UI — running tests is a command-line-only operation via PHPUnit's own runner.
--groupscopes a run to every test class tagged with a matching@groupannotation;--filterscopes to a specific class or method name.- Drush doesn't run PHPUnit tests itself, but is commonly used alongside it for cache rebuilds and database resets between runs.
- Functional and kernel tests need
SIMPLETEST_BASE_URL; pure unit tests don't, since they never bootstrap Drupal. - Separate fast (unit) and slow (functional/kernel) tests into different CI stages so a broken unit test fails fast.
Coming up next
You've now covered the full testing toolkit — unit tests, mocking, functional tests, and how to actually run them. Next, we shift focus to making your module usable by a global audience: adding translation support so your strings can be rendered in any language Drupal supports.