Unit Testing

Information

These informations are not completed yet.

Information

For consistent quality standards we have to use unit test. This article describes the process of writing test for CONTENIDO with PHPUnit.

For writing/fixing unit tests please use branch feature/unit_tests (latest one for CONTENIDO-Unit-Tests)

 

Installation PHPUnit without Pear

  • Download https://phar.phpunit.de/phpunit.phar and put it e.g. to C:\projects\bin
  • Add phpunit.cmd to the same directory. Content is: echo @php "%~dp0phpunit.phar" %* > phpunit.cmd
  • Add C:\projects\bin; to variable Path of your Environment Variables (Windows: Right click on Computer -> Properties -> Advanced System Settings -> Advanced -> Environment Variables)
  • If everything went well, typing phpunit should give you an output in the command line

Pear Installation

At first it is necessary to install PHPUnit. Recommended is the installation with pear:

  • Request http://pear.php.net/go-pear.phar in your browser and save the output to a local file go-pear.phar.
  • You can then run php go-pear.phar with admin rights
  • to verify it works, simply type pear. A list of commands should be shown

PHPUnit installation

pear channel-discover pear.phpunit.de
pear channel-discover pear.symfony.com
pear install phpunit/PHPUnit

If you are running an old version of pear and can't upgrade it with pear upgrade pear you can delete the pear folder and perform a new installation.

Test suite

You have to include the bootstrap.php to be able to run tests in the CONTENIDO environment.  The files with your test classes are included with the call of TestSuiteHelper::loadFeSuite('Template');. This function includes all files out of the given foldername. Finally you have to add your test classes at the suite function:

require_once ('bootstrap.php');
TestSuiteHelper::loadFeSuite('Template');
class ContenidoSecurityAllTest {
    public static function suite() {
        $suite = new PHPUnit_Framework_TestSuite('Template');
        $suite->addTestSuite('TemplateUnitTest');
        return $suite;
    }
}

Test case

All functions with the @test annotation will be executed while the test process. The function name should be the name of the function you want to test with additional prefix ‘test’. The methods setUp() and tearDown() can be used to initialize and remove test data. These functions will be executed before (setUp()) and after (tearDown()) every call of a function with the @test annotation.

 

class TemplateUnitTest extends PHPUnit_Framework_TestCase {
    protected function setUp() {
    }

    protected function tearDown() {
    }

    /**
     * @test
     */
    public function testFunctionName() {
    }
}

Best practises

In some cases you have to manipulate private or protected members to get a 100% code coverage of the function you want to test. Since PHP 5.3 it is possible to reach this goal with reflections.

The following example shows how to manipulate a protected member and get its value without the need to implement a public accessors method.

 At first you have to instantiate a new ReflectionClass object with the class name of the class you want to reflect. After this you have to set the property you want accessible (lines 2-3). Now you can use the setValue and getValue as accessor methods.

$collectionReflection = new ReflectionClass('ItemCollection');
$collectionCache = $collectionReflection->getProperty('_collectionCache');
$collectionCache->setAccessible(true);
$collectionCache->setValue($collectionReflection, 'testValue');
// outputs 'testValue'
var_dump($collectionCache->getValue($collectionReflection));

Optionally you can use the equivalent wrapper frunctions

$collectionReflection = new ReflectionClass('ItemCollection');
Util::setMember($collectionReflection, '_collectionCache', 'testValue');
// outputs 'testValue'
var_dump(Util::getMember($collectionReflection, '_collectionCache'));

If you want to run a protected method you have to instantiate a ReflectionMethod object, set it accessible too and invoke your params with the function invokeArgs(object $object , array $args) .

Conventions

  • Unit tests have to be implemented using PHPUnit.
  • Tests have to be located in the tests folder.
  • The structure should be equivalent to the backend folder.
  • One test suite should be created per folder (which is ideally a subpackage).
  • Test suites for functions could be organized differently (one for tpl functions, one for str functions etc.).
  • Names for test case files should be the same as the name for the tested file, except that '.test' is appended before '.php' e.g. class.item.collection.test.php.
  • Names of test classes testing classes are the same as the name of the tested class that is suffixed by 'Test' e.g. ItemCollection becomes ItemCollectionTest.
  • Names of test classes testing functions should look like the file name w/o 'functions', e.g. functions.general.php becomes GeneralTest.
  • Names of test methods have to begin with 'test' followed by the name of the tested method (capitalized & w/o underscores) or function and optionally suffixed by an identifier indicating the input data used for the current test.
  • The methods setUp & tearDown should always be implemented.
  • Annotations should not be used (e.g. @test & @expectedException)
  • If a test method contains more than one test these should be introduced by a short comment.
  • When starting from scratch a test class skeleton should contain test methods for all public methods of the tested class containing each the line $this->markTestIncomplete('incomplete implementation');
  • cTestCase ...
  • SQL statements whire are needed to perform the tests are organized in the folder "test/sql"
  • SQL files should contain DROP TABLE statements for the corresponding database tables, the CREATE TABLE statement for the table structure and the INSERT INTO statements for the test data entries.
  • The name of the SQL files are equal to the database table name, prefixed by "test_" (instead of the regular database table name prefix) and suffixed by ".sql"