9

I am learning and exploring applications of PHPUnit with PHP 5.2.9 and have run into the globals issue. I have set $backupGlobals to FALSE, included the doc '@backupGlobals disabled' and this doesn't seem to affect the behaviour of PHPUnit's backing up of the globals. Is there something I'm missing? Do I need to alter PHPUnit's xml file? Create a bootstrap?

config.php:

$testString = 'Hello world!';

basicApp.php:

require ('D:\data\clients\security.ca\web_sites\QRASystems.com\wwwroot\__tests\BasicApp\config.php');

class BasicApp {

public $test;

public function __construct() {
    global $testString;
    $this->test = $testString;
}

public function getTest() {
    return $this->test;
}

public function setTest($test){
    $this->test = $test;
}

BasicAppTest.php:

require ('D:\data\clients\security.ca\web_sites\QRASystems.com\wwwroot\__tests\BasicApp\BasicApp.php');

class BasicAppTest extends PHPUnit_Framework_TestCase{
    protected $testClass;
    protected $backupGlobals = FALSE;
    protected $backupGlobalsBlacklist = array('testString');

    public function SetUp(){
        $this->testClass = new BasicApp;
        $this->testClass->bootstrap();
    }

    public function testGlobal(){
        echo $this->testClass->getTest();
        $this->assertNotNull($this->backupGlobals);
        $this->assertFalse($this->backupGlobals);
        $this->assertNotEmpty($this->testClass->test);
    }

    public function testMethods(){
        $this->testClass->setTest('Goodbye World!');
        echo $this->testClass->getTest();
        $this->assertNotNull($this->backupGlobals);
        $this->assertNotNull($this->testClass->test);
        if (empty($this->testClass->test)) echo 'Method set failed!';
    }
}

testGlobal() fails on $this->assertNotEmpty($this->testClass->test), indicating that $this->backupGlobals is set to FALSE and that globals are still being back up by PHPUnit.

EDIT: I got this working by making the following changes-

BasicAppTest.php:

    protected $backupGlobals = FALSE; <- REMOVED
    protected $backupGlobalsBlacklist = array('testString');  <- REMOVED

config.php:

global $testString; <- ADDED
$testString = 'Hello world!';

I am dumbfounded that this hasn't been covered before somewhere!

Malovich
  • 891
  • 5
  • 14
  • 1
    Is the line `global $testString;` needed? My understanding is that the `global` keyword at the global scope has no effect because you're already in the global scope. – David Harkness Mar 15 '12 at 03:51
  • 1
    @DavidHarkness You have no way of knowing when you are writing the code whether you are in the global scope or not. Just because you are in the main scope of the file and not inside a function doesn't mean that you will be in global scope at runtime. If that file is `include`d within a function, you will actually be in the scope of that function, even in the main scope of the file. For this reason it is generally considered best practice to always declare a variable you want to be global as such with the `global` keyword. That way you know it will be global no matter what. – J.D. Jan 19 '16 at 15:26

2 Answers2

10

In your test case you are defining a new $backupGlobals property that PHPUnit won't see. Since the property is protected, you could set it to false in the constructor, but PHPUnit uses its constructors to pass information on how to run the test method. Instead, create a phpunit.xml configuration file to set the backupGlobals property to false.

<phpunit backupGlobals="false">
    <testsuites>
        <testsuite name="Test">
            <directory>.</directory>
        </testsuite>
    </testsuites>
</phpunit>
David Harkness
  • 33,903
  • 10
  • 107
  • 127
  • Thank you very very much. I've banged my head against this for the better part of last week looking for the missing piece. I assumed that protected properties could be over-written by sub-classes! Alright, quick follow-up: where does the .xml file go? – Malovich Mar 12 '12 at 21:06
  • I put it in the root of my `tests` folder, but it can go anywhere. You can point `phpunit` to it using the `--configuration` parameter (I think `-c` was added as a shortcut recently). If it's in the current directory and named `phpunit.xml`, PHPUnit will pick it up automatically. – David Harkness Mar 12 '12 at 22:00
  • 3
    ...somehow, this slipped the net. If you're going to use global variables with PHPUnit, you need to both declare the variable global in the global space (and assign the value to it) and declare its inclusion where it is needed locally. The default behaviour with PHP is that variables initialized in the global space are made global. PHPUnit changes this default! – Malovich Mar 13 '12 at 14:37
  • @Malovich - I'm not sure I follow. With PHP (and I don't see how PHPUnit could change this), you can *read* global variables in a function without declaring them as such, but you must declare them using `global` if you want to write to them. – David Harkness Mar 13 '12 at 17:09
  • As you say,the global keyword normally is used only in a local scope (be it a function, method or namespace) to make a variable initialized in the global namespace available within that local scope. PHP's default behaviour with any variable initialized in the global scope is to make it available as a global variable. – Malovich Mar 13 '12 at 22:23
  • When PHPUnit backs up the global variables, somehow, it doesn't include variables initialized in this way and only backs up variables declared global in the global namespace with the global keyword. I've proven this with the code above on Windows XP and a server running PHP v5.2.9. I also haven't found this odd behaviour mentioned anywhere in quite this way. – Malovich Mar 13 '12 at 22:23
  • @Malovich - Oh, I see what you mean. I suspect that PHPUnit builds a list of all global variables upon startup and doesn't change it as the tests run. Thus, global variables created by tests will never be affected because they don't exist when PHPUnit builds its list. Is this what you're seeing? – David Harkness Mar 15 '12 at 03:49
  • That would be a good reason as to why PHPUnit behaves the way I have observed, yes. – Malovich Mar 16 '12 at 16:50
2

In your edits and comments you've pointed out one workaround for the problem (explicitly declaring globals within the tested application). In onlab's comment to a PHPUnit issue he explains the behavior: when including a file in a function, PHP puts globals from the included files in the function's scope. PHPUnit loads files in a function, and though it tries to extract globals, it fails in the cases I've tried.

Unfortunately, I haven't been able to reproduce the problems of my legacy system in minimal test cases (and I had trouble understanding yours), and so I can't really confirm the explanation. But his suggested workaround helped me: supply a bootstrap file using the --bootstrap option; in it, declare every global used by the tested parts of your application. This avoids the need to modify the application in order to test it. Here's onlab's example from GitHub:

phpunit --bootstrap bootstrap.php test-path

with bootstrap.php:

global $my, $system, $globals, $here;
require_once("/path/to/my/system/bootstrap.php");
  • 1
    Both in the link and in your copy&paste there is a small typo. It should read: `global $my, $system, $globals, $here;`, i.e. without the trailing 's'. – Ognyan Sep 19 '15 at 10:11