Tsuyoshin blog

所属団体とは関係なく、個人的なblog

phpunitの`@runInSeparateProcess`,`@preserveGlobalState`利用時の注意点

概要

phpでアプリケーションを書いていて、当然unit testも書いています。

unit testを書いている中でmockが必要なパターンが出てくると思います。

その際にとても有名なmockeryを使っています

github.com

んで、mockeryを色々と使ってると@runInSeparateProcess,@preserveGlobalStateが必要になることが多くあります

@runInSeparateProcess

https://phpunit.de/manual/current/ja/appendixes.annotations.html#appendixes.annotations.runInSeparateProcess

@preserveGlobalState

https://phpunit.de/manual/current/ja/appendixes.annotations.html#appendixes.annotations.preserveGlobalState

正確な情報は上記のリンク先にお任せして、要は別のプロセスにしてグローバル参照させない設定という感じですね。

ぶち当たった課題

別プロセスで稼働するということはtestの中でsetUpBeforeClass,tearDownAfterClassを設定していた時に予期せぬタイミングで実行されるという...

具体的に

echoを仕込んで実際に動かしながら見てみる

テスト対象のクラス

a.php

<?php
namespace StaticMethods;

class StaticMethods
{
    public static function func()
    {
        return "this is static method. \n";
    }
}

テストコード test.php

<?php

namespace Tests;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use StaticMethods\StaticMethods;


/**
 * @group Test
 */
class Test extends TestCase
{

    public static function setUpBeforeClass()
    {
        parent::setUpBeforeClass();
        echo 'setUpBeforeClass'."\n";
    }

    public static function tearDownAfterClass()
    {
        parent::tearDownAfterClass();
        echo 'tearDownAfterClass'."\n";

    }
    /**
     * @runInSeparateProcess
     * @preserveGlobalState disabled
     */
    public function test_a()
    {
        $mock = m::mock('overload:' . StaticMethods::class);
        $mock->shouldReceive('func')->andReturn('is this static method ?');
        echo(StaticMethods::func()."\n");
    }
    
    public function test_b()
    {
        echo(StaticMethods::func()."\n");
    }
}

本体予想していたechoの期待値は

  1. setUpBeforeClass ->
  2. is this static method ? -> test_aのecho
  3. this is static method. -> test_bのecho
  4. tearDownAfterClass ->

実際のechoの期待値は

  1. setUpBeforeClass -> test_aの設定の別プロセス or もともとの setUpBeforeClass
  2. setUpBeforeClass -> test_aの設定の別プロセス or もともとの setUpBeforeClass
  3. is this static method ? -> test_aのecho
  4. tearDownAfterClass -> test_aの設定の別プロセス or もともとの tearDownAfterClass
  5. this is static method. -> test_bのecho
  6. tearDownAfterClass -> test_aの設定の別プロセス or もともとの tearDownAfterClass

この結果何が問題になるか

tearDownAfterClassとはテストケースの最後に実行されるメソッドです。それが、まだテストが実行中にも関わらず実行されてしまう...😨

この現象でちょっとハマりました

回避方法

何通りかあると思う

  1. そもそも影響しないようにrunInSeparateProcessの設定があるテストは別に切り出す
  2. tearDownAfterClassを諦めてtearDownを使う
    • テストケースが多い場合は遅くなる可能性あるけど致し方なし
  3. 順番を考慮する
    • @runInSeparateProcessの付いているテストを最後に持ってくる。そーすると下記のようになる
      1. setUpBeforeClass -> もともとのプロセスのsetUpBeforeClass
      2. this is static method. -> test_bのecho
      3. setUpBeforeClass -> test_aの設定の別プロセスのsetUpBeforeClass
      4. is this static method ? -> test_aのecho
      5. tearDownAfterClass -> test_aの設定の別プロセス or もともとの tearDownAfterClass
      6. tearDownAfterClass -> test_aの設定の別プロセス or もともとの tearDownAfterClass

最後

微妙にハマり、半日は浪費してしまった気がする...😰

プロセスが並列に動いて処理をしているので(プロセス同士は互いに考慮せずに稼働する)、タイミングというか、ケースによっては必ずこの上記のような順序になるとは限らない...😓

副作用は色々あるなぁーと実感しました