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 classMyPack\Foo\Bar
- file
src/MyPack/Quu/Baz.php
contains classMyPack\Qoo\Baz
- file
src/Client.php
contains classClient
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
Man, what a big question. :) Ok, let's address some of the issues.
PHPUnit Skelgen tests in wrong path
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
Folder structure for your project
Yes, this is the folder structure to work with:
The autoloading issue:
(Without Composer): your autoloader lives somewhere in
src
. During your application bootstrap your require it, after that autoloading is setup for thesrc
folder. All classes in thesrc
folder are from now on autoloaded.Now over to tests: during the test bootstrap you include the autoloader from
src
and addsrc
andtest
. This makes all classes insrc
andtests
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.
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.