Install PHP and PHPUnit on Windows/Ubuntu

Note: This post has been updated for PHP 8.5 and PHPUnit 13. The original 2013 version installed PHPUnit through PEAR (go-pear, pear channel-discover pear.phpunit.de), but PEAR has since been retired and PHPUnit no longer ships through it. The current approach is to install PHPUnit with Composer or as a standalone PHAR, both shown below.

PHPUnit is the de-facto testing framework for PHP. This guide gets PHP and PHPUnit running on Windows and Ubuntu, then writes and runs a first test to confirm the setup works.

Install PHP

PHPUnit 13 requires PHP 8.4 or newer, and PHP 8.5 is the current stable release, so install that if you can.

Windows

Download the latest thread-safe build from the PHP for Windows page, extract it to a folder whose path contains no spaces (for example C:\php), and add that folder to your Path environment variable so php is available from any command prompt.

Ubuntu

Install the CLI package with apt:

sudo apt update
sudo apt install php-cli

On either platform, confirm PHP is on your PATH:

php --version

You should see something like:

PHP 8.5.7 (cli) (built: Jun  4 2026 09:21:33) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.7, Copyright (c) Zend Technologies

If this fails, something is wrong with the PHP install or your PATH. Fix it before continuing.

Install PHPUnit

There are two common ways to install PHPUnit. Use Composer when you want PHPUnit scoped to a single project (the usual choice), or the PHAR when you want one standalone executable on the machine.

Option 1 — Composer (per project)

Composer is PHP’s dependency manager. From your project directory, add PHPUnit as a development-only dependency:

composer require --dev phpunit/phpunit ^13

This installs PHPUnit and its dependencies under vendor/. Verify it:

./vendor/bin/phpunit --version

Option 2 — PHAR (standalone)

A PHAR bundles PHPUnit and all of its dependencies into a single file, which sidesteps any version conflicts with your project’s own dependencies.

On Ubuntu, download it and make it executable:

wget -O phpunit https://phar.phpunit.de/phpunit-13.phar
chmod +x phpunit
./phpunit --version

On Windows there is no executable bit, so run the file through PHP instead:

php phpunit-13.phar --version

Either way, the output should be something like PHPUnit 13.2.1 by Sebastian Bergmann and contributors.

Write and run your first test

Installing the tool is only half the job. Let’s prove the setup works with a tiny test, using the Composer install from above.

First tell Composer where your classes live by adding an autoload entry to composer.json, then run composer dump-autoload:

{
    "autoload": {
        "classmap": ["src/"]
    },
    "require-dev": {
        "phpunit/phpunit": "^13"
    }
}

Create the class under test in src/Email.php. It is a small value object that refuses to hold an invalid address:

<?php declare(strict_types=1);

final class Email
{
    private string $email;

    private function __construct(string $email)
    {
        $this->ensureIsValidEmail($email);
        $this->email = $email;
    }

    public static function fromString(string $email): self
    {
        return new self($email);
    }

    public function asString(): string
    {
        return $this->email;
    }

    private function ensureIsValidEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                sprintf('"%s" is not a valid email address', $email)
            );
        }
    }
}

Then add the test in tests/EmailTest.php. A test class extends PHPUnit\Framework\TestCase, and every public method whose name starts with test becomes a test case:

<?php declare(strict_types=1);

use PHPUnit\Framework\TestCase;

final class EmailTest extends TestCase
{
    public function testCanBeCreatedFromValidEmail(): void
    {
        $email = Email::fromString('user@example.com');

        $this->assertSame('user@example.com', $email->asString());
    }

    public function testCannotBeCreatedFromInvalidEmail(): void
    {
        $this->expectException(InvalidArgumentException::class);

        Email::fromString('invalid');
    }
}

The first test checks the happy path with assertSame; the second uses expectException to assert that an invalid address is rejected. Run the suite:

./vendor/bin/phpunit tests
PHPUnit 13.0.0 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 70 ms, Memory: 10.00 MB

OK (2 tests, 2 assertions)

Two dots, two passing tests. PHPUnit is working, and you have a project layout to build on.

Troubleshooting

  • composer: command not found — install Composer first; see the Composer download page.
  • phpunit/phpunit ... requires php >= 8.4.1 — your PHP is too old for PHPUnit 13. Either upgrade PHP, or pin an older line that still supports your version (for example composer require --dev phpunit/phpunit ^11).
  • “Class not found” / no tests run — double-check the autoload paths in composer.json and rerun composer dump-autoload.

References