How to organize PHPUnit tests and autoloading with NetBeans IDE

2.7k views Asked by At

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

2

There are 2 answers

0
Jens A. Koch On BEST ANSWER

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.

1
mwloda On

The answer of Jens A. Koch was very helpful. But I'd like to write they way it worked for me.

Task

Write and run PHPUnit tests for PHP project with NetBeans IDE 7.4.

Project

The structure of the project

composer.json
index.php
src/
   Client.php
   MyPack/
      Foo/
         Bar.php

where

  • src/Client.php contains class defined in the global namespace
  • src/MyPack/Foo/Bar.php contains class defined in the MyPack\Foo namespace
  • composer.json declares requirements, i.e. PHPUnit, PHPUnit Skeleton Generator and autoladers for classes defined in the src/ and tests/ directories

composer.json

{
    "name": "vendor/php-project-with-tests",
    "autoload": {
        "psr-4": { 
            "": ["src/", "tests/"]
        }
    },
    "require": {},
    "require-dev": {
        "phpunit/phpunit": "4.7.7",
        "phpunit/phpunit-skeleton-generator": "2.0.1"
    }
}

Steps

1. Set Tools

Tell NetBeans where the PHPUnit is contained. I use PHAR files downloaded from https://phar.phpunit.de/

  1. Select menu option Tools -> Options,
  2. Choose PHP, and choose PHPUnit tab
  3. Set scripts:
    • PHPUnit Script: MY_PATH\phpunit-4.7.7.phar
    • Skeleton Generator Script: MY_PATH\phpunit-skelgen-1.2.1.phar

2. Set the directory for tests

  1. Right-click on the project name, choose Properties from context menu
  2. Categories: Sources
    • Test Folder is initially empty
    • Browse Button
    • Create tests directory inside the project directory and use it as Test Folder

3. Set tests provider

  1. Right-click the project name, choose Properties from context menu
  2. Categories: Testing
    • Testing Providers: check PHPUnit

4. Generate test classes

  1. Right-click the class file (e.g. Bar.php), choose Tools -> Create Tests from the context menu

5. Install dependencies

  1. Right-click the project name, choose Composer -> Install (dev) from context menu

That achieves two goals: - generate autoloader, to allow test classes use the src classes - add PHPUnit library to the project for hinting purposes

6. Set bootstrap file

Bootstrap file is executed before tests. For this project vendor/autoload.php is enough.

  1. Right-click the project name, choose Properties from context menu
  2. Categories: Testing/PHPUnit
    • check Use Bootstrap
    • Bootstrap: PROJECT_MAIN_DIR\vendor\autoload.php

Project structure

The test classes are put in wrong path

enter image description here

I can move them manually

enter image description here

The tests are run either way.

Troubles

PHPUnit 4.8 is not working

The PHPUnit of version 4.8.0 and 4.8.6 dont' work. I get message unrecognized option --run

So I must use PHPUnit 4.7.7

PHPUnit Skeleton Generator 2.0.1 is not working

PHPUnit Skeleton Generator 2.0.1 cannot generate tests. I get message [InvalidArgumentException] Commans "MyPack\Foo\Bar" is not defined

So i must use PHPUnit Skeleton Generator 1.2.1