2014.11.20

GitLabとJenkinsでレガシーコードに向き合う

こんにちは。ゲーム開発支援をしている F.S です。
今回は既存のサーバサイドPHPプロジェクトにCIを導入するお話をします。

legacy_code_git
支援しているゲーム開発プロジェクトにおいて、既存のPHP製ゲームサーバ(Web API)のソースを流用して開発するものがありました。
オレオレPHPではなく、一般的なフレームワークが使用されていたのは幸いでしたが、残念なことに、Symfonyの1系という2014年現在ではレガシーなフレームワークでした。アプリケーションのテストコードは存在しません。
少なくとも Symfony2系にはしたかったのですが、Symfony1系と2系では互換性がないのと、コスト・スケジュール的な事情もあって、マイグレーションはあきらめざるを得ませんでした。

そんな状況でしたが、なんとかCIを回せるプロジェクトにしたいという思いがあり、(自身でSymfonyを使うのはバージョン問わず初めてでしたが)Symfonyでできることを調べて開発環境を整備していきました。

既存のプロジェクトを引き継いだ現場などで参考になれば幸いです。

なお、当該プロジェクトでは

  • 構成管理に GitLab
  • CIツールに Jenkins
を使用しています。

プロジェクトに導入したものをポイントで解説します。

コーディング規約

フレームワークに合わせて Symfony1規約、と言いたいところですが、「インデントが空白2文字」など他のメジャーな規約と比較してしっくりこないところがあったので、標準的なPSR-2を採用しました。CodeSnifferによるチェックはSymfonyフレームワーク部分は対象外にしています。
(Symfony2規約では見直されているようでした)

現場ではPhpStormやEclipseを使用しており、IDEでスタイルの設定、変換を行っています。

ブランチ戦略

冗長的との議論がありますが、ひとまずGit-flowを採用しました。
CIは、まだdevelopブランチのみの適用です。
Gitlab Hook Pluginでdevelopブランチへのマージでビルドが走るようにしています。
なお、developブランチへのマージリクエストに対して、コードレビューを実施しています。

テスト

Web APIのエンドポイント単位の機能テスト作成に取りかかりました。
Symfonyのテストフレームワークで言うfunctionalテストです。
(参考)第15章 – ユニットテストと機能テスト
(参考)Symfonyタスク

test/functionl/[your_app_name] ディレクトリ以下にテストコードを作成して、下記のようにtestタスクを実行します。
$ php symfony test:functional your_app_name
また、テストコードは.php単体でも実行できます。
$ php symfony test/functional/your_app_name/path/to/test.php
functional_test

fixtureの利用

テストコード内でテストデータ(fixture)をロードする上で、下記のようにDoctrineのloadDataを使いたいと思いました。
Doctrine_Core::loadData($path_to_fixture);
これを使うには、RDBテーブルのモデルクラスが必要になります。
当該のプロジェクトでは、ゲームサーバという特性からか直接SQLを記述するスタイルになっており、モデルクラスが作成されていませんでした。
ということで、モデルクラスの生成を試みます。
(既存のテーブルにはいくつかプライマリキーが存在しないものがあってモデルが作成できないため、サロゲートキーを追加するなどしてすべてのテーブルにプライマリキーを定義しました)

モデルクラスはスキーマ定義ファイルである config/doctrine/schema.yml から生成するので、MySQLのすべてのテーブル定義をschema.ymlに定義します。doctrine:build-schemaタスクを使うと、現在のRDBテーブルからschema.ymlが自動生成されます。
$ php symfony doctrine:build-schema
ただし、schema.ymlではテーブルコメントがサポートされているにも関わらず、doctrine:build-schemaタスクから生成したschema.ymlにはテーブルコメントが出力されませんでした。テーブルコメントはRDBのGUIツールなどで役に立つので削りたくはなく、一旦は手動で追加していきました。

schema.ymlができたら下記のようにモデルクラスを自動生成します。
$ php symfony doctrine:build-model
lib/model/doctrine ディレクトリ以下にモデルクラスが生成され、これでやっとfixtureが利用できるようになりました。

補足ですが、Web APIのレスポンスがJSONフォーマットであるため、必須要素と型のチェックのためにJSONスキーマによるバリデーションをテストに追加しています。JSONスキーマライブラリはcomposerでjustinrainbow/json-schemaをインストールしました。

DBマイグレーション

Doctrineにマイグレーション機能があるので、こちらを使うことにしました。

マイグレーション手順は下記のようになります。
  1. schema.ymlを編集
  2. schema.ymlとモデルクラスの差分からマイグレーションファイルを生成
    $ php symfony doctrine:generate-migrations-diff
    
  3. 必要に応じてマイグレーションファイルを修正
  4. マイグレーションを実施し、動作確認(UP/DOWN)
    • UP
      $ php symfony doctrine:migrate
      
    • DOWN
      $ php symfony doctrine:migrate --down
      
  5. doctrine:build-modelでモデルクラスをアップデート
    $ php symfony doctrine:build-model
    
Symfony2 のDoctrineプラグインではマイグレーションファイルに直接SQLを記述できるようですが、1ではできないようです。
既存のテーブル構造に対応する上で、そのままでは実現できないことがあったので、Doctrineプラグインに若干の修正を加えることになりました。
  • テーブルコメントを反映させる
  • MySQLのdatetimeとtimestamp型を区別させる
  • カラム追加の際に、位置を指定できる

静的解析の可視化

定石として、下記のチェックを入れました。
  • PHP_CodeSnifferによるコードスタイルチェックとJenkins Checkstyleプラグインによる可視化
  • PHP_PMD(循環的複雑度などのチェック)によるチェックとJenkins PMDプラグインによる可視化
  • phpcpdによる重複コードチェックとJenkins Dryプラグインによる可視化

コードカバレッジの可視化

test:coverageタスクで簡易的にカバレッジを確認することができますが、Jenkinsの Clover PHPプラグインで可視化したいので、Symfonyプラグイン rsPHPUnitLimePlugin をインストールして下記のようにテスト実行します。
$ symfony test:phpunit-functional --coverage-clover=report_dir/clover-functional.xml \
--coverage-html=report_dir/clover-functional --coverage-folder=apps/your_app_name your_app_name
clover

CI環境ができたおかげで、機能変更による影響を認識しやすくなりました。テスト駆動による開発もできつつあります。
また、既存のコードスタイルがいわゆるファットコントローラーになりがちな構造だったので、テストのカバレッジが上がってくれば、今後メンテナンスしていく上でリファクタを進めていけるかと思います。

次世代システム研究室では、グループ全体の継続的インテグレーションを支援してくれるアーキテクトを募集しています。CIによる開発経験の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。

それでは、また。

Pocket