IT業界に携わっているものの、なかなかテクノロジのキャッチアップをできていない自分。
今風の開発現場では何が常識となっているか?を整理することを目的に、
- バージョン管理
- CI(継続的インテグレーション)
- デプロイはどうあるべきか
- リグレッションテスト
文中の_イタリック文字_は私見。記述内容も本文をそのまま転載しているわけではありません。
##バージョン管理
###ロックモデルとマージモデル
Subversionやgitはロックができないためにコンフリクトが多発し、効率的なソースコード管理ができないと偏見を持たれることがある。VSSなどのロックモデルになれた開発現場や、その出身者で構成されているプロジェクトでは顕著である。実際はロックモデルの方が非合理的なことが多く、ロックがかかっていてもかまわず手元で開発をすすめ、あとから手でマージしてコミットしようとする人もいる。
ソースコードの自動マージ、コンフリクトの発生自体を嫌う開発者もいるので認識合わせや説得が必要なケースがある
###ファイルベースとチェンジセットベース
ファイルベースはファイル一つ一つが別々にリビジョンを管理している。チェンジセットベースは複数ファイルにまたがる変更を論理的なひとまとまりと見なしている。つまり、過去のあるバージョンを取得する場合、前者はファイルそれぞれのリビジョンを把握する必要があるが、後者はその時点のリビジョンひとつを知っていればよい。
VSSやCVSはファイルベース、SubversionやGitはチェンジセットベース。ファイルベースに慣れている開発者は、細かいコミットを行う傾向がある。これは論理的な固まりを意識するチェンジセットベースのバージョン管理システムに適していない。
論理的な変更としてのコミットを考慮していない(コミットに意味を付加しない)ため、ファイルベースの場合はむしろこちらのほうが直感的とも言える
###分散バージョン管理システム
Git や Github が相当する。リポジトリをローカルマシンにクローンして、この中でブランチ戦略をたてたり、頻繁なコミットを行うことができる。この時点ではマスタとなるリポジトリに影響を与えないところがメリット。デメリットは、これまでのバージョン管理システムとは一線を画しているため、教育コストがかかる点。
ただしこちらは遠くない将来に十分ペイされるはず。
###バージョン管理システムの進化のスピードは早い
バージョン管理システムがなかった時代から現在まで、20年程度しか経過していない。20年前に奮闘していたプログラマが、今PMで活躍しているとすると彼らは40代。当時の知識や経験をベースにプロジェクトを運営しているなら、それは劇的な進化を続けるテクノロジへのキャッチアップを怠った結果と言える。そのことを理解して、我々がその文化を変えられるように努力する必要がある。
今スタートアップ企業などで行われてる一連の作業の内容は、若いプログラマにとっては当たり前のことかもしれないが、例えばSierに長く所属しているエンジニアにとっては受け入れ難い、共感できないこともあるかもしれない。しかし時代の流れを読んで選択しなくてはならない
###バージョン管理システムで管理すべきもの
- ソースコード
- 要件定義書、設計書
- データベーススキーマ、起動に必要なマスタデータ
- 設定ファイル
- ライブラリなどの依存関係定義
一般には要件定義書や設計書は excel や powerpoinnt で作成されている。これらのバイナリファイルはバージョン管理にはあまり向いていないので、可能であれば markdown や textile を活用したテキスト形式で作成することが望まれる。
最大のメリットは、これらのドキュメントもソースコードと同様にバージョン管理システムにて差分を正確に管理できるようになること、マージが行えることである。
###データベーススキーマを管理する必要性
複数人で開発すると、SQLの適用漏れや順番の間違いにより、データベースの整合性が容易に損なわれる状況を生み出してしまう。ではそうならないように専任者を作ることがベストプラクティスなのかというと、そうでもない。その人がボトルネックになってしまうことがあるからである。データベーススキーマを管理する要件は大きく3つある。
- どのような環境でも同じ手順でデータベース構築が可能なこと
- 繰り返し再実行することが可能なこと
- テキストファイルであること
データベーススキーマの変更にもマージが発生する可能性が十分にあるため、テキストで管理することに価値がある。
###設定ファイル、依存関係の管理
アプリケーション側の設定は環境ごとに設定ファイルを分けられるように管理する。ミドルウェアの接続先の設定やアプリケーション固有の設定など。
通常、アプリケーションをデプロイするサーバは複数台構成となっているので、設定ファイルの管理をいかに効率的に行うかは成功要因の一つであると言える。
##CI(継続的インテグレーション)
###CIとは?
複数の人たちによる開発をスムーズに行う基礎となるのは「インテグレーション」。「インテグレーション」とは、具体的には下記を指す。
- すべてのコードを1カ所に集める
- 依存するライブラリなどにパスを通す
- 必要な場合はコンパイルする
- 必要に応じてミドルウェア設定や起動を行う。
- 単体テストや結合テスト、ユーザ受入テストなどを実施する
これらを継続的に、つまり常時いつでも行える状態を維持することを、CI(Continuous Integration)と呼ぶ。
後半に計画されがちなインテグレーションを前倒しで行っていくことで、開発の複雑性を取り除くアプローチ。
###アジャイル開発と CI
短い期間で開発サイクルを回そうとしたら、インテグレーションに時間をかけている余裕はない。それ故に自動化が必須となる。
フィードバックを繰り返し、品質とスピードを両立するためのブレークスルーがアジャイル開発であり、それを支えるのが CI である。
###なぜ CI のようなプラクティスが求められるか
コストメリット
バグ1件あたりの修正コストは、そのバグが生まれた瞬間からの時間の経過に合わせて増加する。たった今書いたコードをテストして見つけたバグなら原因追及と修正は容易。インテグレーションが後ろになれば、書いた内容もうろ覚えの状態からスタートしてしまう。
####市場の変化のスピード
ビジネスは IT に早い開発とリリースを要求する。CIをせずにリリース日を早めるとどうなるか。犠牲になるのは品質であることが自明。最悪のケースではリリース時点のユーザ要求とマッチせず、コストがまるまる無駄になることもある。早い開発を支える屋台骨として CI が提唱されていることを理解する。
CIがあるからこそ、ハック的な開発をしてサービスを開始し、後からリファクタリングして保守性を高めるようなアプローチを採用できる。
####リファクタリングの必要性
複数の人たちによる開発において、修正箇所にコンフリクトが発生するのは、同じ箇所を違う目的でコードを追加しているからであり、何らかの形でコードの構成を見直すことを示唆している。つまりリファクタリングが必要ということ。しかし、リファクタリングを行うためには外から見た動作が変わっていないことを保証しなくてはならず、手動で行うと時間がかかる。面倒であるという思いが頭をよぎると、リファクタリング自体を行う心理的障壁が高くなる。
###自動化するためのテストフレームワーク
####テスト駆動開発(TDD)系フレームワーク
テスト対象のクラスとメソッドに対してその動作が正しいかを確認するテスト。言い換えると、テストコードによってAPI設計を行うイメージ。
通称テスト・ファースト。
####振る舞い駆動開発(BDD)系フレームワーク
要求仕様をテストケースに落とし込むイメージ。作成するテストケースの抽象度が高くなる。
要件定義者や顧客など、ビジネスのステークホルダーに近い人が作成できるように意図されたテストフレームワーク。
通称スペック・ファースト
###ビルドに関する注意点
eclipseなどの IDE がビルドを実行してくれることが、 CI を妨げる原因になることがある。
開発者が IDE をつかってビルドし、ビルド担当者が改めて Ant を使ってビルドし直してデプロイするということは、開発者が意図した成果物をリリースしたものとは言えないため、やめた方がいい。開発者でもテスト担当者でもビルド担当者でもリリース担当者でも、まったく同じ手順でビルドできることを担保することが CI では重要となる。
CI の対象となるテストとは何か
下記のすべてがその対象となる
- 単体テスト
- 結合テスト
- ユーザ受入テスト
- リグレッションテスト
注意すべきは、テストの作り込みには初期コスト、維持コストがかかること。対象としたいテストのトレードオフを考慮する。逆にCIを実行できていると言えるならリグレッションテストは行っているととも言える。
###厄介なテストをどう書くか
####モッキングフレームワークを利用する
外部APIや DBMS と接続するAPIをテストする場合は、単体テストであればモックやスタブを作成して実施する。 CI においては、継続的にテストできることすなわち何回実行しても同じ結果がでなければならない。DBMSはデータが永続化されているため、そのまま接続してしまうとあるときはパス、あるときはフェイルといった結果を生み出すことがある。
####テストの度にデータベースを起動しテーブルを作成、データをロードする。テストが終わったらテーブルを消す。
モックではなく、本物のデータペースを使った方がテストの価値が上がるようなテストで採用する。検証したい値が多数あり、モックで実装するのが大変な場合も採用する。
データベーススキーマや設定ファイルを変更したら
スモークテストを行う。
このテストケースも自動化しておく。
UIを伴うテスト
Selenium を利用する。
###厄介なテストとコストとのトレードオフ
テストの自動化は、こだわりはじめるとそれ自体がパズルのようで楽しく、ついつい得られるリターンに見合わないレベルまで作り込んでしまう。
またテストの自動化が、評価者の立場や見通しによって必ずしもコスト低減につながる訳ではない点を理解する。
自動テストのよいところは、テストフェーズの成果が積み上がるところ。自動テストの完成度も継続的に向上させていく気持ちがダイジ。
##デプロイはどうあるべきか
###細かくたくさんデプロイできればリスクをコントロールしやすくなる
定期的なデプロイでは、どうしても変更要素が集中する。すべての変更が問題なく動作すれば良いが、そうはいかない。重大な問題が複数同時に起きたら深刻である。デプロイが自動化されて簡単になっていさえすれば、こまめにデプロイすることで、リリース単位の不具合リスクをコントロールできる。
なぜ定期リリースというアプローチを採用しているのかを今一度考える。誰が、何のためにそうしているのか。
###フィードバックを早められる
ユーザからのヒアリングやログ分析などのプロセスを活用すれば、デプロイ結果に対する反応をいち早く取り入れることができ、開発という投資に対する回収、収益化の評価を行いやすくなる。
###組織がスケールする
運用チームのリソースがボトルネックとならない。複数のサービスを複数回デプロイすることが可能となり、安心してプロダクトを増やせる。
これが実現すると、経営と IT が戦略、戦術レベルでマッチしていると言える。
##デプロイの自動化
###デプロイメントパイプライン
アプリケーションをビルド、デプロイ、テスト、リリースするプロセスを自動化する一連の実装をデプロイメントパイプラインと呼ぶ。
The implementation of end-to-end automation of our build, deploy, test, and release processes has had a number of knock-on effects, bringing some unexpected benefits. One such outcome is that over the course of many projects utilizing such techniques, we have identified much in common between the deployment pipeline systems that we have built. We believe that with the abstractions we have identified, some general patterns have, so far, fit all of the projects in which we have tried them. This understanding has allowed us to get fairly sophisticated build, test, and deployment systems up and running very quickly from the start of our projects. These end-to-end deployment pipeline systems have meant that we have experienced a degree of freedom and flexibility in our delivery projects that would have been hard to imagine a few years ago. We are convinced that this approach has allowed us to create, test, and deploy complex systems of higher quality and at significantly lower cost and risk than we could otherwise have done.
目指すゴールはこのデプロイメントパイプラインを構築し、このパイプラインをいかに早く回せるかにある。そのために関係者が理解すべき共通認識が4つある。
- すべてをバージョン管理せよ
- すべての環境を同じ方法で構築せよ
- リリース作業は自動化し、事前に検証せよ
- 繰り返しテストせよ
###プロビジョニングツールチェーン
デプロイメントパイプラインの構築を支援するツールをレイヤー別に整理できる。
####Bootstrapping
サーバOSの設定や仮想マシンによるサーバ立ち上げの自動化に関するツール
####Sysytem Configuration
サーバやミドルウェアの設定を自動化するツール
####Application Service Orchestration
ソースコードのデプロイやリリースに関するサーバ操作などを自動化するツール
###手作業でデプロイするケースと注意点
自動化できないシーンがある。
- 自動化環境を初めて構築する
- 障害対応用の特殊なログの取得
- 緊急のサービス再起動
- 予期せぬ事象により自動デプロイが停止した
1~3については、繰り返し発生する可能性があるなら自動化の準備を進める。
4については、デプロイスクリプトが問題なのであればそれを直して再実行する。無理矢理デプロイを実行すると、環境差異が発生する。それ以外の作業を手作業で行う(例えば、デプロイ先サーバの容量不足が原因でデプロイが停止したなら、不要ファイルの削除操作などは手作業で行っても良い)
###リリース作業のアンチパターン
ここまでで既に自明かもしれないが、リリースのアンチパターンについても記述してある
- リリース作業を手動で行う
- リリース作業の内容が毎回違う
- リリース作業に特別な知識が必要(属人性が高い)
- 何度繰り返してもリリースが行えない
本番環境に対するリリースは、常に失敗するリスクがつきまとう。失敗する可能性をゼロに限りなく近づけるには、リリース作業の自動化を進めてテスト環境やステージング環境で自動化されたリリースのリハーサルを繰り返し検証し、本番環境の手前で失敗を経験すること。
どのステージに対しても、同じ方法でリリース作業を自動的に行えれば、運用チームの作業負荷軽減や、精神衛生にも良いはず。
###サービスをとめないデプロイ方法
- ブルーグリーンデプロイメント
##テスト種別の整理
###テストの4象限
実施しようとしているテストが何のためなのテストなのかを確認するために、テストの4象限をつかってチームや組織で認識合わせすると良い。
第1象限、第2象限のテストはチームを支援するためにも自動化を推進したい。
##リグレッションテスト
###リグレッションテストの必要性
変更を加えたコードから影響範囲を絞り込み、さらに重要度などのリスクの観点でテスト範囲を絞り込み、テストを行うことが一般的かもしれない。しかし、どのテストケースのテストを行うかを判断すること自体にミスを招く可能性があること、制約された時間の中で実施可能なテスト範囲や量を決めてしまうことなどは、結局のところ思い当たるすべてのテストケースのテストを行わない限りは心配が取り除けない。
リグレッションテストのリグレッションテストは人的リソース、納期的に行われることはまずない。人手でリグレッションテストを行うということは、「確保できる品質の割り切り」という大きな判断があってこそ一定の意味を持つ。
###リグレッションテストの効果
リグレッションテストの自動化は、必ずしもテストコストを下げることにつながらない。時に大きなメンテナンスが必要となることもあるし、手動で行った方が良いこともある。自動化テストを作成するときには、「今後何回このテストを行うだろうか?」「将来どのようなメンテナンスが必要となるだろうか?」ということを一度立ち止まって考えた方が良い。
テスト自動化を短期的に完成させようとは、最初から思わない方が良い。自動化が必要だと判断したケースを積み上げていくといつかたどり着く領域だと思う。