Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active August 13, 2018 07:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yano3nora/3fb964ceeb88b998bcea9d18420b7322 to your computer and use it in GitHub Desktop.
Save yano3nora/3fb964ceeb88b998bcea9d18420b7322 to your computer and use it in GitHub Desktop.
[php: phpunit] PHPUnit - Unit test tool for PHP. #php #phpunit #test

OVERVIEW

あまりにも有名な PHP のユニットテストツール。CLI で叩いて実行、カバレッジの HTML 出力とかもやってくれるやつ。単体でも利用できるが composer でインストールして CakePHP など FW とセットで利用されるケースや Phing のような PHP ビルドツールとセットで利用するケースも多い。

Refs

Remarks - テストのコツとか考え方とか

全クラス全メソッド全条件分岐

  • 単体テストは理想論として 全てのクラスの全ての public メソッドの全ての分岐 を埋めるように作成したい
  • 同じメソッドの違う振る舞いをテストする場合はテストメソッドを分ける
  • private メソッドの複雑なテストをするのは議論が分かれるところらしい
    • テストが必要な private メソッドってそもそも不自然なのかな
    • やりたい場合は PHP の ReflectionClass を使って解決する

コードカバレッジを埋めていく

  • まずはコードカバレッジを出力しアプリケーションのクラス、メソッド数と テスト網羅率 をチェック
  • テストで埋めるべきモデル層やプレゼンテーションの層コンポーネント系から埋めていくイメージ

依存するサブシステム&コンポーネントはモック化する

DB 状態は Fixture で管理する

  • 各テストメソッド毎に setUp() tearDown() が走るため DB は常に Fixture の状態になる
  • テストデータを使いまわしたい場合は以下頑張る
    • Fixture を適切に用意する
    • プロパティにデータをセットしておく
    • setUp() などで毎回登録を走らせる

要らないテストは省略

100 % を目指さない

メソッド内で定数に依存しない

  • 定数は当然のことながら 単体テスト時に動的に変更できない ので Testable でなくなる
    • PHP の Runkit 拡張で定数を再定義したり、定数ファイルをテスト用に分けたりしなきゃいけなくなる
  • 引数のデフォルト値として定数をセットして メソッド自体は定数に依存しない設計にすべき

HOW TO USE?

TestCase

PHPUnit 用のテストの書き方

use PHPUnit\Framework\TestCase;

class StackTest extends TestCase
{

    /**
     * Test: Push and pop by array.
     * @test
     */
    // @test を省略する場合メソッド名は test* にする必要あり
    public function pushAndPop()  
    {
        $stack = [];
        $this->assertSame(0, count($stack));

        array_push($stack, 'foo');
        $this->assertSame('foo', $stack[count($stack)-1]);
        $this->assertSame(1, count($stack));
        
        // var_dump() とかしたくなったやつを assertion で片づけていく
        $this->assertSame('foo', array_pop($stack));
        $this->assertSame(0, count($stack));
    }
}

Execution

コマンドラインのテストランナー

基本的に設定ファイル phpunit.xml とセットで利用する。CakePHP なんかを Composer でインストールした場合は、この設定ファイルのひな型 phpunit.xml.dist 生成や composer.json の scripts の設定までしてくれるので何も考えなくていい。

$ phpunit 

# Options
# --bootstrap <file>      実行前に参照する require などが書かれた PHP ファイル
# --configuration <file>  省略時は現 DIR の phpunit.xml ( .dist ) を参照
# --colors=<flag>         色の出力 ( flag は never, auto, always )
# --verbose               詳細情報の出力
# --debug                 デバッグ情報の出力
# --coverage-html <dir>   コードカバレッジを HTML 形式で dir へ出力
# --filter <pattern>      正規表現ライクなパターンで実行するテストをフィルタリング

Composer scripts 経由で叩く

$ composer test

# composer.json は例えばこんな風に
#
# "scripts": {
#   "test": "vendor/bin/phpunit -v --debug --coverage-html tests/coverage"
# }

実行結果

出力 テスト結果
. テスト成功
F テスト失敗
E 危険マーク
S スキップ
I 未実装

Assertions

アサーション - phpunit.readthedocs.io

// mixed is ...
$this->assertNull($var);                // $varがNULLである
$this->assertEquals($val1, $val2);      // $val1が$val2と等しい
$this->assertSame($val1, $val2);        // $val1と$val2が型も含めて等しい
$this->assertInternalType($type, $val); // $valの型名が$typeである - @link https://goo.gl/X71MJ2

// number is ...
$this->assertGreaterThan($expect, $var);         // $expect < $var が成立する
$this->assertGreaterThanOrEqual($expect, $var);  // $expect <= $var が成立する
$this->assertLessThan($expect, $var);            // $expect > $var が成立する
$this->assertLessThanOrEqual($expect, $var);     // $expect >= $var が成立する

// string is ...
$this->assertJsonStringEqualsJsonString($str1, $str2);  // $str1と$str2がjsonとして等しい
$this->assertRegExp($ptn, $str);                        // $strが正規表現$ptnにマッチする

// boolean is ...
$this->assertTrue($var);  // $varがTRUEである
$this->assertFalse($var); // $varがFALSEである

// array is ...
$this->assertArrayHasKey($key, $array);   // 配列$arrayにキー$keyが存在する
$this->assertContains($val, $array);      // 配列$arrayに値$valが存在する
$this->assertContainsOnly($type, $array); // 配列$arrayの値の型がすべて$typeである
$this->assertCount($count, $array);       // 配列$arrayの値の数が$countである
$this->assertEmpty($array);               // 配列$arrayが空である

// object/class is ...
$this->assertObjectHasAttribute($attr, $object);      // オブジェクト$objectにプロパティ変数$attrが存在する
$this->assertClassHasAttribute($attr, $class);        // クラス名$classにプロパティ変数$attrが存在する
$this->assertClassHasStaticAttribute($attr, $class);  // クラス名$classに静的プロパティ変数$attrが存在する
$this->assertInstanceOf($class, $instance);           // $instanceがクラス名$classのインスタンスである

// file is ...
$this->assertFileExists($file);                        // $fileが存在する
$this->assertFileEquals($file1, $file2);               // $file1と$file2の内容が等しい
$this->assertJsonFileEqualsJsonFile($file1, $file2);   // $file1と$file2の内容がjsonとして等しい
$this->assertJsonStringEqualsJsonFile($file1, $json);  // $file1の内容と$jsonがjsonとして等しい

phpunit.xml のサンプル ( CakePHP3 )

<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    colors="true"
    processIsolation="false"
    stopOnFailure="false"
    syntaxCheck="false"
    bootstrap="./tests/bootstrap.php"
    >
    <php>
        <ini name="memory_limit" value="-1"/>
        <ini name="apc.enable_cli" value="1"/>
    </php>

    <!-- Add any additional test suites you want to run here -->
    <testsuites>
        <testsuite name="app">
            <directory>./tests/TestCase</directory>
        </testsuite>
        <!-- Add plugin test suites here. -->
    </testsuites>

    <!-- Setup a listener for fixtures -->
    <listeners>
        <listener
        class="\Cake\TestSuite\Fixture\FixtureInjector"
        file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
            <arguments>
                <object class="\Cake\TestSuite\Fixture\FixtureManager" />
            </arguments>
        </listener>
    </listeners>

    <!-- Ignore vendor tests in code coverage reports -->
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
            <directory suffix=".php">./plugins/*/src/</directory>
        </whitelist>
    </filter>
</phpunit>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment