Testing eZ Publish - Test System

Warning

The test system is considered ever evolving and backwards incompatible changes may occur. We do invite you to start writing tests for it, just be warned that the tests may need some updating as we evolve the system.

Getting started

Requirements

  • PHPUnit 3.5.x or newer: http://www.phpunit.de/
  • eZ Components

Installation

  1. Start with a clone of the github repo (updated to at least 30/9/2008 - svn trunk rev 22408or github commit 530f3ad97ba5abecc19474baad92e8effd12d6d3)
  2. To run the tests in another eZ installation, copy into it the "tests" folder from the github installation
  3. Generate autoloads for the test system:
php bin/php/ezpgenerateautoloads.php -s

Running tests

To run all tests do:

php tests/runtests.php --dsn mysql://root@localhost/testdb

The --dsn parameter is required and is used to tell the test system what type of database, username, host and which database to use. Make sure the username you specify is allowed both create and remove the database. The DSN uses the following format:

databasetype://username:password@host/database

Note: do not use an existing eZ Publish database for running tests, as it will be wiped out and recreated on need by the tests!

If you want to run a single test suite, just point to the directory containing a suite.php file.

php tests/runtests.php --dsn mysql://root@localhost/testdb tests/tests/lib/ezdb

Filter which tests to run

To run a single test you can use the --filter option like this:

php tests/runtests.php --dsn mysql://root@localhost/testdb --filter=testGlobBrace

In the above example both eZSysTest::testGlobBrace and eZSys::testGlobBraceSupported will be executed. If you only want to execute eZSysTest::testGlobBrace run:

php tests/runtests.php --dsn mysql://root@localhost/testdb --filter="testGlobBrace$"

This works too:

php tests/runtests.php --dsn mysql://root@localhost/testdb --filter="eZSysTest::testGlobBrace$"

As of PHPUnit 3 --filter can also be used to filter on test class names. This is a handy way of running all tests in a test case. Example:

php tests/runtests.php --dsn mysql://root@localhost/testdb --filter="eZSysTest"

List tests

To list all test use the --list-tests option:

php tests/runtests.php --list-tests

You can also narrow the output to only list tests defined in a specific suite:

php tests/runtests.php --list-tests tests/tests/lib/ezutils

Running a group of tests (group annotation)

With PHPUnit you can group different tests together by adding the group annotation to a method's documantion block:

To list all groups run:

php tests/runtests.php --list-groups

To run a group use the -g parameter:

php tests/runtests.php --dsn mysql://root@localhost/testdb -g issue_13492

Writing tests

Naming conventions

The test system itself follows eZ Components naming conventions defined here: http://ezcomponents.org/contributing/coding_standards#id8.

The tests itself follows the naming conventions of the code that it tests. In this case this means the tests follows the eZ Publish naming convention.

File and directory layout

The file structure used in eZ Publish is mirrored inside the test system. As an example, if you want to write tests for kernel/classes/ezcontentobject.php the test file should be located in tests/tests/kernel/classes/ezcontentobject_test.php. Regression tests for ezcontentobject should be located in tests/tests/kernel/classes/ezcontentobject_regression.php

*_test.php is the suffix used for unit tests. *_regression.php is used for as the suffix for regression tests.

Writing a unit test

The test system comes with a handy CLI script for generating stub tests from an existing class to get you going quickly. As an example, you can generate a stub test from kernel/classes/ezpreferences.php like this:

php tests/toolkit/extras/scripts/create-test-from-class.php \
               -s kernel/classes/ezpreferences.php \
               -d tests/tests/kernel/classes/ezpreferences_test.php

The generated tests/tests/kernel/classes/ezpreferences_test.php file will then look like this:

<?php
 
class eZPreferencesTest extends ezpTestCase
{
    public function __construct()
    {
        parent::__construct();
        $this->setName( "eZPreferences Unit Tests" );
    }
 
    public function testSetValue()
    {
        self::markTestIncomplete( "Not implemented" );
    }
 
    public function testValue()
    {
        self::markTestIncomplete( "Not implemented" );
    }
    // [...]
}
?>

To make the new test case work we need to add it to the appropriate suite.php. For this test case that is tests/tests/kernel/suite.php. Add

$this->addTestSuite( 'eZPreferencesTest' );

to the __construct method of eZKernelTestSuite and remember to regenerate test autoloads. To verify that the new test case is working run 'runtests.php' with -v:

oh:/www/trunk $ php tests/runtests.php -D mysql://root@localhost/testdb -v 
Based on ezcUnitTest in eZ Components
 
PHPUnit 3.3.0 by Sebastian Bergmann.
 
eZ Publish
 eZ Publish Test Suite
  eZ Publish Kernel Test Suite
   eZPreferencesTest
   IIIIIIII
 
[...]

Success! We're now ready to start writing the test by filling in the test* methods.

Normally tests are short and straight forward and should require little or no documentation. Due to the complexity of eZ Publish not all tests ends up being in this way. If you are writing a somewhat complex unit test, please document it a way that makes it easier to understand.

Writing a regression test

Regression tests are created in the same way as with unit tests, except the classes end with "Regression" and the file suffix is _regression.php. When writing a regression test please document it with the following:

  1. Issue number + title of the issue.
  2. Short description of the issue if the title isn't descriptive enough.
  3. If the test is longer than a few lines, include what the test does in order to reproduce the issue.
  4. The result and the expected result.
  5. Link to the issue.

Here's a doc header example taken from testLinksAcrossTranslations():

/**
 * Test scenario for issue #13492: Links are lost after removing version
 *
 * Test Outline
 * ------------
 * 1. Create a Folder in English containing a link (in the short_description attribute).
 * 2. Translate Folder into Norwegian containing another link (not the same link as above.)
 * 3. Remove Folder version 1. (Version 2 is created when translating).
 *
 * @result: short_description in version 2 will have an empty link.
 * @expected: short_description should contain same link as in version 1.
 * @link http://issues.ez.no/13492
 */

If the issue has multiple tests group them together to make it easy to execute all tests for a single issue using the group annotation "@group issue_ISSUE_NUMBER" where ISSUE_NUMBER is the issue ID in the bug tracker. Example:

* @group issue_13492

Using a database

A common thing for tests is to interact with a database. By extending your test class or suite from either ezpDatabaseTestCase or ezpDatabaseTestSuite your test class/suite will become database aware. By default ezpDatabaseTestCase and ezpDatabaseTestSuite sets up a clean database using the dba files included in eZ Publish:

  • share/db_schema.dba
  • share/db_data.dba

Inserting your own data

If you need to provide your own schema/data you can override which SQL files is loaded by ezpDatabaseTestSuite by specifing the path to one or more SQL files in the $sqlFiles class method:

Example:

class eZURLAliasMlTest extends ezpDatabaseTestCase
    {
        protected $insertDefaultData = false;
        protected $sqlFiles = array( array( "kernel/sql/", "kernel_schema.sql" ),
                                             "kernel/sql/common/cleandata.sql" );
        // [...]
    }

Setting $insertDefaultData to false tells the test system that it should not attempt to load any data. If $insertDefaultData is not defined or if it's set to true the test system will first load the default data, then load any SQL files defined in $sqlFiles.

The first entry in $sqlFiles, array( "kernel/sql/", "kernel_schema.sql" ) is a way to provide a SQL file for one or more of the supported database types. If you're using MySQL, the test system would try to insert kernel/sql/ mysql/kernel_schema.sql. If the database type is postgresql, the test system would try to insert kernel/sql/ postgresql/kernel_schema.sql, and so forth.

The database type is determined by the -D (--dsn) parameter provided to the test runner.

Note: you can specify $sqlFiles on either your test class or in your suite. The default test runner behaviour is to only create a new database per suite. If you provide $sqlFiles in your test classes you will need start the test runner with the --db-per-test argument.

Providing extra data

In the above exampe we discussed how to override what data gets initially loaded into the test database. If all you need is to provide some additional data you can leverage the ezpTestDatabaseHelper class to load some extra SQL files:

$sqlFiles = array( "myextension/sql/data.sql" );
ezpTestDatabaseHelper::insertSqlData( $this->sharedFixture, $sqlFiles );

Writing tests for an extension

Extensions can include their own tests. When running tests the test runner will not only look for tests inside the tests/ directory, it will also scan each extension looking for a tests/suite.php file inside the extension. The extension does not need to be in the list of active extension (ActiveExtensions[] in site.ini) for it to be included.* The only critera for the extension to be included is that is has a folder "tests/" with a suite.php inside. The suite.php should then include all tests in the extension. Below is an example of a typical extension file layout and where the tests should be located:

extension
    |-- myextension
        |-- autoloads
        |-- design
        |-- modules
        |-- settings
        |-- tests
        |   |-- mytest1.php
        |   |-- mytest2.php
        |   `-- suite.php
        `-- translations

You can specify the path to an extension to only run tests for that extension:

php tests/runtests.php --dsn mysql://root@localhost/testdb extension/myextension

* Remember that you still might need to regenerate the autoload array for the extension depending on how it's built.

One database per suite

Currently, the default behaviour is that a database is created per suite, not per test. Creating/removing the database easily adds 1-2 seconds of overhead per test on the database management systems that eZ Publish supports out of the box (MySQL, PostgreSQL). However, there's an extension ezsqlite which allows you to use an in-memory SQLite 3 database to run the tests with.

If you pass in the argument --db-per-test to the test runner it will change from the default behaviour, one database per suite, to one database per test. It's strongly encouraged that you write tests that works with both --db-per-test turned on and off.

To illustrate the difference in speed, below is how long it takes to run all tests with --db-per-test:

php tests/runtests.php --dsn mysql://root@localhost/testdb --db-per-test
PHPUnit 3.3.0 by Sebastian Bergmann.
 
............................................
 
Time: 01:48
 
OK (44 tests, 99 assertions)

And without --db-per-test:

php tests/runtests.php -D mysql://root@localhost/testdb
PHPUnit 3.3.0 by Sebastian Bergmann.
 
............................................
 
Time: 19 seconds
 
OK (44 tests, 99 assertions)

Resources