読者です 読者をやめる 読者になる 読者になる

アプリ開発の品質底上げ施策をWebhooksでBotが支援する世界

こんにちは。技術部の松尾(@Kazu_cocoa)です。

主にモバイルアプリ開発において、数ヶ月前よりGitHubのWebhooksを使ったとある取り組みを始めました。HipChatやSlackなどをはじめとした様々なサービスとの連携サービスを提供しているGitHubですが、Webhooksの機能を使うことで、より自分たちの開発を支援する未来を創造できればと思います。以降では、実際の使用例、その実装よりな話しへと話しをすすめます。

クックパッドにおけるWebhooksの使用例

チェックリストによるセルフチェックをPR時に実施する

モバイルアプリ開発はWebアプリ開発と異なる点が多々あります。例えば、開発対象の面では端末の多様性、端末システム側設定・通信状態の多様さなど、リリースの面ではデプロイに対する制限や更新がユーザ依存であることなど、です。そのため、当たり前品質の底上げのために不具合の作り込みを減らす仕組みが必要でした。

当たり前品質の底上げのために、基本的な振る舞いの考慮漏れによる不具合の作り込みや、クラッシュに繋がる不具合の作り込みを防ぐことに焦点を絞り対策を考えました。対策の1つとして、確認する観点を絞ったチェックリストを作成し、変更をmasterにマージする前のPull Requestの段階で開発者がチェックできるようにしました。このチェックリストによるセルフチェックと、他開発者のレビューにより当たり前品質の底上げを図っています。

Pull Requestへのチェックリスト張りつけは、当初は利用者の反応を得て改善するために人手で行っていました。一方、開発がスケールすると人手による対応が追いつかなくなることは明らかでした。

Webhooksを使って機械的にチェックリストを応答する

開発のスケールに対応するため、GitHubのWebhooksを使うことでこのチェックリストの応答を機械に任せることにしました。具体的には、以下の順序で処理が行われます。

  1. 開発中のmasterブランチに対して開発者がPull Requestを出す
  2. Pull Requestを契機としたGitHubのWebhooksを受信したサーバが、そのPull Requestへのコメントとしてチェックリストを返す
  3. 他開発者のレビューと、チェックリストによるチェックを終えたPull Requestを開発者が自分でmasterにマージする

チェックリストもテンプレ化できる粒度まで落とし込みましたが、現段階のクライアントアプリの不具合状態を見る限りではある程度効果をだしているようです。蛇足ですが、このチェックリストにはもう一つ狙いがあります。それはリリース前検証の段階で、テストエンジニアが探索的に障害度の高い不具合を見つけていく為のきっかけを作ることです。リリースまでに多くの時間を検証に割ける訳ではないので、検証する側としてもあてをつけて探索的に検証を進めることは重要な要素ですね。

GitHubによるチェックリストの応答例

f:id:kazucocoa:20141007134018p:plain

チェックを終え、マージを無事行ったらそれに合わせてイイネもしてくれます。

f:id:kazucocoa:20141007134021p:plain

GitHub Webhooksの使い方

GitHub Webhooksとは

Webhooksを設定したリポジトリに対する操作を契機に、設定したURLに対してHTTPリクエストを送信するGitHubのサービスです。図1や図2のようにGUIからWebhooksを有効にすることで利用可能になります。Webhooksに関する詳細な説明は公式ドキュメントを参照ください。WebhooksはGitHub/GitHub Enteroriseのいずれにおいても利用可能です。ただし、古いバージョンのGitHub Enterpriseでは、GUIではなくWeb API経由によるWebhooksの設定が必要になるかもしれません。

f:id:kazucocoa:20141002161746p:plain

図1:Webhooksの設定画面

f:id:kazucocoa:20141002161758p:plain

図2:Webhooksの有効化

Pull Requestを契機にコメントを返す

先ほどのWebhooksの使用例のように、あるリポジトリに対するPull Requestを契機とするWebhooksに対して、そのリポジトリへ何らかのコメントを返すという流れを以下に記述してみます。

まずはGitHub側設定のWebhooksの設定において、HTTPリクエスト送信先サーバのURLを指定、pull_requestにチェックをし、WebhooksをActivateします。図1,2がその設定画面です。設定を終えたら、そのWebhooksを受け取るサーバに以下のような実装のサンプルを用意します。

require 'sinatra'
require 'octokit' # https://github.com/octokit/octokit.rb
require 'hashie'

post '/hook_sample' do
  # Webhooksのヘッダに含まれる要素X-Github-Eventを取得する
  # 'pull_request'のような文字列が含まれている
  github_event = request.env['HTTP_X_GITHUB_EVENT']

  # WebhooksのJSON形式のペイロード(https://developer.github.com/v3/)を取得する
  req_body =  Hashie::Mash.new(JSON.parse(request.body.read))
  repository = req_body.repository.full_name

  # GitHub Web APIクライアントのOctokitのインスタンス作成
  # ACCESS_TOKEN: Web APIを使うさいに必要となるOAuth認証に使う文字列
  client = Octokit::Client.new access_token: ACCESS_TOKEN

  case github_event
  when 'pull_request'
    # GitHub Web API 経由でコメントを送る
    client.add_comment(repository, req_body.pull_request.number, 'コメント')
  else
    # pull_request以外の処理
  end
end

GitHub EnterpriseでOctokitを使う時に注意すべきこと

クックパッドでは、GitHub Enterpriseを使っています。GutHub Enterpriseのように操作したいGitHubのAPIが独自ホスト上にある場合、以下のようにnewする前にホスト名を指定する必要があります。

require 'octokit'
Octokit.api_endpoint = 'http://api.github.dev'
Octokit.web_endpoint = 'http://github.dev'
client = Octokit::Client.new access_token: ACCESS_TOKEN

このようにすることで、以下のようにGitHub Enterpriseへアクセスするようになります。

puts client.api_endpoint # => 'http://api.github.dev/'
puts client.web_endpoint # => 'http://github.dev/'

Pull Requestへの応答以外にも

Webhooksを調べると、issueが作成されたりissueに対してコメントがつけられたことを契機に、そのissueに記載された文字列を読んで所定の文言をコメントするという流れも実現することができます。これにより、例えばWebhooksを設定しているリポジトリのissueにおいて、please comment meとすると、そのissueに対して機械的にコメントを返すことができるようになります。

GitHubにBotが生息し、開発を補助する

HubotのようなBotがチャットツール上で意思を持つかのように開発プロセスを補助するChatOpsという言葉が存在するように、GitHub上においてもBotが開発を補助しているような形にみえてきました。どこまで任せるかなどの考慮が入ると思いますが、いろいろBotに作業の補助を任せるような未来を実現できそうですね。

最後に

GitHubのWebhooksのように、様々なサービスをつなぎ合わせ、各々の目的や体制に見合ったプロセスの構築を支援する手段は巷にあふれています。今回の実例は、品質の底上げを目的とした、不具合作り込みを避けるための気づきを開発の一連のプロセスの中に組み込んでいく一例でした。一方で、様々な用途への未来へ進むことができそうな連携だったのではないでしょうか。

ところで、このような取り組みはテストエンジニアとしての改善活動のひとつの側面でした。皆さんの近くにいますテストエンジニアは、品質をより向上させるためにどのような創造的な取り組みをしていますか?色々な取り組みを聞いてみたいものですね。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/