How to Run Naked Unit Tests in a New Legacy Project

Found a typo? Edit me

You know the situation. You come to a new project that you should upgrade and refactor. It has some tests that you long-term developer can run locally. But the automated CI that runs tests on every commit is missing.

Shall we start refactoring and adding CI tools like PHPStan or ECS? How about using what is already there? The tests.

But the tests require this PHP extension, those environment variables, this external service, and these few Docker images to be running.

What if we can find the naked unit tests and run them today by ourselves?

Proof over theory? This technique allowed us to run 41 tests without any setup. That's 41 more than we usually have before investing a lot of learning time. Maybe it will help your legacy project too.


When we come to a project, the first thing to do is run tests. Just in case they work without any setup:

vendor/bin/phpunit


If we get a positive response and tests run successfully, we can thank the developers of this project and start upgrading and refactoring. But more likely, we get one of the following responses:

Then we can check for .env, docker-compose.yml, phpunit.xml extensions in composer.json or even README.md to figure out what extension we need to run the tests. But maybe, we don't have to.

External and Local test Dependencies

Our tests depend on some manual steps we have to do. They do not work out of the box, but they might run after we complete this value here and run this CLI command.

The test dependency can be split into 2 categories:

The first group requires our manual input and research, so there is nothing we can do right now to run them except to learn more about the project.

However, we can run the second group right now:

use PHPUnit\Framework\TestCase;

use App\ValueObject\Tool;

final class SomeTest extends TestCase
{
    public function test()
    {
        $tool = new Tool('Rector');
        $this->assertSame('Rector', $tool->getName());
    }
}

...and we can see if a test fails or passes. How?

vendor/bin/phpunit

Looks so easy, and it is.

But there is a catch. We have to know which of these are the local ones and which are hiding a fractal of dependencies.


How to detect Local Test?

Let's look at examples. What dependencies does the following test have?

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

final class ControllerTest extends KernelTestCase
{
    public function tests()
    {
    }
}

From KernelTestCase we can assume that:

If the test meets one of these conditions, we can skip it.


All the other tests left are the ones we want to test. We put them explicitly into phpunit.xml into the new test suite:

<?xml version="1.0"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="unit">
            <file>tests/Services/ConfigPrinterTest.php</file>
            <file>tests/Services/PackageUpgradeTest.php</file>
        </testsuite>
    </testsuites>
</phpunit>

So we go one test by one and carefully review their parent class...

That sounds like a lot of work. Hmm, how could we automate it?

4 Steps to Automate Local Test Detection

In previous post, we learned about few useful commands for Easy CI setup. There is one more just for our use case.


1. Install Composer Package

composer require symplify/easy-ci --dev


2. Run Command on your tests directory

vendor/bin/easy-ci detect-unit-tests tests

This command generated a phpunit-unit-files.xml file that contains an XML list of detected test files.


3. Open the phpunit-unit-files.xml and move files to your phpunit.xml

<?xml version="1.0"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
    bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="unit">
            <file>tests/Services/ConfigPrinterTest.php</file>
            <file>tests/Services/PackageUpgradeTest.php</file>
        </testsuite>
    </testsuites>
</phpunit>


4. Run --testsuite unit group

vendor/bin/phpunit --testsuite unit

Then set up your CI to run all your local tests we've detected this way.

Your next refactoring is now a bit safer, and you can continue to learn more about the new project.


Happy coding!


Have you find this post useful? Do you want more?

Follow me on Twitter, RSS or support me on GitHub Sponsors.