3分でCodeIgniter 3.xからPHPUnit 6.0を使う

PHPUnit 6.0がリリースされたわけですが、CodeIgniter 3.xでももちろん使えます。

なお、PHPUnit 6.0を使うにはPHP 7.0以上が必要です。

CodeIgniterのインストール

ComposerからCodeIgniter 3.xの最新版をインストールします。

$ composer create-project kenjis/codeigniter-composer-installer ci31

以下のように、現在だとCodeIgniter 3.1.3がインストールされます。

Installing kenjis/codeigniter-composer-installer (v0.4.6)
  - Installing kenjis/codeigniter-composer-installer (v0.4.6)
    Loading from cache

Created project in ci31
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing codeigniter/framework (3.1.3)
    Loading from cache

kenjis/codeigniter-composer-installer suggests installing kenjis/codeigniter-cli (A command-line tool for CodeIgniter 3.0)
kenjis/codeigniter-composer-installer suggests installing kenjis/ci-phpunit-test (An easier way to use PHPUnit with CodeIgniter 3.0)
kenjis/codeigniter-composer-installer suggests installing kenjis/codeigniter-ss-twig (A Simple and Secure Twig integration for CodeIgniter 3.0)
kenjis/codeigniter-composer-installer suggests installing kenjis/codeigniter-doctrine (A simple Doctrine integration for CodeIgniter 3.0)
kenjis/codeigniter-composer-installer suggests installing kenjis/codeigniter-deployer (A Deployment Tool for CodeIgniter 3.0)
codeigniter/framework suggests installing paragonie/random_compat (Provides better randomness in PHP 5.x)
Writing lock file
Generating autoload files
> Kenjis\CodeIgniter\Installer::postInstall
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing mikey179/vfsstream (v1.1.0)
    Loading from cache

Writing lock file
Generating autoload files
==================================================
`public/.htaccess` was installed. If you don't need it, please remove it.
If you want to install translations for system messages or some third party libraries,
$ cd <codeigniter_project_folder>
$ php bin/install.php
Above command will show help message.
See <https://github.com/kenjis/codeigniter-composer-installer> for details
==================================================

PHPUnit 6.0のインストール

ComposerからPHPUnit 6.0をインストールします。

$ cd ci31/
$ composer require phpunit/phpunit:6.0.*

以下のように、現在だとPHPUnit 6.0.6がインストールされます。

./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing sebastian/version (2.0.1)
    Loading from cache

  - Installing sebastian/resource-operations (1.0.0)
    Loading from cache

  - Installing sebastian/recursion-context (2.0.0)
    Loading from cache

  - Installing sebastian/object-enumerator (2.0.0)
    Loading from cache

  - Installing sebastian/global-state (1.1.1)
    Loading from cache

  - Installing sebastian/exporter (2.0.0)
    Loading from cache

  - Installing sebastian/environment (2.0.0)
    Loading from cache

  - Installing sebastian/diff (1.4.1)
    Loading from cache

  - Installing sebastian/comparator (1.2.4)
    Loading from cache

  - Installing doctrine/instantiator (1.0.5)
    Loading from cache

  - Installing phpunit/php-text-template (1.2.1)
    Loading from cache

  - Installing phpunit/phpunit-mock-objects (4.0.0)
    Loading from cache

  - Installing phpunit/php-timer (1.0.8)
    Loading from cache

  - Installing phpunit/php-file-iterator (1.4.2)
    Loading from cache

  - Installing sebastian/code-unit-reverse-lookup (1.0.0)
    Loading from cache

  - Installing phpunit/php-token-stream (1.4.9)
    Loading from cache

  - Installing phpunit/php-code-coverage (5.0.0)
    Loading from cache

  - Installing webmozart/assert (1.2.0)
    Loading from cache

  - Installing phpdocumentor/reflection-common (1.0)
    Loading from cache

  - Installing phpdocumentor/type-resolver (0.2.1)
    Loading from cache

  - Installing phpdocumentor/reflection-docblock (3.1.1)
    Loading from cache

  - Installing phpspec/prophecy (v1.6.2)
    Loading from cache

  - Installing myclabs/deep-copy (1.6.0)
    Loading from cache

  - Installing phpunit/phpunit (6.0.6)
    Loading from cache

sebastian/global-state suggests installing ext-uopz (*)
phpunit/phpunit suggests installing phpunit/php-invoker (^1.1)
phpunit/phpunit suggests installing ext-xdebug (*)
Writing lock file
Generating autoload files

ci-phpunit-testのインストール

CodeIgniterからPHPUnitを簡単に使うために、ci-phpunit-testをインストールします。なお、PHPUnit 6.0を使うためには、ci-phpunit-test 0.14.0以上が必要です。

$ composer require kenjis/ci-phpunit-test --dev

以下のように、現在だとci-phpunit-test 0.14.0がインストールされます。

Using version ^0.14.0 for kenjis/ci-phpunit-test
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing nikic/php-parser (v3.0.4)
    Downloading: 100%         

  - Installing kenjis/ci-phpunit-test (v0.14.0)
    Downloading: 100%         

Writing lock file
Generating autoload files

実はこれだけではまだインストールが完了しません。以下のコマンドを実行します。

$ php vendor/kenjis/ci-phpunit-test/install.php

これで、ci-phpunit-testがインストールされました。

PHPUnitの実行

$ vendor/bin/phpunit -c application/tests/ --debug

以下のように、ci-phpunit-testに付属しているサンプルのテストが実行されます。

PHPUnit 6.0.6 by Sebastian Bergmann and contributors.

Error:         No code coverage driver is available


Starting test 'Welcome_test::test_index'.
.
Starting test 'Welcome_test::test_method_404'.
.
Starting test 'Welcome_test::test_APPPATH'.
.                                                                 3 / 3 (100%)

Time: 80 ms, Memory: 4.00MB

OK (3 tests, 3 assertions)

関連

Tags: codeigniter, phpunit, testing

GitLab.comのデータベース障害から学ぶ

(最終更新:2017/02/15)

2017/01/31、GitLab.comでデータベース障害が発生し、サービスの停止および約6時間分のデータベースのデータ(projects、 issues、comments、snippetsなど)が消失しました。

なお、Gitリポジトリに障害はなく、リポジトリのコンテンツ(Wikiを含む)にデータ消失はありませんでした。

GitLab.comとは?

GitLab.comはGitLab社が提供しているGitHubのようなGitリポジトリのホスティングサービスです。無料でプライベートリポジトリも使えます。

タイムライン

日時(UTC) 出来事
2017/01/31 23:27 GitLab.com、オペレーションミスにより本番データベースのデータを削除。サービス停止
2017/01/31 23:28 データベースの緊急メンテナンス中である旨をツイート
2017/02/01 00:00 約6時間前のスナップショットをリストアすることを決定
2017/02/01 00:44 Google Docsでのライブの状況報告についてツイート
2017/02/01 11:42 YouTubeでの復旧作業のライブストリームについてツイート
2017/02/01 18:14 GitLab.com復旧
2017/02/10 Postmortem(事後分析)公表

透明性

今回のGitLabのインシデント対応で際立ったことは、迅速な報告と透明性です。

恐ろしく早く状況が詳細にGoogle Docsで報告されていき、ついに復旧作業がYouTubeでライブで流されました。

データバックアップは失敗したのか?

データベースの消失は、複数のインシデント対応中にオペレーションミスが発生し、本番のPostgreSQLデータベースのデータディレクトリが削除されたことによります。

GitLabは事前に以下のバックアップ手段を講じていました。

  1. 24時間ごとのデータベースダンプ(pg_dump)
  2. データベースダンプのS3へのバックアップ
  3. 24時間ごとのAzureのディスクスナップショット
  4. 24時間ごとのLVMスナップショット
  5. PostgreSQLのレプリケーション

しかし、実際には、

5.のレプリケーションはもともとリカバリ目的ではなくフェイルオーバーのためのものであり、厳密にはバックアップとは言えません。また、そもそもレプリケーションを再構築する途中で結果的に今回のインシデントが発生しています。障害発生時にはレプリケーションにはすでにデータはありませんでした。

1.のデータベースダンプは、pg_dumpのバージョンが9.2と古く、実際のデータベースはPostgreSQL 9.6だったため、エラーでダンプが取得できていませんでした。そのため、2.のS3へのバックアップも当然空でした。

3.のAzureのディスクスナップショットはNFSサーバでは取得されていましたが、データベースサーバでは取得されていませんでした。また、仮にスナップショットがあったとしても、ディスクスナップショットから一部のファイル(データベースファイル)をリストアすることは簡単ではありません。

4.のLVMスナップショットは機能していましたが、障害の6時間前に手動で取得された別のスナップショットがありました。そのため、今回は6時間前のスナップショットからリストアされました。

つまり、いろいろとバックアップに不具合はありましたが、それでも最悪24時間前の状態にはLVMスナップショットから戻れたということになります。

仮にすべてのバックアップ手段が正常に機能していたとしても、データベースダンプは最大24時間前のものですし、ディスクスナップショットも最大24時間前です。

このことから、リカバリ要件は最大24時間のデータロスを前提としていたと考えられます。そして、少なくとも24時間前のLVMスナップショットがあったことから、その時点への復旧は可能でした。

結論としては、今回のインシデントに関しては、バックアップは要件を満たして機能していたことになると思います。

重要なのはリカバリ要件

もし、24時間のデータロスが許容できないのであれば、それはリカバリ要件が間違っていたということになります。

また、リカバリ要件には復旧時間もありますので、そちらの要件はたぶん満たせていなかったのでしょう。Postmortemには、復旧に18時間以上かかったことが問題であるとされています。

Postmortem(事後分析)

事前に予告されていた通り、GitLabからPostmortemが02/10に公表されました。

根本原因分析(なぜなぜ分析)やリカバリ手順の改善など、非常に興味深い内容が含まれています。是非、お読み下さい。

データディレクトリが削除された原因は、レプリケーションの再構築手順が自動化されておらず、また、作業のドキュメントもきちんと作成されていなかったからとされています。

バックアップ手順が検証されていなかった原因については、責任者が決まっていなかったことがあげられています。

まとめ

こうして見ると、今回の障害はリカバリ要件の定義やバックアップやリストア手順の設計により根本的な問題があったことがわかります。要するに事前の分析検討や準備計画が不十分だったということです。

  • リカバリ要件が不明か誤ってる
  • バックアップの責任者がいない
  • バックアップからリストアできるか検証されていない
  • バックアップが正常に取得できているかの監視がない
  • レプリケーション再構築の手順書が不十分
  • 複雑な作業を一人で行う運用
  • 間違いやすそうな似たようなサーバ名(db*.cluster.gitlab.com)

障害の直接の原因は、担当者が誤ってデータベースのデータディレクトリをrmしてしまったことですが、それは単なるきっかけにすぎないことがわかります。

参考

GitLabからのデータベース障害に関する報告:

GitLabの緊急対応マニュアル:

PostgreSQLのコミッタからのアドバイス:

この障害に関する記事:

Tags: postgres, gitlab, incident

PHPUnit 6.0の主な変更点

PHPUnit 6.0.0が2017/02/03にリリースされました。その後立て続けにバージョンがあがり、02/08に6.0.6がリリースされています。

主な変更点

要件

PHP 7.0以上が必要になりました。

名前空間

PHPUnit_Framework_TestCasePHPUnit\Framework\TestCaseに変更されました。

もし、既存のテストコードを変更できない場合は、bootstrapに以下のハックを追加することで対応できます。

if (! class_exists('PHPUnit_Framework_TestCase')) {
    class_alias('PHPUnit\Framework\TestCase', 'PHPUnit_Framework_TestCase');
}

デフォルト設定

デフォルト設定が変更されました。

  • backupGlobals="true"でしたが、backupGlobals="false"に変更されました
  • 役に立たないテストがRiskyになります

既存のテストコードをそのまま動作させたい場合は、phpunit.xmlbackupGlobals="true"を設定してください。

ただし、backupGlobals="false"の方がテストの実行が速くなりますので、falseでテストが通るようにした方が好ましいと思います。

デフォルトはfalseにして、必要なテストケースだけ@backupGlobals enabledにすることも可能です。

役に立たないテストは修正するか削除すべきでしょう。

ログ

  • --log-junitフォーマット変更
  • --log-json廃止

テストランナー

すべてのテストがパスするが警告がある場合、exitコードが1に変更されました。

廃止

  • $this->getMock()
  • $this->setExpectedException()および$this->setExpectedExceptionRegExp()
    • $this->expectException(), $this->expectExceptionCode(), $this->expectExceptionMessage(), $this->expectExceptionMessageRegExp() を使います
  • --tap

その他

  • PHP 5.3以上が要件のPHPUnit 4.8の保守が終了しました
  • PHP 5.6以上が要件のPHPUnit 5.7は2018/02/02まで保守されます

参考

Tags: phpunit