DokuWiki

It's better when it's simple

User Tools

Site Tools


devel:unittesting

This is an old revision of the document!


Unit Testing Dokuwiki

Very briefly explained, Unit Testing is writing code to test “units” of other code, so that tests can be re-run at any time to determine whether or not code has been “broken” (vs. manually testing applications, which is lengthy and potentially unreliable). The word “unit” is used to mean anything that provides a clearly defined API, such as a PHP class or function.

DokuWiki's unit tests are located in the _test directory of a git checkout. They are not included in the regular releases.

We use the PHPUnit test framework for PHP. This page guides you through preparing your test environment, running tests and writing your own tests.

Setup

Requirements

PHPUnit Installation

via PEAR installer

pear config-set auto_discover 1                                          
pear install pear.phpunit.de/PHPUnit                                     

via Composer

Include a composer.json file in your project, which can be as minimal as:

{                                                                        
    "require-dev": {                                                     
        "phpunit/phpunit": "3.7.*"                                       
    }
}                                                                    

via PHP archive (PHAR)

Download https://phar.phpunit.de/phpunit.phar and make it executable on your system.

Running Tests

Running the test suite is simple, here's how.

All Tests

Just change to the _test directory and run phpunit:

cd _test/
phpunit

PHPUnit will fail on some systems with a headers already sent error. This is a known problem with PHPUnit, the error can be avoided by passing the '–stderr' flag to phpunit:

phpunit --stderr

Selected Tests

You can run a single test file by providing it as an argument to phpunit:

phpunit --stderr tests/inc/common_cleanText.test.php

You can also use groups to exclude certain test from running. For example use the following command to avoid long running test or tests accessing the Internet.

phpunit --stderr --exclude-group slow,internet

Writing Tests

PHPUnit makes writing new tests as painless as possible. In general it means writing a fairly simple PHP class and placing it in the right directory with the right file name. Once that's done, the test can immediately be executed as explained above.

For a general overview on how to write unit tests refer to the PHPUnit manual and the existing tests, our DokuWiki specific parts are described below.

Naming Conventions

Tests are placed somewhere below the ./_test/tests/ directory. Generally it's recommended this directory structure follow that of the DokuWiki code it is testing i.e. tests for ./feed.php go directly under ./_test/tests while tests for ./inc/auth/plain.php would go under ./_test/tests/inc/auth.

All tests need to end in .test.php to be found automatically by the test suite. For DokuWiki code files that contain many unrelated or complex functions you may want to create multiple test files. Eg. rather than having a single _test/tests/inc/common.test.php, having files like _test/tests/inc/common_clientip.test.php and _test/tests/inc/common_obfuscate.test.php is recommended.

Each test file has to contain one or more1) classes inheriting from DokuWikiTest. This class can have multiple test functions, each named with a test_. Inside these functions your assertion can be written.

Environment

Our testsuite runs a setUpBeforeClass() method on instantiating each test class which will:

  • setup a minimal data directory (copied from _test/data) in the system's TEMP directory
  • create a custom config directory in the system's TEMP directory
    • with default configs
    • some overrides from _test/conf
  • rerun DokuWiki initialization

This setup won't load any plug-ins. If you need to enable a plug-in you can override the DokuWikiTest::$pluginsEnabled array and provide the plugin names to load.

Note: Remember to call parent::setUpBeforeClass() on overwriting the setUpBeforeClass() method

Additionally the test suite runs a setUp() method before every test. It does:

  • reload the dokuwiki config
  • reload the plugin controller and plug-ins
  • reload the event handler
  • ensure existing of some files (see init_files())
  • ensure dokuwiki paths (see init_path())
  • reload global variables like $_SERVER
Note: Remember to call parent::setUp() on overwriting the setUp() method

There is no real process isolation between all tests, each test class should have a pretty clean DokuWiki environment to work on. This also includes DokuWiki's autoloader mechanism, so there should be no need to require_once any files yourself.

Integration Tests

Unit tests should test minimal code parts (units) whenever possible. But with a complex application like DokuWiki this isn't always possible. This is why our test suite allows to run simple integration tests as well using a fake request.

Using this mechanism a HTTP request is faked and the resulting HTML output can be inspected. This also allows for testing plugin events.

Running a request

The TestRequest class is used to run a request. A request contains three steps:

  1. Initialization
  2. Prepare request
  3. send request

The initialization is done by:

$request = new TestRequest();

Once it's initialized you can prepare the request. The preparation can be register event hooks or setting request variables.

The request variables are set via setter methods and look like:

Variable Setter method
$_SERVER $request->setServer()
$_SESSION $request->setSession()
$_POST $request->setPost()
$_GET $request->setGet()

Finally you have to execute the request. This can be done by calling the $request->execute('someurl') method. The return value of the execute method is the html content rendered by DokuWiki.

Additionally there are two methods for POST and GET calls. They may be shorter then using the setter methods.

// a get request
$request->get(array('id' => 'start'), '/doku.php');
// a post request
$request->post(array('id' => 'start'), '/doku.php');

The result of a request is a TestResponse object.

Example: Event hooks

As mentioned the requests are not real requests. Every call you make in your test changes the behavior of DokuWiki on the request.

Here this is used to hook a event.

function testHookTriggering() {
    global $EVENT_HANDLER;
 
    $request = new TestRequest(); // initialize the request
    $hookTriggered = false; // initialize a test variable
 
    // register a hook
    $EVENT_HANDLER->register_hook('TPL_CONTENT_DISPLAY', 'AFTER', null,
        function() use (&$hookTriggered) {
            $hookTriggered = true;
        }
    );
 
    // do the request
    $request->execute();
 
    // check the result of our variable
    $this->assertTrue($hookTriggered, 'Hook was not triggered as expected!');
}

Example: Using phpQuery after a request

Sometimes you want to inspect the resulting html of a request. To provide a little comfort we use the phpQuery library. With this you can use operate in a jQuery like style on the html.

A simple example is:

function testSimpleRun() {
    // make a request
    $request = new TestRequest();
    $response = $request->execute();
 
    // get the generator name from the meta tag.
    $generator = $response->queryHTML('meta[name="generator"]')->attr('content');
 
    // check the result
    $this->assertEquals('DokuWiki', $generator);
}

Plugin and Template Tests

Sometime you need unit tests in your extensions2). Here you can use the same classes and methods as used in the DokuWiki core. The plugin wizard has an option to add an example test in your plugin skeleton.

Just put your tests under the <extension dir>/_test/ folder. The tests must conform to the naming convention., eg. your test cases must extend the DokuWikiTest class and the file name must end with .test.php.

To work, your plugin needs to be enabled during the test. This is done by putting it and all plugins it depends on in the $pluginsEnabled member.

Plugin tests should declare a group for all their tests named plugin_<pluginname> to make it easy to run them separately.

If your plugin needs additional files during testing, you can copy them to the test directory in a setUpBeforeClass() method. Be sure to call the parent first.

Here's a small example:

/**
 * @group plugin_data
 * @group plugins
 */
class helper_plugin_data_test extends DokuWikiTest {
 
    protected $pluginsEnabled = array('data', 'sqlite');
 
    public static function setUpBeforeClass(){
        parent::setUpBeforeClass();
        // copy our own config files to the test directory
        TestUtils::rcopy(dirname(DOKU_CONF), dirname(__FILE__).'/conf');
    }
 
    public function testExample() {
        $this->assertTrue(true,'if this fails your computer is broken');
    }
}

Plugin authors are encouraged to register their plugins with Travis CI to have automated testing. The Plugin Wizard adds an appropriate .travis.yml to your plugin when you select the Unit Test option. The needed build environment is provided by the dokuwiki-travis script.

See also

1)
should be avoided as only the first is executed when running the file solo
2)
extensions are plug-ins and templates
devel/unittesting.1432674124.txt.gz · Last modified: 2015-05-26 23:02 by Klap-in

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki