106 lines
4.4 KiB
Markdown
106 lines
4.4 KiB
Markdown
|
# Writing PHPUnit tests
|
||
|
|
||
|
## Test path and filename
|
||
|
|
||
|
The path where you should put your PHPUnit tests should reflect the path in the sourcetree, with test/php/ prepended. So
|
||
|
if you're testing a file library/Icinga/My/File.php the test file should be at test/php/library/Icinga/My/File.php. This
|
||
|
also applies for modules, where the test folder is underneath modules/myModule/test/php
|
||
|
|
||
|
## Example test skeleton
|
||
|
|
||
|
Let's assume you're testing a class MyClass underneath the MyModule module and the file can be found at
|
||
|
modules/mymodule/library/MyModule/Helper/MyClass.php.
|
||
|
|
||
|
<?php
|
||
|
// The namespace is the same namespace as the file to test has, but with 'Test' prepended
|
||
|
namespace Test\Modules\MyModule\Helper;
|
||
|
|
||
|
// Require the file and maybe others. The start point is always the applications
|
||
|
// testss/php/ folder where the runtest executable can be found
|
||
|
|
||
|
require_once '../../mymodule/library/MyModule/Helper/MyClass.php';
|
||
|
|
||
|
class MyClassTest extends \PHPUnit_Framework_TestCase
|
||
|
{
|
||
|
public function testSomething()
|
||
|
{
|
||
|
$this->assertTrue(true, "Asserting that the world didn't end yet");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
## Requirements and the dependency mess
|
||
|
|
||
|
### spl_autoload_register vs. require
|
||
|
|
||
|
When looking at our test classes, you'll notice that we don't use PHPs autoloader to automatically load dependency, but
|
||
|
write 'require_once' by ourselfs. This has the following reasons:
|
||
|
|
||
|
- When writing tests, you to be aware of every dependency your testclass includes. With autoloading, it's not directly
|
||
|
obvious which classes are included during runtime.
|
||
|
- When mocking classes, you don't need to tell your autoloader to use this class instead of the one used in production
|
||
|
- Tests can't be run isolated without an boostrap class initializing the autoloader
|
||
|
|
||
|
### How to avoid require_once massacres: LibraryLoader
|
||
|
|
||
|
The downside of this approach is obvious: Especially when writing compoment tests you end up writing a lot of 'require'
|
||
|
classes. In the worst case, the PHP require_once method doesn't recognize a path to be already included and ends up
|
||
|
with an 'Cannot redeclare class XY' issue.
|
||
|
|
||
|
To avoid this, you should implement a LibraryLoader class for your component that handles the require_once calls.
|
||
|
For example, the status.dat component tests has a TestLoader class that includes all dependencies of the component:
|
||
|
|
||
|
|
||
|
namespace Tests\Icinga\Protocol\Statusdat;
|
||
|
use Test\Icinga\LibraryLoader;
|
||
|
|
||
|
require_once('library/Icinga/LibraryLoader.php');
|
||
|
|
||
|
/**
|
||
|
* Load all required libraries to use the statusdat
|
||
|
* component in integration tests
|
||
|
*
|
||
|
**/
|
||
|
class StatusdatTestLoader extends LibraryLoader
|
||
|
{
|
||
|
/**
|
||
|
* @see LibraryLoader::requireLibrary
|
||
|
*
|
||
|
**/
|
||
|
public static function requireLibrary()
|
||
|
{
|
||
|
// include Zend requirements
|
||
|
require_once 'Zend/Config.php';
|
||
|
require_once 'Zend/Cache.php';
|
||
|
require_once 'Zend/Log.php';
|
||
|
|
||
|
// retrieve the path to the icinga library
|
||
|
$libPath = self::getLibraryPath();
|
||
|
|
||
|
// require library dependencies
|
||
|
require_once($libPath."/Data/AbstractQuery.php");
|
||
|
require_once($libPath."/Application/Logger.php");
|
||
|
require_once($libPath."/Data/DatasourceInterface.php");
|
||
|
|
||
|
// shorthand for the folder where the statusdat component can be found
|
||
|
$statusdat = realpath($libPath."/Protocol/Statusdat/");
|
||
|
require_once($statusdat."/View/AccessorStrategy.php");
|
||
|
// ... a few more requires ...
|
||
|
require_once($statusdat."/Query/Group.php");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Now an component test (like tests/php/library/Icinga/Protocol/Statusdat/ReaderTest.php) can avoid the require calls and
|
||
|
just use the requireLibrary method:
|
||
|
|
||
|
use Icinga\Protocol\Statusdat\Reader as Reader;
|
||
|
|
||
|
// Load library at once
|
||
|
require_once("StatusdatTestLoader.php");
|
||
|
StatusdatTestLoader::requireLibrary();
|
||
|
|
||
|
**Note**: This should be used for component tests, where you want to test the combination of your classes. When testing
|
||
|
a single execution unit, like a method, it's often better to explicitly write your dependencies.
|
||
|
|
||
|
If you compare the first approach with the last one you will notice that, even if we produced more code in the end, our
|
||
|
test is more verbose in what it is doing. When someone is updating your test, he should easily see what tests are existing
|
||
|
and what scenarios are missing.
|