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**.