Search code examples
phpnetbeansphpunitautoloadpsr-0

How to organize PHPUnit tests and autoloading with NetBeans IDE


There are rules for autoloading ( http://www.php-fig.org/psr/psr-0/ ) and PHPunit testing ( https://phpunit.de/manual/current/en/organizing-tests.html ). Separetely they are easy to implement but combining them is not.

I have also read topic a PHPUnit best practices to organize tests but applying the answer is not obvious for me. Writing my own project I have had several problems that probably have the same origins in code organization. I wish I could have recipes for a real simple example. Therefore I make my question quite big.

Example project

I use NetBeans IDE 7.4 because it supports PHPUnit, SVN and Doxygen (but I lack experience of using that IDE).

Project Properties are

  • Project Folder: C:\xampp\htdocs\myapps\PhpProject1
  • Source Folder: C:\xampp\htdocs\myapps\PhpProject1
  • Test Folder: C:\xampp\htdocs\myapps\PhpProject1\tests

There is the first problem. NetBeans blocks the mirror src/tests structure from the beginning. There is no Browse button for Source Folder. The Test folder must be different from Source folder.

I use PHPUnit and skelgen from PHAR files located outside the project. (Tools->Options->PHP/PHPUnit)

  • PHPUnit Script: C:\xampp\phar\phpunit.phar
  • Skeleton Generator Script: C:\xampp\phar\phpunit-skelgen-1.2.1.phar

I have the following file structure in the project directory

index.php
src/
    Client.php
    srcAutoload.php
    MyPack/
        Foo/
            Bar.php
        Quu/
            Baz.php

Where src is not a namespace since

  • file src/MyPack/Foo/Bar.php contains class MyPack\Foo\Bar
  • file src/MyPack/Quu/Baz.php contains class MyPack\Qoo\Baz
  • file src/Client.php contains class Client

srcAutoload.php contains the modified PSR-0 autoload implementation from https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md . The difference is that before doing require() the PSR-0 filename is prefixed with src/

$fileName = 'src' . DIRECTORY_SEPARATOR . $fileName;

Test Generation and Autoloading Trouble

I used Tools/Create Tests (from context menu) for the Bar and Baz classes and I end up with following structure

index.php
src/
    Client.php
    srcAutoload.php
    MyPack/
        Foo/
            Bar.php
        Quu/
            Baz.php
tests/
    src/
        BarTest.php
        BazTest.php
        MyPack/
           Foo/
           Quu/

That definitely is not mirror symmetric. Moreover, generated tests don't know how to load the classes they are testing.

Fatal error: Class 'MyPack\Foo\Bar' not found in C:\xampp\htdocs\myapps\PhpProject1\tests\src\BarTest.php on line 20

My Workaround for Autoloading

As stated before, the test runner has different working directory than the project. So the autoloader for testing should create different full filename. I have duplicated PSR-0 autoloader, save it as tests/srcAutoloadFromTests.php and added another prefix

/// after PSR-0 implementation code 
/// I have PSR-0 $fileName

/* Escaping from tests subfolder by "../" prefix*/ 
$fromTests = '..' . DIRECTORY_SEPARATOR;

/* Prefixing full filename with "src/" */ 
$fileName = $fromTests . 'src' . DIRECTORY_SEPARATOR . $fileName;

if (is_file($fileName)) {
    require $fileName;
}

Afterwards, I have added Use Bootstrap (Project Properties->Testing/PHPUnit). File tests/bootstrap.php contains:

require_once 'srcAutoloadFromTests.php';

This solution makes PHPUnit executing tests but looks ugly to me.

More specific questions

Why the mirroring src/ in tests/ is not helping for autoloading?

I have manually created mirrored structure by creating correct folders and moving files:

index.php
src/
    Client.php
    srcAutoload.php
    MyPack/
        Foo/
            Bar.php
        Quu/
            Baz.php
tests/
    bootstrap.php
    srcAutoloadFromTests.php
    MyPack/
        Foo/
            BarTest.php
        Quu/
            BazTest.php

According to https://phpunit.de/manual/current/en/organizing-tests.html PHPUnit can automatically discover and run the tests by recursively traversing the test directory. It does not work for me. Without my srcAutoloadFromTests.php I get the same error as for the former non-mirrored structure.

Fatal error: Class 'MyPack\Foo\Bar' not found in C:\xampp\htdocs\myapps\PhpProject1_1\tests\MyPack\Foo\BarTest.php on line 20

What about the duplication of autoloading code

Because NetBeans does not allow use Source Folder as a Test Folder, I must have two autoloaders for the same class. I'm not sure if it is a good practice.

Autoloading should not bother other programmers working same project. I look for something more generic.

Besides in the future I could use third-party code. Would their autoloaders also need duplication?

Should the src/ and tests/ folders be top-level as vendor/

I have been learning the autoloading rules some time ago. Now I find out that PSR-0 is marked as deprecated According to PSR-4 src/ and tests/ should be placed somewhere in the middle of the path.

But there is a practice to put the third-party code inside the top-level vendor/ folder. Shouldn't the src/ and tests/ folders be placed on the same level.

src/
   … my code
tests/
   … my tests 
vendor/
   … third-party code

that makes the entire structure more PSR-0 style.

No Hinting PHPUnit in NetBeans

While writing BazTest and BarTest code the NetBeans IDE does not hint PHPUnit methods although both class extend \PHPUnit_Framework_TestCase

namespace MyPack\Foo;

class BarTest extends \PHPUnit_Framework_TestCase { /* ... */ }

Is it because I use PHPUnit from PHAR?

I hope citing the example made my questions more clear. Thank you for reading and I look for good advices


Solution

  • Man, what a big question. :) Ok, let's address some of the issues.

    1. PHPUnit Skelgen tests in wrong path

      That definitely is not mirror symmetric

      True. It's an odd (and i think unresolved) path issue with skelgen.

      Just move the generated *Test files into their correct folders.

      Same question over here: PHPUnit, Netbeans and Symfony2 and wrong tests location

    2. Folder structure for your project

      Yes, this is the folder structure to work with:

      src/
         … my code
      tests/
         … my tests 
      vendor/
         … third-party code
      
    3. The autoloading issue:

      (Without Composer): your autoloader lives somewhere in src. During your application bootstrap your require it, after that autoloading is setup for the src folder. All classes in the src folder are from now on autoloaded.

      Now over to tests: during the test bootstrap you include the autoloader from src and add src and test. This makes all classes in srcand tests autoloadable.

      (with Composer): This whole issue gets a non-issue, if you work with Composer. Composer has require and require-dev sections to define the vendor dependencies. The autoloading is generated automatically, you simply include the Composer autoload file and done (one inclusion during src bootstrap and one inclusion during test boostrap). If the project is installed with development dependencies then those will be part of the autoloading, if not, they will not appear.

    4. No Hinting PHPUnit in NetBeans

      Yes, the PHAR is not introspected or extracted, so Netbeans will not know the Class names of PHPUnit.

      Solution 1: Extract the PHAR.

      Solution 2: Add a PHP with classnames as aliases to guide Netbeans. I don't know if one is around. But it's a pretty common issue, chances are high that someone created it already.

      Solution 3: Let me suggest Composer again: just pull phpunit as require-dev dependency into your project. The vendor path is scanned by Netbeans and the classnames will resolve.