您将如何测试使用全局变量的PHP代码?

Suppose the class Account (in account.php file) which uses a $DB variable to store data in the database. The $DB variable is initialized in another globals.php file.


/**** globals.php ****/

$DB = PDO (....);

/**** account.php ****/

public function create($data) {
global $DB;
....
}

Now, suppose that you want to test a function in account class called create using PHPUnit test. How would you initialize the $DB object?

Ideally, you should not use globals you are hiding dependencies and can cause things to happen due to changes in the global variable.

It would be possible for your to hack in and replace your variable so that you can mock it. You can use the setUp method to store what is in the global $DB and then restore it in the teardown() method. This way, you don't accidentally break some other test with your mock.

public function setUp() {
    global $DB;
    $this->oldDB = $DB;
}

public function testCreate() {
    $mockDB = $this->getMock('PDO') ... //More steps to complete mock object

    global $DB;
    $DB = $mockDB;

    //Rest of Test here
}

public function teardown() {
    global $DB;
    $DB = $this->oldDB;
}

Now just because you CAN do this, doesn't mean you SHOULD do this. It would be better to refactor your code so that it doesn't depend upon the global scope. But if that isn't an option at this time, here is a work around that will at least make the test usable until you refactor.

How would you initialize the $DB object?

Just initialize it as usual:

class TestCase extends PHPUnit_Framework_TestCase
{
    function testDatabaseConnection()
    {
        require 'path/to/globals.php'; // take "global" variable in local scope
        $GLOBALS['DB'] = $DB; // make it globally available

        $account = new account();
        $this->assertInstanceOf('account', $account);

        $this->assertTrue($account->create("data"));

        ...
    }
}

Then start with testing and get a feeling how the code behaves when you test it thoroughly.

You can then consider to initialize the database connection before the test-class is created once, so that you can write the test-routines faster.

Later then you probably write yourself a helper method to instantiate the account class.

That perhaps is then also the time where you realize that injecting the database connection object into the constructor might not be that bad as well.