Skip to content

File Export

Charlie Hileman edited this page Dec 13, 2018 · 20 revisions

This is a tutorial for using the FileExport PHPUnit extension.

Comparing large data elements

Unit testing can be painful. Building robust tests is time consuming, particularly when looking at large data structures. If you are validating large objects or arrays, this extension may help you. Rather than trying to break them down into small individual assert statements, you can simply make the call:

$this->assertExportCompare($someBigObjectOrArray);

This will compare the var_export of the element against a previously saved export file. These export files should be saved into your repository. They can be easily updated as your project changes. And you can reference the files to find exact output of methods in the future.

Installation

The file export PHPUnit extension is currently forked and awaiting merge into the master. To install now, the following parameters can be added to composer.json:

{
   "repositories": [
       {
           "type": "git",
           "url": "https://github.com/aiqui/phpunit-extensions"
       }
  ]
  "require": {
    	"etsy/phpunit-extensions": "dev-master"
  }
}

Run composer install and be sure to include vendor/autoload.php in your PHP code to get the proper classes.

Initial configuration

To use the file export class, extend the abstract class:

class ExampleTest extends \PHPUnit\Extensions\FileExport\TestCase {
    public function test_getCaseOverview() {
        $this->oSomeObj = new Whatever();
        $this->assertExportCompare($this->oSomeObj->getCaseOverview());
    }
}

Limitation of custom options in phpunit

This extension uses custom command-line options, but PHPUnit will throw an exception when encountering unknown options. So the options for this extension must be added at the end of the call, without any prefix hyphens.

How to save exported elements

There are two command-line options for saving the export files:

  • save-exports - save the export files for one method
  • save-all-exports - save all the export files for all methods

It's recommended that you use save-exports so that you can carefully compare you results, rather than saving all the exports at once via save-all-exports. Assuming your export files are saved in git, running git diff will quickly show you the changes.

The options must be placed after any standard PHPUnit options. For example:

phpunit --filter test_getCaseOverview Example.uut.php save-exports

The files are saved in a subdirectory named phpunit_exports in the same directory where phpunit is run. A subdirectory with name of the class is created inside of phpunit_exports. If the class uses PHP namespaces, the directory includes the namespaces separated by hyphens (to avoid problematic backslashes).

phpunit_exports/TopName-SubName-ExampleTest/test_getCaseOverview.1

The file simply contains the contents of var_export for that given variable. The file name has an index which will increment with each call to assertExportCompare in the same method (i.e. if you compare multiple structures, the same filename will be used, but the number will increment).

When the assert fails

The export assertion takes the output of the variable via var_export, saving it to a temporary file, and comparing the output to the export file via a shell call to diff. When it fails, you would see something like this:

1) TopName\SubName\ExampleTest::test_getCaseOverview
Export file: phpunit_exports/TopName-SubName-ExampleTest/test_getCaseOverview.1
Differences found between exported file and variable
--- Exported file
+++ Actual variable

Typical work flow

1. Build your test methods

Extend your class like the example above, and build your test methods as normal. Whenever you hit a complicated structure, call assertExportCompare. Often there are dynamic elements to the structure that should be unset first, so that you are only comparing parts of the structure that will be the same with every call to phpunit.

2. Save the export files

Save your export files with either save-exports or save-all-exports. To be methodical, you normally would use the --filter option to select the method and save-exports at the end to save the export files.

3. Review and manually validate the exported files

This is the most critical step. Look carefully at the output of the exported file, and review every element. If there are any problems, fix your code and save again.

4. Run phpunit without exporting files

When you run phpunit normally, no export files will be saved, and it will compare against the existing saved export files.

5. Fixing problems

If a difference is found and an exception is thrown, investigate the problem, fix your code and, if necessary, save your exported files again. Always carefully validate after saving.

Export file backups

When saving export files, if a difference is discovered between the old and new exports, the old export files are saved in a backup subdirectory that includes the date and time in the directory name. A message appears notifying the developer of the changes. If no differences are found, the backup export files are removed.

Version control

It's recommended that you save the export files under version control (e.g. git). Typically there is no need to save the backup directories; these should be removed after any changes have been investigated.

Documentation of output

The export files can be used an documentation reference, providing developers with the exact output of a method.

Limitations

Consistently validating complicated objects and arrays is easy with this extension. But that also removes the deliberate exercise of manually composing the assert statements for each value. Furthermore, developers can overlook problems if they are not paying attention to the differences in the export files. This can greatly reduced the time to build unit tests, but developers must diligently review any changed exports.