DEV Community

Philip Weinke
Philip Weinke

Posted on • Originally published at philip-weinke.de

Colocating Tests and Production Code in PHP(Unit)

Colocating tests and production code is the default in some programming languages and has become the de facto standard in others. I have to admit that while I liked it in Go and TypeScript, I always felt a little queasy about doing it in PHP. I couldn't really name it; it was more a sense of "it doesn't feel right". Of course, it turned out to be just a bullshit bias, because it works just as well in PHP as it does in other languages. No matter which language, the benefits are the same:

  • You see at first glance if there are tests for a piece of code
  • You don't have to navigate as much anymore
  • You don't have to maintain parallel structures in source and test directories

Let's give it a try, shall we?

Migrating a simple project

Take this setup with test and production code in parallel structures and a single test suite:

src/
└── MyProject
    ├── MyClass.php
    └── MyOtherClass.php
tests/
└── MyProject
    ├── MyClassTest.php
    └── MyOtherClassTest.php
Enter fullscreen mode Exit fullscreen mode
<testsuites>  
    <testsuite name="default">  
        <directory>tests</directory>  
    </testsuite>
</testsuites>
Enter fullscreen mode Exit fullscreen mode

The good news is that migration is as easy as dragging and dropping the MyProject directory from tests to src; at least if you're using PhpStorm. PhpStorm will take care of changing namespaces appropriately. If your editor of choice doesn't offer this kind of refactoring, search and replace will do as well.

src/
└── MyProject
    ├── MyClass.php
    ├── MyClassTest.php
    ├── MyOtherClass.php
    └── MyOtherClassTest.php
Enter fullscreen mode Exit fullscreen mode

The last thing you need to do is point PHPUnit to the new location. Just change the directory from tests to src and you're ready to go:

<testsuites>  
    <testsuite name="default">  
        <directory>src</directory>  
    </testsuite>
</testsuites>
Enter fullscreen mode Exit fullscreen mode

What about multiple test suites?

When there is more than a single test suite, things get a bit tricky. Let's take a look at this setup:

tests/
├── Integration
│   └── MyProject
│       └── MyClassTest.php
└── Unit
    └── MyProject
        └── MyOtherClassTest.php
Enter fullscreen mode Exit fullscreen mode
<testsuites>  
    <testsuite name="integration">  
        <directory>tests/Integration</directory>  
    </testsuite>
    <testsuite name="unit">  
        <directory>tests/Unit</directory>  
    </testsuite>
</testsuites>
Enter fullscreen mode Exit fullscreen mode

If we colocate them the same way we did in the first example, there's no way to determine which test belongs to which test suite. Here are two options to approach this problem.

Using suffixes

The first option is to change the suffix Test to either IntegrationTest or UnitTest. This allows a clear distinction to which suite a test belongs, although it's a bit clunky.

src
└── MyProject
    ├── MyClass.php
    ├── MyClassIntegrationTest.php
    ├── MyOtherClass.php
    └── MyOtherClassUnitTest.php
Enter fullscreen mode Exit fullscreen mode
<testsuites>
    <testsuite name="integration">  
        <directory suffix="IntegrationTest.php">src</directory>  
    </testsuite>
    <testsuite name="unit">  
        <directory suffix="UnitTest.php">src</directory>  
    </testsuite>
</testsuites>
Enter fullscreen mode Exit fullscreen mode

Using groups

The second options is to use the group annotation to categorize tests. Since groups and test suites are different things and a test suite in PHPUnit cannot be composed of groups, the configuration can be simplified to a single test suite. Just remember that you have to use the group option instead of the testsuite option when running tests (phpunit --group integration or phpunit --group unit).

/**
 * ...
 * @group integration
 */
final class MyClassTest extends TestCase
{
    // ...
}


/**
 * ...
 * @group unit
 */
final class MyOtherClassTest extends TestCase
{
    // ...
}
Enter fullscreen mode Exit fullscreen mode
<testsuites>
    <testsuite name="default">  
        <directory>src</directory>  
    </testsuite>
</testsuites>
Enter fullscreen mode Exit fullscreen mode

Tweaking PhpStorm

I was a little disappointed when I discovered that the files in my project window looked like this:

Snippet of PhpStorm's project window: test and production classes without clear distinction

The test classes have the red/green arrow indicating a test, but I was hoping they would be highlighted as I was used to with jest tests. Fortunately, this is easy to customize.

Go to Preferences | Appearance & Behavior | Scopes and set up a new scope matching test classes (file:*Test.php):

Screenshot of PhpStorm's settings dialog (Preferences | Appearance & Behavior | Scopes)

Then add an element for the new scope and pick a color under Preferences | Appearance & Behavior | File Colors:

Screenshot of PhpStorm's settings dialog (Preferences | Appearance & Behavior | File Colors)

It should look something like this now:

Snippet of PhpStorm's project window: test classes highlighted green

Sweet!

Another thing you may want to tweak is the severity of certain inspections. I noticed warnings showing up that weren't there before. This is probably due to the way PhpStorm handles files based on what type of directory they're in (Preferences | Directories). Since we already have our new scope set up, we just have to change the severity under Preferences | Editor | Inspections to mute unwanted inspections.

Screenshot of PhpStorm's settings dialog (Preferences | Editor | Inspections)

That's it

That's all I have to say on this topic at the moment. I've been using this approach in my personal projects for over a year now, and introduced it at work about a month ago. So far, everyone likes it.

Top comments (0)