diff --git a/tests/php-unit-tests/README.md b/tests/php-unit-tests/README.md index fb3f48fdb..4cc175be1 100644 --- a/tests/php-unit-tests/README.md +++ b/tests/php-unit-tests/README.md @@ -1,15 +1,29 @@ # PHP unitary tests -## Where should I add my test? +Documentation on creating and maintaining tests in iTop. -- Covers an iTop PHP class or method? - - Most likely in "unitary-tests". -- Covers the consistency of some data through the app? - - Most likely in "integration-tests". -## Tests prerequisites -Install iTop with default setup options : + +## Prerequisites + +### PHPUnit configuration file +A default file is located in `/tests/php-unit-tests/phpunit.xml.dist` + +If you need to customize it, copy it to `phpunit.xml` (not versioned). + +### PHP configuration +* PHPUnit configuration file + - `memory_limit`: as the tests are for the most part ran in the same process, memory usage may become an issue! A default value is set in default PHPUnit configuration XML file, don't hesitate to update it if needed +* PHP CLI php.ini + - enable OpCache + - disable Xdebug (xdebug.mode=off) : huge performance improvements (between X2 and X3), and we can still debug using PHPStorm ! + +### Dependencies +Whereas iTop dependencies are bundled inside its repository, the tests dependencies are not, and must be added manually. To do so, run `composer install` in the `/tests/php-unit-tests` directory. + +### iTop instance prerequisites to run its test suite +Install iTop with default setup options : - Configuration Management options : everything checked - Service Management for Enterprises - Simple Ticket Management + Customer portal @@ -18,14 +32,119 @@ Install iTop with default setup options : Plus : - Additional ITIL tickets : check Known Errors Management and FAQ +## Create an iTop PHPUnit test -## What about skipped tests ? +### Where should I add my test? +- Covers an iTop PHP class or method? => Most likely in the `unitary-tests` directory +- Covers the consistency of some data through the app? => Most likely in `integration-tests` directory + +### iTop test parent classes +iTop provides PHPUnit TestCase children that provides some helpers and setUp/tearDown overrides : +- `\Combodo\iTop\Test\UnitTest\ItopTestCase` : for the most simple iTop tests +- `\Combodo\iTop\Test\UnitTest\ItopDataTestCase` : to get a started metamodel and have cleanup of CRUD operations on iTop objects (transactions by default) +- `\Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase` : to test a non standard datamodel (available since iTop 2.7.9, 3.0.4, 3.1.0 N°6097) + + +### Naming convention +* to test `MyClass` class then create a `MyClassTest` class +* to test `MyMethod` method, the corresponding test method should be named `testMyMethod` + +Source [PHPUnit Manual – Chapter 2. Writing Tests for PHPUnit](https://docs.phpunit.de/en/9.6/writing-tests-for-phpunit.html#writing-tests-for-phpunit) + +### What about skipped tests ? A test can be marked as skipped by using the `markTestAsSkipped()` PHPUnit method. Please use it only for temporary disabled tests, for example the ones that are pushed before their corresponding fix. -For other cases like non-relevant data provider cases, just mark the test valid with `assertTrue(true)` and `return`. +For other cases like non-relevant data provider cases, just mark the test valid with `assertTrue(true)` and `return`. -## How do I make sure that my tests are efficient? (performences) + + +## Tips: generic PHPUnit + +### Disable a test +```php +$this->markTestSkipped('explanation'); +``` + +### Test an exception +Just before calling the code throwing the exception, call `\PHPUnit\Framework\TestCase::expectException`. You might also use `expectExceptionMessage` and/or `expectExceptionMessageMatches`. + +Example : + +```php + // Try to delete the tag, must complain ! + $this->expectException(DeleteException::class); + $this->expectExceptionMatches('/'.$this->GetKey().'/'); + $oTagData->DBDelete(); +``` + +Warning : when the condition is met the test is finished and following code will be ignored ! + +Another way to do is using try/catch blocks, for example : +```php + $validator = new FormValidator(); + + try { + $validator->validate( + new DateTimeImmutable('2020-01-01'), + new DateTimeImmutable('1999-01-01'), + -3, + '' + ); + $this->fail('FormValidationException was not thrown'); + } catch (AssertionFailedError $e) { + throw $e; // handles the fail() call just above + } catch (FormValidationException $e) { + $this->assertSame( + [ + 'End must be after start', + 'The new id must be greater than 0', + 'Description can not be empty', + ], + $e->getErrors() + ); + } +``` + + + + + +## Tips: iTop tests + +### Load an iTop class which is outside the autoloader +When running a test extending ItopDataTestCase you'll get all of the iTop classes loaded (Composer autoloader + iTop autoloader including installed modules) + +For ItopTestCase files, you may need to load specific iTop classes that aren't part of the Composer autoloader. If so, since N°5608 (introduced in iTop 2.7.9, 3.0.3, 3.1.0-1) you can use : +- ItopTestCase::RequireOnceItopFile +- ItopTestCase::RequireOnceUnitTestFile + +### Add a User context +Use `UserRights::Login()` + + + + +## Test performances + +### Memory limit + +As the tests are run in the same process, memory usage +may become an issue as soon as tests are all executed at once. + +Fix that in the XML configuration in the PHP section +```xml + +``` + + +### Measure the time spent in a test + +Simply cut'n paste the following line at several places within the test function: + +```php +if (isset($fStarted)) {echo 'L'.__LINE__.': '.round(microtime(true) - $fStarted, 3)."\n";} $fStarted = microtime(true); +``` ### Derive from the relevant test class @@ -65,26 +184,10 @@ See also `@beforeClass` and `@afterClass` to handle cleanup. If you can't, then ok you will have to isolate it! -## Tips -### Memory limit - -As the tests are run in the same process, memory usage -may become an issue as soon as tests are all executed at once. - -Fix that in the XML configuration in the PHP section -```xml - -``` -### Measure the time spent in a test - -Simply cut'n paste the following line at several places within the test function: - -```php -if (isset($fStarted)) {echo 'L'.__LINE__.': '.round(microtime(true) - $fStarted, 3)."\n";} $fStarted = microtime(true); -``` +## PHPUnit process isolation ### Understand tests interactions @@ -133,14 +236,14 @@ the exact same effect as `@runTestsInSeparateProcesses`. Note : this option is documented only in the [attributes part of the documentation](https://docs.phpunit.de/en/10.0/attributes.html). ### Traps -#### When it is a matter of stars +#### Doc block comment format : when it is a matter of stars ```php /* * @runTestsInSeparateProcesses ``` This won't work because the comment MUST start with `/**` (two stars) to be considerer by PHPUnit. -#### SetupBeforeClass called more often than expected +#### SetupBeforeClass called more often than expected when running in separate processes `setupBeforeClass` is called once for the class **in a given process**.