DokuWiki

It's better when it's simple

User Tools

Site Tools


devel:unittesting

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
devel:unittesting [2024-04-05 17:12] 90.186.128.233devel:unittesting [2024-04-06 11:40] (current) – old revision restored (2024-03-09 23:41) saggi
Line 1: Line 1:
-====== Bluetooth ======+====== Unit Testing Dokuwiki ====== 
 + 
 +Very briefly explained, [[wp>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 [[devel:git]] checkout. **They are not included in the regular releases**. 
 + 
 +We use the [[http://www.phpunit.de/manual/current/en/index.html|PHPUnit]] test framework for PHP. This page guides you through preparing your test environment, running tests, and writing your own tests. 
 + 
 +===== Setup ===== 
 + 
 +Install the needed requirements using [[composer]]:
  
-  * installiere bluetooth Tool falls nicht vorhanden: 
 <code> <code>
-apt install bluetooth pi-bluetooth bluez blueman+cd dokuwiki/_test 
 +composer install
 </code> </code>
-  * öffne Bluetooth Interface+ 
 +===== Running Tests ===== 
 + 
 +Running the test suite is simple, here's how. 
 + 
 +==== All Tests ==== 
 + 
 +Just change to the ''_test'' directory and run phpunit: 
 <code> <code>
-bluetoothctl+cd _test/ 
 +composer run test
 </code> </code>
-^ Befehl                 ^ Beschreibung                                                                                                            ^ 
-| agent on/off           | Schaltet Bluetooth Agent ein/aus                                                                                        | 
-| scan on/off            | Schaltet Scan nach Geräten ein/aus, gibt Mac-Adressen der Geräte an. Scant bis Scan ausgeschaltet wird                  | 
-| pair <mac-adresse>     | Pairt mit dem angegeben Gerät                                                                                           | 
-| connect <mac-adresse>  | Verbindet zu bereits bekannten Gerät                                                                                    | 
-| trust <mac-adresse>    | Trustet bekanntes Gerät, Verbindung wird automatisch hergestellt wenn Bluetooth eingeschaltet ist und Gerät erreichbar  | 
  
 +==== Single Test Files ====
 +
 +You can run a specific test file by giving it as a parameter:
 +
 +  cd _test
 +  composer run test tests/inc/input.test.php
 +
 +==== Grouped Tests ====
 +
 +You include or exclude tests by their group:
 +
 +<code bash>
 +  cd _test
 +  # run all tests that require an internet connection
 +  composer run test -- --group internet
 +  # run all tests that don't require an internet connection  
 +  composer run test -- --exclude-group internet
 +</code>
 +
 +Note the ''<nowiki>--</nowiki>'' separator, which composer requires before script arguments starting with double dashes (like ''<nowiki>--group</nowiki>''). ((https://getcomposer.org/doc/articles/scripts.md#running-scripts-manually))
 +
 +==== Plugins ====
 +
 +Plugins tests are tagged with a group named ''plugin_<pluginname>'':
 +
 +<code bash>
 +cd _test
 +composer run test -- --group plugin_extension
 +</code>
 +
 +Note the ''<nowiki>--</nowiki>'' separator, which composer requires before script arguments starting with double dashes (like ''<nowiki>--group</nowiki>'').
 +
 +Please note that some plugins may require other plugins in order to pass, either as dependencies, or because some integration code is being tested. Check if they have a ''requirements.txt'' file in their folder for the needed plugins.
 +
 +===== 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 [[http://www.phpunit.de/manual/current/en/index.html|PHPUnit manual]] and the existing tests, our DokuWiki specific parts are described below.
 +
 +==== Naming Conventions ====
 +
 +Every new bit of code should follow [[https://www.php-fig.org/psr/psr-12/|PSR-12]] code style and be in classes that can be auto-loaded according to [[https://www.php-fig.org/psr/psr-4/|PSR-4]]. Classes following these conventions should be accompanied by tests, following the same naming conventions but use the ''\dokuwiki\test\'' namespace which is autoloaded from the ''_test/tests/'' directory.
 +
 +In accordance ẃith PHPUnit conventions test classes should end in ''Test'' while non-test classes (eg. mock objects) omit that postfix.
 +
 +This means a class ''\dokuwiki\File\PageResolver'' should have a test in ''\dokuwiki\test\File\PageResolverTest'' located in ''_test/tests/File/PageResolverTest.php''.
 +
 +Each test class need to inherit from [[xref>_test/core/DokuWikiTest.php|\DokuWikiTest]]. A test class can have multiple test functions, each prefixed with ''test''. Inside these functions your assertion can be written.
 +
 +
 +For legacy, functional code no namespace is used. Tests are located in the sub directories ''inc'', ''lib'', ''conf'' etc. to reflect the directory structure of the files the tested functions are located in. Legacy testing uses the ''.test.php'' extension.
 +
 +
 +==== 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 the existence of some files (see [[xref>init_files()]])
 +  * ensure DokuWiki paths (see [[xref>init_path()]])
 +  * reload global variables like ''$_SERVER''
 +
 +^ //Note:// Remember to call ''parent::setUp()'' on overwriting the ''setUp()'' method ^
 +
 +Even though 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.
 +
 +To clean up and reset the remainder of a test, the methods ''tearDown()'' and ''tearDownAfterClass()'' are available. Remember here also to use the parent methods, ''parent::tearDown()'' and ''parent::tearDownAfterClass()'' for future robustness.
 +
 +==== 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 [[xref>TestRequest]] class is used to run a request. A request contains three steps:
 +
 +  - Initialization 
 +  - Prepare request
 +  - send request
 +
 +The initialization is done by:
 +<code php>
 +$request = new TestRequest();
 +</code>
 +
 +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 result of the execute method is a [[xref>TestResponse]] object, which let you access headers and the html content rendered by DokuWiki.
 +<code php>
 +$response = $request->execute('/doku.php');
 +</code>
 +
 +Additionally there are two methods for ''POST'' and ''GET'' calls. They may be shorter than using the setter methods, as these combine the setter and ''execute()''.
 +
 +<code php>
 +// a get request
 +$response = $request->get(['id' => 'start'], '/doku.php');
 +// a post request
 +$response = $request->post(['id' => 'start'], '/doku.php');
 +</code>
 +
 +The result of a request is a [[xref>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 an event  followed by an ''execute()'' with the default uri.
 +
 +<code php>
 +public 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!');
 +}
 +</code>
 +
 +=== Example: Using php-dom-wrapper after a request ===
 +
 +Sometimes you want to inspect the resulting html of a request.
 +To provide a little comfort we use the [[https://github.com/scotteh/php-dom-wrapper|php-dom-wrapper]] library.
 +
 +With this you can operate in a jQuery like style on the returned HTML. 
 +
 +Two simple examples are:
 +<code php>
 +public 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);
 +}
 +</code>
 +
 +<code php>
 +public function testPageContent()
 +{
 +    // make a request
 +    $request = new TestRequest();
 +    $response = $request->get(['id' => 'wiki:dokuwiki']);
 +
 +    // search your html
 +    $links = $response->queryHTML('p')->find('a');
 +    $countLinks = $links->count();
 +
 +    // check the result
 +    $this->assertEquals(12, $countLinks);
 +}
 +</code>
 +
 +==== Plugin and Template Tests ====
 +
 +Sometime you need unit tests in your extensions((extensions are plug-ins and templates)). 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. Alternatively you can use the [[plugin:dev|dev Plugin]] to add tests.
 +
 +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 [[xref>DokuWikiTest]] class and the file name must end with ''Test.php''. You should use the namespace ''\dokuwiki\plugin\<yourplugin>\test\''.
 +
 +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:
 +
 +<code php>
 +/**
 + * @group plugin_data
 + * @group plugins
 + */
 +class helper_plugin_data_test extends DokuWikiTest
 +{
 +
 +    protected $pluginsEnabled = ['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');
 +    }
 +}
 +</code>
 +
 +A plugin's config can simply be changed by writing to the **$conf** array. The changes can e.g. be done in the test functions themselves or in the function ''setUp()''. Config changes in function ''setUpBeforeClass()'' will have **no effect**. Have a look at this example:
 +
 +<code php>
 +    public function setUp()
 +    {
 +        global $conf;
 +
 +        parent::setUp();
 +
 +        $conf['plugin']['creole']['precedence'] = 'Creole';
 +        $conf['plugin']['creole']['linebreak'] = 'Whitespace';
 +    }
 +</code>
 +
 +So the correct indexing for plugins is ''$conf ['plugin']['plugin name']['option name']''.
 +
 +===== Continous Integration with Github Actions =====
 +
 +
 +Plugin authors are encouraged to have their tests run automatically on Github Actions.The [[http://pluginwizard.dokuwiki.org/|Plugin Wizard]] and the [[plugin:dev|dev plugin]] add an appropriate yaml configuration to your plugin when you select the Unit Test option. 
 +
 +==== Requirements ====
 +FIXME update
 +
 +If your tests require additional plugins to be installed, provide a ''requirements.txt'' file in your plugin's root directory. See for the details the  [[https://github.com/splitbrain/dokuwiki-travis#plugins-with-dependencies|README ]] in the dokuwiki-travis repository.
 +
 +==== Javascript + Frontend-tests ====
 +
 +
 +FIXME This section needs to be rewritten for Github Actions since Travis is no longer recommended.
 +
 +It is possible to integrate javascript-tests written in [[http://qunitjs.com/|qunit]] to the automated testing. The basis for this is npm, grunt and phantomjs((This approach has been inspired by [[https://jordankasper.com/automated-javascript-tests-using-grunt-phantomjs-and-qunit/]])).
 +
 +<file foo .travis.yml>
 +language: php
 +php:
 +  - "5.5"
 +  - "5.4"
 +  - "5.3"
 +env:
 +  - DOKUWIKI=master
 +  - DOKUWIKI=stable
 +  - DOKUWIKI=old-stable
 +before_install:
 +  - wget https://raw.github.com/splitbrain/dokuwiki-travis/master/travis.sh
 +  - npm install
 +install: sh travis.sh
 +script:
 +  - cd _test && phpunit --stderr --group plugin_yourplugin
 +  - cd ../lib/plugins/yourplugin && grunt
 +</file>
 +
 +npm needs a ''package.json'' to install the dependencies:
 +
 +<file json package.json>
 +{
 +  "name": "yourplugin",
 +  "devDependencies": {
 +    "grunt": "^0.4.5",
 +    "grunt-contrib-qunit": "^0.7.0",
 +    "jquery": "^2.1.4",
 +    "qunitjs": "^1.18.0"
 +  }
 +}
 +</file>
 +
 +grunt needs a ''Gruntfile.js'' to define the tasks:
 +
 +<file javascript Gruntfile.js>
 +module.exports = function(grunt) {
 +  grunt.initConfig({
 +    pkg: grunt.file.readJSON('package.json'), // the package file to use
 +
 +    qunit: {
 +      all: ['_jstest/*.html']
 +    }
 +});
 +grunt.loadNpmTasks('grunt-contrib-qunit');
 +grunt.registerTask('default', ['qunit']);
 +};
 +</file>
 +
 +Finally the qunit html-files have to be adjusted to be able to work with node_modules for automated testing and with online-libraries for browser-testing:
 +
 +<file html qunit.test.html>
 +<!DOCTYPE html>
 +<html>
 +<head>
 +    <meta charset="utf-8">
 +    <title>tests for your plugin</title>
 +    <link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.18.0.css">
 +    <script src="../node_modules/jquery/dist/jquery.js"></script>
 +    <script>window.jQuery || document.write('<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"><\/script>')</script>
 +</head>
 +<body>
 +<div id="qunit"></div>
 +<div id="qunit-fixture"></div>
 +<script src="../node_modules/qunitjs/qunit/qunit.js"></script>
 +<script>window.QUnit || document.write('<script src="//code.jquery.com/qunit/qunit-1.18.0.js"><\/script>')</script>
 +<script src="../script/myscript.js"></script>
 +<script src="mytest.tests.js"></script>
 +
 +
 +</body>
 +</html>
 +</file>
 +
 +See the [[https://github.com/cosmocode/edittable/|edittable plugin]] for an implementation of this approach. 
 +
 +
 +=====See also====
 +  * [[devel:syntax_plugins#unit_testing_syntax_plugins|Unit Testing Syntax Plugins]]
 +  * [[devel:intellij_idea#integrate_unit_tests|Integrating Unit tests in Intellij IDEA/PHPStorm]]
devel/unittesting.txt · Last modified: 2024-04-06 11:40 by saggi · Currently locked by: 2a03:2880:11ff:7::face:b00c

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