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
Next revision
Previous revision
devel:unittesting [2014-06-05 03:04] – [Integration Tests] Typo. samwilsondevel:unittesting [2024-03-09 23:41] (current) – fix xref 2001:9e8:18f1:9e00:a2af:9ad3:e886:2a5b
Line 5: Line 5:
 DokuWiki's unit tests are located in the ''_test'' directory of a [[devel:git]] checkout. **They are not included in the regular releases**. 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.+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 testsand writing your own tests.
  
 ===== Setup ===== ===== Setup =====
  
 +Install the needed requirements using [[composer]]:
  
-==== Requirements ==== 
- 
-  * PHPUnit 3.6.10+ http://www.phpunit.de/ 
-  * PHP 5.3+ http://www.php.net 
- 
-==== 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: 
 <code> <code>
-{                                                                         +cd dokuwiki/_test 
-    "require-dev": {                                                      +composer install
-        "phpunit/phpunit": "3.7.*"                                        +
-    } +
-}                                                                    +
 </code> </code>
- 
-=== via PHP archive (PHAR) === 
- 
-Download https://phar.phpunit.de/phpunit.phar and make it executable on your system. 
  
 ===== Running Tests ===== ===== Running Tests =====
Line 45: Line 24:
 Just change to the ''_test'' directory and run phpunit: Just change to the ''_test'' directory and run phpunit:
  
-  cd _test/ +<code> 
-  phpunit+cd _test/ 
 +composer run test 
 +</code>
  
-PHPUnit will fail on some systems with a //headers already sent// error. +==== Single Test Files ====
-This is a known problem with PHPUnit, the error can be avoided by passing the +
-'--stderr' flag to phpunit:+
  
-  phpunit --stderr+You can run a specific test file by giving it as a parameter:
  
 +  cd _test
 +  composer run test tests/inc/input.test.php
  
-==== Selected Tests ====+==== Grouped Tests ====
  
-You can run a single test file by providing it as an argument to phpunit:+You include or exclude tests by their group:
  
-  phpunit --stderr tests/inc/common_cleanText.test.php+<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>
  
-You can also use groups to exclude certain test from runningFor example use +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))
-the following command to avoid long running test or tests accessing the +
-Internet.+
  
-  phpunit --stderr --exclude-group slow,internet+==== 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 ===== ===== 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.  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. 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.
Line 77: Line 71:
 ==== Naming Conventions ==== ==== 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''+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.
  
-All tests need to end in ''.test.php'' to be found automatically by the test suiteFor DokuWiki code files that contain many unrelated or complex functions you may want to create multiple test files. Egrather than having 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.+In accordance ẃith PHPUnit conventions test classes should end in ''Test'' while non-test classes (egmock objects) omit that postfix. 
 + 
 +This means 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.
  
-Each test file has to contain one <del>or more</del>((should be avoided as only the first is executed when running the file solo)) classes inheriting from [[xref>_test/core/DokuWikiTest.php|DokuWikiTest]]. This class can have multiple test functions, each named with a ''test_''. Inside these functions your assertion can be written. 
  
 ==== Environment ==== ==== Environment ====
Line 98: Line 98:
  
 Additionally the test suite runs a ''setUp()'' method before every test. It does: Additionally the test suite runs a ''setUp()'' method before every test. It does:
-  * reload the dokuwiki config+  * reload the DokuWiki config
   * reload the plugin controller and plug-ins   * reload the plugin controller and plug-ins
   * reload the event handler   * reload the event handler
-  * ensure existing of some files (see [[xref>init_files()]]) +  * ensure the existence of some files (see [[xref>init_files()]]) 
-  * ensure dokuwiki paths (see [[xref>init_path()]])+  * ensure DokuWiki paths (see [[xref>init_path()]])
   * reload global variables like ''$_SERVER''   * reload global variables like ''$_SERVER''
  
 ^ //Note:// Remember to call ''parent::setUp()'' on overwriting the ''setUp()'' method ^ ^ //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.+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 ==== ==== Integration Tests ====
  
Line 131: Line 134:
  
 The request variables are set via setter methods and look like: The request variables are set via setter methods and look like:
 +
 ^ Variable      ^ Setter method ^ ^ Variable      ^ Setter method ^
 | ''$_SERVER''  | ''$request%%->%%setServer()''  | | ''$_SERVER''  | ''$request%%->%%setServer()''  |
Line 138: Line 142:
  
  
-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.+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 then using the setter methods.+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> <code php>
 // a get request // a get request
-$request->get(array('id' => 'start'), '/doku.php');+$response = $request->get(['id' => 'start'], '/doku.php');
 // a post request // a post request
-$request->post(array('id' => 'start'), '/doku.php');+$response = $request->post(['id' => 'start'], '/doku.php');
 </code> </code>
  
Line 155: Line 162:
 As mentioned the requests are not real requests. Every call you make in your test changes the behavior of DokuWiki on the request. 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 event.+Here this is used to hook an event  followed by an ''execute()'' with the default uri.
  
 <code php> <code php>
-function testHookTriggering() {+public function testHookTriggering() 
 +{
     global $EVENT_HANDLER;     global $EVENT_HANDLER;
  
Line 179: Line 187:
 </code> </code>
  
-=== Example: Using phpQuery after a request ===+=== Example: Using php-dom-wrapper after a request ===
  
 Sometimes you want to inspect the resulting html of a request. Sometimes you want to inspect the resulting html of a request.
-To provide a little comfort we use the [[http://code.google.com/p/phpquery/|phpQuery]] library+To provide a little comfort we use the [[https://github.com/scotteh/php-dom-wrapper|php-dom-wrapper]] library.
-With this you can use operate in a jQuery like style on the html+
  
-simple example is:+With this you can operate in a jQuery like style on the returned HTML.  
 + 
 +Two simple examples are:
 <code php> <code php>
-function testSimpleRun() {+public function testSimpleRun() 
 +{
     // make a request     // make a request
     $request = new TestRequest();     $request = new TestRequest();
Line 193: Line 203:
  
     // get the generator name from the meta tag.     // get the generator name from the meta tag.
-    $generator = $response->queryHTML('meta[name="generator"]')->attr('content'); +    $generator = $response->queryHTML('meta[name="generator"]') 
-    +        ->attr('content'); 
     // check the result     // check the result
     $this->assertEquals('DokuWiki', $generator);     $this->assertEquals('DokuWiki', $generator);
 } }
 </code> </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 ==== ==== 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.+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''.+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. 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.
Line 218: Line 246:
  * @group plugins  * @group plugins
  */  */
-class helper_plugin_data_test extends DokuWikiTest {+class helper_plugin_data_test extends DokuWikiTest 
 +{
  
-    protected $pluginsEnabled = array('data', 'sqlite');+    protected $pluginsEnabled = ['data', 'sqlite'];
  
     public static function setUpBeforeClass(){     public static function setUpBeforeClass(){
         parent::setUpBeforeClass();         parent::setUpBeforeClass();
         // copy our own config files to the test directory         // copy our own config files to the test directory
-        TestUtils::rcopy(dirname(DOKU_CONF), dirname(__FILE__).'/conf');+        TestUtils::rcopy(dirname(DOKU_CONF), dirname(__FILE__) . '/conf');
     }     }
  
     public function testExample() {     public function testExample() {
-        $this->assertTrue(true,'if this fails your computer is broken');+        $this->assertTrue(true, 'if this fails your computer is broken');
     }     }
 } }
 </code> </code>
  
-Plugin authors are encouraged to register their plugins with [[https://travis-ci.org|Travis CI]] to have automated testing. The [[http://pluginwizard.dokuwiki.org/|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 [[https://github.com/splitbrain/dokuwiki-travis|dokuwiki-travis script]].+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==== =====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:intellij_idea#integrate_unit_tests|Integrating Unit tests in Intellij IDEA/PHPStorm]]
devel/unittesting.1401930278.txt.gz · Last modified: 2014-06-05 03:04 by samwilson

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