Posted at

PHPUnit の TestCase のメンバはテストが完了するまで解放されない

More than 5 years have passed since last update.

PHPUnit のドキュメントには、

http://phpunit.de/manual/3.7/ja/fixtures.html#fixtures.more-setup-than-teardown


setUp() と tearDown() は理屈上では対称的になるはずですが、実際にはそうではありません。

実際には、 tearDown() を実装する必要があるのは setUp() で外部リソース (ファイルやソケットなど) を割り当てた場合のみです。

もし setUp() で単に PHP オブジェクトを作成しただけの場合は、 一般には tearDown() は必要ありません。

しかし、もし setUp() で大量のオブジェクトを作成した場合には、 それらの後始末をするために tearDown() で変数を unset() したくなることもあるでしょう。

テストケースオブジェクト自体のガベージコレクションにはあまり意味がありません。


とありますが、次のようなテストを、


PHPUnitTestCaseMemberTest.php

<?php

class PHPUnitTestCaseMemberTest extends \PHPUnit_Framework_TestCase
{
private $val;

protected function setUp()
{
$this->val = str_repeat("x", 1024*1024);
}

/**
* @dataProvider data
*/

function test()
{
$this->assertTrue(true);
}

function data()
{
return array_fill(0, 1000, []);
}
}


次のように実行するとエラーになります。

$ phpunit -d memory_limit=128M PhpUnitMemberTest.php

PHPUnit 3.7.27 by Sebastian Bergmann.

............................................................. 61 / 1000 ( 6%)
......................................
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 1048577 bytes) in /home/ng/work/sandbox/php/PhpUnitMemberTest.php on line 8

次のように修正するとエラーになりません。


PHPUnitTestCaseMemberTest.php

<?php

class PHPUnitTestCaseMemberTest extends \PHPUnit_Framework_TestCase
{
private $val;

protected function setUp()
{
$this->val = str_repeat("x", 1024*1024);
}

protected function tearDown()
{
$this->val = null;
}

/**
* @dataProvider data
*/

function test()
{
$this->assertTrue(true);
}

function data()
{
return array_fill(0, 1000, []);
}
}


$ phpunit -d memory_limit=128M PhpUnitMemberTest.php

PHPUnit 3.7.27 by Sebastian Bergmann.

............................................................. 61 / 1000 ( 6%)
............................................................. 122 / 1000 ( 12%)
............................................................. 183 / 1000 ( 18%)
............................................................. 244 / 1000 ( 24%)
............................................................. 305 / 1000 ( 30%)
............................................................. 366 / 1000 ( 36%)
............................................................. 427 / 1000 ( 42%)
............................................................. 488 / 1000 ( 48%)
............................................................. 549 / 1000 ( 54%)
............................................................. 610 / 1000 ( 61%)
............................................................. 671 / 1000 ( 67%)
............................................................. 732 / 1000 ( 73%)
............................................................. 793 / 1000 ( 79%)
............................................................. 854 / 1000 ( 85%)
............................................................. 915 / 1000 ( 91%)
............................................................. 976 / 1000 ( 97%)
........................

Time: 738 ms, Memory: 5.00Mb

OK (1000 tests, 1000 assertions)

TestCase クラスはテストの開始前にテストメソッドの数だけインスタンス化され(↑のように @dataProvider を使っている場合はさらにデータごと)、テストがすべて終わるまで解放されません(TestSuite が TestCase を握りっぱなしにするため)。

そのため、TestCase のメンバにオブジェクトや変数を保持させるとそれらはテストが完了するまで解放されません。


phpunit に --process-isolation オプションを付ければテスト毎に別プロセスになるため、最初の例(tearDown が無い版)でも大丈夫です。

$ phpunit -d memory_limit=128M --process-isolation PhpUnitMemberTest.php

PHPUnit 3.7.27 by Sebastian Bergmann.

............................................................. 61 / 1000 ( 6%)
............................................................. 122 / 1000 ( 12%)
............................................................. 183 / 1000 ( 18%)
............................................................. 244 / 1000 ( 24%)
............................................................. 305 / 1000 ( 30%)
............................................................. 366 / 1000 ( 36%)
............................................................. 427 / 1000 ( 42%)
............................................................. 488 / 1000 ( 48%)
............................................................. 549 / 1000 ( 54%)
............................................................. 610 / 1000 ( 61%)
............................................................. 671 / 1000 ( 67%)
............................................................. 732 / 1000 ( 73%)
............................................................. 793 / 1000 ( 79%)
............................................................. 854 / 1000 ( 85%)
............................................................. 915 / 1000 ( 91%)
............................................................. 976 / 1000 ( 97%)
........................

Time: 32.51 seconds, Memory: 4.75Mb

OK (1000 tests, 1000 assertions)

その代わりテストにものすごく時間がかかるようになりますが。


TestCase に巨大なオブジェクトや変数を持たせることはあまりないと思いますが、Zend Framework のテストケース(zf1 の Zend_Test_PHPUnit_ControllerTestCase や zf2 の Zend\Test\PHPUnit\Controller\AbstractControllerTestCase )は、いろいろなオブジェクトをテストケースのメンバに持たせているので、注意が必要かもしれません。