taisablog.com

taisa's engineer blog

PHPのモッキンフレームワークPhakeの使い方

20161019203514

前回に引き続きPHPUnit関連の記事です。今回はPHPのモッキンフレームワークであるPhakeの使い方を確認します。

Phakeについて

作者は?

Mike Livelyという方のようです。

Phakeって?

こちらですね。

ファイル構成

一応ファイル構成を載せておきます。

.
├── Phake
│   ├── Annotation
│   │   ├── MockInitializer.php
│   │   └── Reader.php
│   ├── CallRecorder
│   │   ├── Call.php
│   │   ├── CallExpectation.php
│   │   ├── CallInfo.php
│   │   ├── IVerificationFailureHandler.php
│   │   ├── IVerifierMode.php
│   │   ├── OrderVerifier.php
│   │   ├── Position.php
│   │   ├── Recorder.php
│   │   ├── Verifier.php
│   │   ├── VerifierMode
│   │   │   ├── AtLeast.php
│   │   │   ├── AtMost.php
│   │   │   ├── Result.php
│   │   │   └── Times.php
│   │   └── VerifierResult.php
│   ├── ClassGenerator
│   │   ├── EvalLoader.php
│   │   ├── FileLoader.php
│   │   ├── ILoader.php
│   │   ├── InvocationHandler
│   │   │   ├── CallRecorder.php
│   │   │   ├── Composite.php
│   │   │   ├── FrozenObjectCheck.php
│   │   │   ├── IInvocationHandler.php
│   │   │   ├── MagicCallRecorder.php
│   │   │   └── StubCaller.php
│   │   └── MockClass.php
│   ├── Client
│   │   ├── Default.php
│   │   ├── IClient.php
│   │   └── PHPUnit.php
│   ├── Exception
│   │   ├── MethodMatcherException.php
│   │   └── VerificationException.php
│   ├── Facade.php
│   ├── IMock.php
│   ├── Matchers
│   │   ├── AbstractChainableArgumentMatcher.php
│   │   ├── AnyParameters.php
│   │   ├── ArgumentCaptor.php
│   │   ├── ChainedArgumentMatcher.php
│   │   ├── EqualsMatcher.php
│   │   ├── Factory.php
│   │   ├── HamcrestMatcherAdapter.php
│   │   ├── IArgumentMatcher.php
│   │   ├── IChainableArgumentMatcher.php
│   │   ├── IMethodMatcher.php
│   │   ├── IgnoreRemainingMatcher.php
│   │   ├── MethodMatcher.php
│   │   ├── PHPUnitConstraintAdapter.php
│   │   ├── ReferenceSetter.php
│   │   └── SingleArgumentMatcher.php
│   ├── Mock
│   │   ├── Freezer.php
│   │   ├── Info.php
│   │   └── InfoRegistry.php
│   ├── PHPUnit
│   │   ├── VerifierResultConstraint.php
│   │   └── VerifierResultConstraintV3d6.php
│   ├── Proxies
│   │   ├── AnswerBinderProxy.php
│   │   ├── AnswerCollectionProxy.php
│   │   ├── CallStubberProxy.php
│   │   ├── CallVerifierProxy.php
│   │   ├── StaticVisibilityProxy.php
│   │   ├── StubberProxy.php
│   │   ├── VerifierProxy.php
│   │   └── VisibilityProxy.php
│   ├── String
│   │   └── Converter.php
│   └── Stubber
│       ├── AnswerBinder.php
│       ├── AnswerCollection.php
│       ├── Answers
│       │   ├── ExceptionAnswer.php
│       │   ├── InvalidAnswerException.php
│       │   ├── LambdaAnswer.php
│       │   ├── NoAnswer.php
│       │   ├── ParentDelegate.php
│       │   ├── ParentDelegateCallback.php
│       │   ├── SelfAnswer.php
│       │   ├── SmartDefaultAnswer.php
│       │   └── StaticAnswer.php
│       ├── IAnswer.php
│       ├── IAnswerBinder.php
│       ├── IAnswerContainer.php
│       ├── SelfBindingAnswerBinder.php
│       └── StubMapper.php
└── Phake.php

PhakeはPhake.phpファサードになっていてstaticに呼び出すのが特徴です。

Phakeの使い方

Phakeの使い方を前回同様以下の例を使って確認します。

例の補足

モック化の確認をしやすいように以下のパターンを用意しています。

  • 自分の中だけで完結する処理をするメソッド
  • クラス内のgetterを呼び出して処理しているメソッド
  • クラス内のsetterを呼び出して処理しているメソッド
  • privateメソッド
  • staticメソッド
class Example {
    private $total = 0;
    private static $static_total = 1;
    public function __construct() {
        $this->total = 1;
        self::$static_total = 2;
    }
    public function plusA() {
        return $this->total += 1;
    }
    public function plusB() {
        $this->total = $this->getTotal();
        return $this->total += 1;
    }
    public function plusC() {
        $this->setTotal(10);
        return $this->total += 1;
    }
    private function plusD() {
        return $this->total += 1;
    }
    public function getTotal() {
        return $this->total;
    }
    public function setTotal($total) {
        $this->total = $total;
    }
    public static function getStaticTotal() {
        return self::$static_total;
    }
}

Phake::mockで全モック

コンストラクタは呼ばれず全てのメソッドは握りつぶされます。その状態でメソッドを呼び出しても結果は取得できません。

/**
 * 全部握りつぶし
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれない
 */
public function test_mock() {
    $example = Phake::mock(Example::class);
    $result = $example->plusA();
    $this->assertNull($result);
    $result = $example->plusB();
    $this->assertNull($result);
    $result = $example->plusC();
    $this->assertNull($result);
}
イメージ
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-15-20-42-52

Phake::mockで振る舞い設定

コンストラクタは呼ばれず全てのメソッドは握りつぶし、plusAにだけ返り値を設定します。

/**
 * 全部握りつぶし
 * plusAだけ振る舞い設定
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれない
 */
public function test_mock_setting() {
    $example = Phake::mock(Example::class);
    Phake::when($example)->plusA()->thenReturn('A');
    $result = $example->plusA();
    $this->assertEquals('A', $result);
}
イメージ
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-15-20-59-27

Phake::partialMockで部分モック

partialMockだけを使った場合

まずpartialMockだけを使った場合、全ての振る舞いは元のままとなります。後述しますがこの場合verifyが使えます。

/**
 * 全部そのまま
 *
 * 引数:クラス(コンストラクタに値を渡す場合は引数追加)
 * コンストラクタ:呼ばれる
 */
public function test_partialMock_non() {
    $example = Phake::partialMock(Example::class);
    $result = $example->plusA();
    $this->assertEquals(2, $result);
    $result = $example->plusB();
    $this->assertEquals(3, $result);
    $result = $example->plusC();
    $this->assertEquals(11, $result);
}
イメージ
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-20-19-38-07

partialMockで振る舞いを設定する場合

partialMockを使った後に以下のように振る舞いを設定します。PHPUnitのモックオブジェクトのcreateMockメソッドとは違いコンストラクタは呼ばれます。

/**
 * 全部そのまま
 * 指定したメソッドのみ振る舞い設定
 *
 * 引数:クラス(コンストラクタに値を渡す場合は引数追加)
 * コンストラクタ:呼ばれる
 */
public function test_partialMock_partial() {
    $example = Phake::partialMock(Example::class);
    Phake::when($example)->getTotal()->thenReturn(3);
    $result = $example->plusA();
    $this->assertEquals(2, $result);
    $result = $example->plusB();
    $this->assertEquals(4, $result);
    $result = $example->plusC();
    $this->assertEquals(11, $result);
}
イメージ
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-20-19-45-49

どのように呼ばれたかを確認する

Phakeの場合どのように呼ばれたかのチェックは通常処理実行後にverifyを使って行います。getterやsetterがどの引数で何回呼ばれたかを確認するには以下のように書きます。

/**
 * 全部そのまま
 * 期待動作確認(1 test, 5 assertions)
 *
 * 引数:クラス(コンストラクタに値を渡す場合は引数追加)
 * コンストラクタ:呼ばれる
 */
public function test_partialMock_setStubMethod() {
    $example = Phake::partialMock(Example::class);
    $result = $example->plusA();
    $this->assertEquals(2, $result);
    $result = $example->plusB();
    $this->assertEquals(3, $result);
    $result = $example->plusC();
    $this->assertEquals(11, $result);
    Phake::verify($example, Phake::times(1))->getTotal();
    Phake::verify($example, Phake::times(1))->setTotal(10);
}

privateメソッドをモック化する

ここまではPHPUnitのモックオブジェクトとそれほど変わりませんが、Phakeを使えばprivateメソッドを簡単に実行したりモック化することができます。

Phake::makeVisibleを使って普通に実行する

Phake::makeVisibleを使うことでprivateメソッドを実行することができます。

/**
 * 全部握りつぶさない
 * privateメソッドを普通に実行
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれる
 */
public function test_partialMock_private() {
    $example = Phake::partialMock(Example::class);
    // 直接は呼び出せないのでmakeVisibleにてプロキシして実行確認可能
    $result = Phake::makeVisible($example)->plusD();
    $this->assertEquals(2, $result);
}

モック化する

以下のように普通にprivateなスタブメソッドに振る舞いを設定することもできます。

/**
 * 全部握りつぶさない
 * privateメソッドの振る舞いを設定
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれる
 */
public function test_partialMock_private2() {
    $example = Phake::partialMock(Example::class);
    // 普通に振る舞い設定
    Phake::when($example)->plusD()->thenReturn(5);
    // 直接は呼び出せないのでmakeVisibleにてプロキシして実行確認可能
    $result = Phake::makeVisible($example)->plusD();
    $this->assertEquals(5, $result);
}

whenStaticを使ってstaticメソッドをモック化する

PhakeならstaticメソッドもPhake::whenStaticを使って簡単にモック化できてしまいます。

/**
 * 全部握りつぶし
 * staticメソッドの振る舞いを設定
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれない
 */
public function test_partialMock_static() {
    // そのまま実行する場合
    $result = Example::getStaticTotal();
    $this->assertEquals(1, $result);
    $example = Phake::partialMock(Example::class);
    // 直接はNG
    $result = $example::getStaticTotal();
    $this->assertNull($result);
    // 振る舞い設定
    Phake::whenStatic($example)->getStaticTotal()->thenReturn(3);
    $result = $example::getStaticTotal();
    $this->assertEquals(3, $result);
}

その他に便利なもの

thenCallParent

以下のようにPhake::mockで全部握りつぶしたとしてもthenCallParentを使えば元の処理を呼び出すことができます。

/**
 * 全部握りつぶし
 * privateメソッドをスタブ化する
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれない
 */
public function test_mock_private2() {
    $example = Phake::mock(Example::class);
    Phake::when($example)->plusD()->thenCallParent();
    // 普通にスタブ化
    Phake::when($example)->plusD()->thenReturn(5);
    // 直接は呼び出せないのでmakeVisibleにてプロキシして実行確認可能
    $result = Phake::makeVisible($example)->plusD();
    $this->assertEquals(5, $result);
}

一括で振る舞いを設定する

モックに個別に振る舞いを設定するのが面倒なときは、Phake::ifUnstubbedを使って一括で設定できてしまいます。

/**
 * 全部スタブ化
 * 振る舞いを一括設定
 *
 * 引数:クラスのみ
 * コンストラクタ:呼ばれない
 */
public function test_mock_setting2() {
    $example = Phake::mock(Example::class, Phake::ifUnstubbed()->thenReturn('A'));
    $result = $example->plusA();
    $this->assertEquals('A', $result);
    $result = $example->plusB();
    $this->assertEquals('A', $result);
    $result = $example->plusC();
    $this->assertEquals('A', $result);
    $result = Phake::makeVisible($example)->plusD();
    $this->assertEquals('A', $result);
}

カバレッジレポート

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-20-20-35-31
%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-20-20-35-05

最後にカバレッジレポートを見てみます。ソースはこちらにあります。


きました、オールグリーン!もちろんモック確認の為のクラスなんでオールグリーンになったところでたいした意味はありませんが、Phakeを使えば簡単にprivateもstaticもモック化できてとにかく楽ちんです。

まとめ

ここまで簡単にモック化できてしまうとPHPのモッキンフレームワークはPhakeで十分ですね。また、期待動作の設定を処理実行後のverifyで行うのも直感的でわかりやすいです。

今回使ったPHPUnitとPhakeのバージョン