PHPUnit loads all classes at once. Causes PHP Fata

2019-02-14 11:12发布

问题:

I've done my due diligence, but I don't think any of the questions so far have touched on this problem.

I have a situation where my PHP code generates class definitions based on config properties.

The class definitions are basically data holders and can have either:

  • public properties
  • or protected properties with a public interface that supplies getter/setters.

In certain config cases, the generated CLASS NAMES WILL BE THE SAME but their FILE NAMES WILL BE DIFFERENT. In a real environment, the user just generates a single set of class definitions based on the config they require and uses the classes in their app. No problems there, even if the user generates multiple sets of class definitions. PHP will happily churn along, providing only one set of definitions is referenced in the app.

I have two PHPUnit test cases where I do basically the same tests using the classes with the different definitions generated by either config. I use "require_once" in all my code and do the same in unit tests, "require_once"ing only the class files that I need for that test.

PHPUnit is configured to run all tests that it finds under my test directory and was running as normal. When I added tests for the different class generation, PHPUnit stopped working with a PHP Fatal error: Cannot redeclare class.

Now, the default behaviour of PHPUnit seems to be to load ALL the classes that ALL the tests need to run in one go. This causes the fatal PHP error even before any test are run because PHPUnit picks up a second definition for the generated classes (they have the same class name but different file names) even though I have only "require_once"ed the correct class definitions in each test file.

I can still run tests, but only with a single test file at a time, so currently I have to run all my tests either manually or by a batch file. A full code coverage report across all tests is not possible.

The behaviour I would like is for PHPUnit to only load the classes that have been "require_once"ed per test file, not for all the test files it sees as being run from the phpunit.xml config. Or a better way would be for PHPUnit to only load its own classes for all tests specified and leave PHP itself to do the users "require_once"ing for each test.

Is there any way to get around this? I have looked at PHPUnit config settings but none seem to fit the particular problem. I am not really familiar with PHPUnit under the covers so building another testsuite loader would be difficult.

It's probably best to do a simple example. Here are a couple of PHPUnit test files foo1Test.php and foo2test.php. The only difference between them is they require_once a different class file which has a definition of the Foo class. It doesn't matter that all these classes are empty, they are all perfectly legitimate and it demonstrates the problem well. I just put all these files in a 'testfoo' directory and ran PHPunit on them. You can do it too and you will get the same result.

foo1Test.php

<?php

require_once dirname ( __FILE__ ).'/Foo1.php';

class Foo1TestCase extends PHPUnit_Framework_TestCase
{
}

?>

foo2Test.php

<?php

require_once dirname ( __FILE__ ).'/Foo2.php';

class Foo2TestCase extends PHPUnit_Framework_TestCase
{
}

?>

Foo1.php

<?php

class Foo
{
}

?>

Foo2.php

<?php

class Foo
{
}

?>

The two test files foo1Test.php and foo2Test.php are perfectly valid files and run through the following PHPunit command lines

phpunit foo1Test.php
phpunit foo2Test.php

As you would expect, PHPUnit fails complaining about no tests which is fine.

I also have a standard phpunit.xml config file which will run all tests under the current directory.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupStaticAttributes="false"
         cacheTokens="false"
         colors="false"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         forceCoversAnnotation="false"
         mapTestClassNameToCoveredClassName="false"
         printerClass="PHPUnit_TextUI_ResultPrinter"
         processIsolation="false"
         stopOnError="false"
         stopOnFailure="false"
         stopOnIncomplete="false"
         stopOnSkipped="false"
         testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader"
         strict="false"
         verbose="true">
    <testsuite name="fooTests">
        <directory>.</directory>
    </testsuite>
</phpunit>

I ran PHPUnit with this config as follows

phpunit

The output is:

PHP Fatal error:  Cannot redeclare class Foo in /var/www/testfoo/Foo2.php on line 4
PHP Stack trace:
PHP   1. {main}() /usr/bin/phpunit:0
PHP   2. PHPUnit_TextUI_Command::main() /usr/bin/phpunit:46
PHP   3. PHPUnit_TextUI_Command->run() /usr/share/php/PHPUnit/TextUI/Command.php:130
PHP   4. PHPUnit_TextUI_Command->handleArguments() /usr/share/php/PHPUnit/TextUI/Command.php:139
PHP   5. PHPUnit_Util_Configuration->getTestSuiteConfiguration() /usr/share/php/PHPUnit/TextUI/Command.php:671
PHP   6. PHPUnit_Util_Configuration->getTestSuite() /usr/share/php/PHPUnit/Util/Configuration.php:768
PHP   7. PHPUnit_Framework_TestSuite->addTestFiles() /usr/share/php/PHPUnit/Util/Configuration.php:848
PHP   8. PHPUnit_Framework_TestSuite->addTestFile() /usr/share/php/PHPUnit/Framework/TestSuite.php:419
PHP   9. PHPUnit_Util_Fileloader::checkAndLoad() /usr/share/php/PHPUnit/Framework/TestSuite.php:358
PHP  10. PHPUnit_Util_Fileloader::load() /usr/share/php/PHPUnit/Util/Fileloader.php:79
PHP  11. include_once() /usr/share/php/PHPUnit/Util/Fileloader.php:95
PHP  12. require_once() /var/www/testfoo/foo2Test.php:3

That is what this question was about. Although the test files are perfectly valid in isolation, PHPUnit wont run the tests under a testsuite because it has tried to load all the classes it needs for the whole testsuite BEFORE it runs any tests.

To my way of thinking PHPUnit is doing the wrong thing here by picking up dependencies that it shouldn't for a particular test.