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

LCL Engineers' Blog

夜行バス比較なび・格安移動・高速バス比較を運営する 株式会社LCL開発者のブログ

iOS/Androidアプリのリリースフローとチェックリスト

こんにちは、森脇です。

今回は、弊社でのモバイルアプリのリリースフローと、リリース時に利用しているチェックリストについて、可能な範囲でご紹介したいと思います。

リリースサイクル

決まった期間でのイテレーションを回しているわけではなく、次のリリースに入れたい機能などを決めて、それを元にしてリリース目標日を決めるという方法でやっています。

現時点ではモバイルアプリ開発に専従できるリソースがないため、Webサイト側の改善などとのバランスを見て優先度を決定しています。

開発からリリースまでの流れ

git-flowを利用して開発をしており、developブランチへPUSHすると、Bitrise + Beta Crashlyticsで随時テスト版アプリを配布しています。開発対象の機能が完成した時点で、releaseブランチを作成しリリースへと進めます。

releaseブランチを作成した時点で、原則コードフリーズとし、本番環境に近いRC版アプリを配布してテストをします。

RC版でのテストに問題なければ、TestFligthやGoogle PlayのBeta配信でProduction版の最終的なテストを行い、問題がなければ申請・公開をします。

f:id:lcl-engineer:20170130005853p:plain

CI,テスト配布については別記事で詳細を書いています。

リリースチェックリスト

Qiita Teamにチェックリストを用意して、それを元に漏れがないようにチェックしています。

f:id:lcl-engineer:20170131061158p:plain

RC版 配布前の前提条件

まず、RC版を配布できる状態であることを確認します。特にアプリ以外の部分での準備が忘れがちとなるので、それによってリリースが遅れることにならないように、早めに準備が整っているかを確認します。

  • [ ] 今回のバージョンで開発予定の機能は全て消化できているか
  • [ ] アプリから呼び出すAPI側に改修がある場合は、リリース準備はできているか
  • [ ] Webサイトのランディングページの準備はできているか
  • [ ] AppStore / Google Playの更新内容は準備はできているか
  • [ ] リリース後のプロモーションの準備はできているか

RC版アプリでのテスト

この時点でコードフリーズし、2,3日かけて重点的にテストを行います。 テスト版アプリで各機能の詳細なテストは実施済みのため、このフェーズでは端末・機能に漏れがないように網羅的に確認しています。

  • [ ] 今回の開発対象のテスト
  • [ ] シナリオに沿った主要機能でのレグレッションテスト
  • [ ] 新規インストール、バージョンアップで共に問題なく動作するか
  • [ ] Google Analytics等の計測ログが正しく動作するか
  • [ ] 各種OS・端末での確認
    • [ ] Android 4.4
    • [ ] Android 5.x
    • [ ] Android 6.x
    • [ ] iOS 8.x
    • [ ] iOS 9.x
    • [ ] iOS 10.x

Testflight / GooglePlay Beta配信

リリース前の最後のテストとして Testflight ,GooglePlayのBeta配信を行って確認をします。Production版アプリは、RC版と同じソースコードを元にしているため、この段階では細かいテストは行わず、簡単にクラッシュしてしまうなどの致命的な問題がないかを確認しています。

  • [ ] 各種OS・端末での確認
    • [ ] Android 4.4
    • [ ] Android 5.x
    • [ ] Android 6.x
    • [ ] iOS 8.x
    • [ ] iOS 9.x
    • [ ] iOS 10.x

AppStore申請・Google Play公開

最終確認に問題がなければ、申請・公開へと進めます。 問題が発生した場合に対応できるように、公開は原則午前中に実施し、休み前は公開しないことを原則としています。

アプリリリース後

アプリリリース後の数日間は、クラッシュが発生していないかを重点的に監視し、問題があればすぐに対応します。

  • [ ] releaseブランチをmasterにマージ
  • [ ] Crashlyticsなど監視

最後に

以上、弊社でのモバイルアプリのリリースフローについて簡単に紹介しました。 Webアプリケーションのリリースと比べて、まだまだ人力作業が多く非効率な点は否めませんので、品質を保ちつつより効率のよいやりかたとを今後も考えていきたいと思います。

働き方を工夫して効率UP

フロントエンドエンジニアの岡田です。私の仕事は、勤務時間中はほぼPCの前にいなければなりません。そうなると、1日8時間は座って同じ姿勢を続けることになりますが、それは体に良くないそうですし、頭の回転も悪くなる気がします。 そこで、働き方を工夫しています。

在宅勤務を活用してバランスよく働く

一般的な会社では、昼休みを挟んで、午前中3時間+午後5時間くらい働くと思います。 午前中は良いのですが、午後の5時間は少し長すぎて肩がこりますし、集中力にも欠けることがありました。

そこで、在宅勤務を活用します。 LCLでは、在宅勤務の場合は、7:00〜22:00の間で好きな時間に働くことができます。 (お昼前後に一応コアタイムがあります。在宅勤務については以下で詳しくご紹介しています。) techblog.lclco.com

この制度を利用して、以下のように勤務しています。

在宅勤務:7:00〜8:00 1h
   ↓
準備・身支度
   ↓
在宅勤務:9:00〜12:30 3.5h
   ↓
昼休みを兼ねて会社へ移動
   ↓
会社勤務:14:00〜17:30 3.5h

途中で準備の時間をはさんだり、会社へ移動したりすることで、リフレッシュできます。 朝の1時間は少し短すぎると思われるかもしれませんが、その時間でたまったissueを確認したり、他のメンバーのコードレビューやテストをするのにちょうどよいです。

ランチヨガ部

昼休みには週に2回、ヨガをしています。 f:id:lcl-engineer:20161102140454j:plain

当日になるとchatworkでbotが連絡をくれるので、参加したい人が2人以上集まると開催しています。 f:id:lcl-engineer:20170129224852p:plain

だいたい15分程度で、リラックス系のヨガをしています。 ちなみに私のお気に入りは、肩こり解消ヨガです。 www.youtube.com

最近は、瞑想を試したりもしていて、こちらも頭がスッキリして良いですね。

ハイデスクや土足禁止のフリースペースを活用する

最近では、同じ姿勢を続けていると「からだに悪い気がする…!」とそわそわします。 そんなときには、ハイデスクに移動したり、 f:id:lcl-engineer:20161017005556p:plain

土足禁止のフリースペースで働きます。 f:id:lcl-engineer:20160825181310j:plain

オフィスについては以下の記事で詳しく紹介しています。 techblog.lclco.com

以上です。 働き方を工夫して、より短時間で成果をあげていきたいと思います。

jQuery + Railsで作られた非SPAサイトを一部だけReact & ES2015で書き換えました

フロントエンドエンジニアの岡田です。 昨年末に、弊社のサービス:夜行バス比較なびの一部分をReactで書き換えました。

www.bushikaku.net

夜行バス比較なびのJavaScriptは、構築から3年以上たつこともあり、コードの見通しが悪くなってきています。 リグレッションテストなども導入しながら、不具合が起きないように努めてはいますが、テストに時間がかかりすぎるなどの問題がありました。

techblog.lclco.com

そこで今回、Reactを導入して、リファクタリングをしました。 いろいろつまずくところもあったので、この記事では、夜行バス比較なびでどうやってReactを使っているかをご紹介します。 SPAサイトの事例はけっこうありますが、運用中のサイトの一部にだけReactを導入、という事例はあまりなさそうなので参考になれば幸いです。

環境

以下の組み合わせで使っています。
Webpack + babel + ESLint(Airbnb)+ imagemin(画像圧縮)+ browser-sync

webpackの環境は、各自の開発PC(Mac)につくります。 もともとRailsのsprocketsを使っていたため、React化(ES2015化)完了までは今まで通りsprockets を使うことにしました。 つまり、Webpackで書き出したファイルを、sprocketsで管理しているディレクトリ以下へコミットしてしまいます。 (良い方法ではないと思いますが、サーバーにnode.jsの環境を作るまでのつなぎです。)

Webpackの設定

webpack.config.js(開発中に使用)

// 画像圧縮
const imagemin = require('imagemin');
const imageminOptipng = require('imagemin-optipng');
const imageminMozjpeg = require('imagemin-mozjpeg');
const imageminGifsicle = require('imagemin-gifsicle');

imagemin(['images/**/*.{gif,jpg,png}'], '../public/images', {
    plugins: [
        imageminGifsicle(),
        imageminMozjpeg(),
        imageminOptipng(),
    ]
}).then(files => {
    console.log(files);
    //=> [{data: <Buffer 89 50 4e …>, path: 'build/images/foo.jpg'}, …]
});

// エントリーポイントの設定
module.exports = {
  entry: {
    // PC用エントリーポイント
    'es/pc/es2015': './src/scripts/entry/navi/pc.jsx',
    // SP用エントリーポイント
    'es/sp/es2015': './src/scripts/entry/navi/sp.jsx',
  },
  output: {
    // 出力ファイルのベースとなる階層
    // 例:PCは/app/assets/javascripts/es/pc/es2015.js に出力される
    // 出力された /app/assets/javascripts/es/pc/es2015.js はsprocketsへ任せる
    path: '../app/assets/javascripts',
    filename: '[name].js',
  },
  module: {
    preLoaders: [{
      test: /\.(js|jsx)$/,
      loader: 'eslint-loader',
      exclude: /node_modules/,
    },],
    loaders: [{
      test: /\.(js|jsx)$/,
      loader: 'babel-loader',
      exclude: ['/node_modules/'],
    }
  ],
  },
  eslint: {
    configFile: './.eslintrc',
  },
};

webpack-production.config.js(production buildで使用)

const webpack = require('webpack');

module.exports = {
  entry: {
    'es/pc/es2015': './src/scripts/entry/navi/pc.jsx',
    'es/sp/es2015': './src/scripts/entry/navi/sp.jsx',
  },
  output: {
    path: '../app/assets/javascripts',
    filename: '[name].js',
  },
  module: {
    loaders: [{
      test: /\.(js|jsx)$/,
      loader: 'babel-loader',
      exclude: ['/node_modules/'],
    },],
  },
  // 以下の部分でproduction用で書き出し
  plugins: [
    new webpack.DefinePlugin({
      // process.env.NODE_ENVを'production'に置き換える
      'process.env.NODE_ENV': JSON.stringify('production'),
    }),
    // UglifyJsPluginの実行
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        // 圧縮する時に警告を除去する
        warnings: false,
      },
    }),
  ],
};

最初は圧縮&難読化もsprocketsへ任せていましたが、本番環境でJSエラーが出るため、webpackで行うことにしました。

package.json(scripts部分のみ)

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack --watch & browser-sync start --config ./bs-config.js",
    "build": "webpack --config webpack-production.config.js --progress"
  },
}

ディレクトリ構成

frontend
├── images
├── src
│   └── scripts
│       ├── entry       // エントリーポイントのファイルを設置
│       │   └── navi
│       │       ├── pc.jsx
│       │       └── sp.jsx
│       ├── component     // ページをまたいで使うパーツを設置(js, jsx)
│       │   ├── common
│       │   │   └── xxxxxxxx.js
│       │   └── pc
│       │       └── { コンポーネント名 }
│       │           ├── xxxxxxxx.jsx
│       │           └── xxxxxxxx.jsx
│       ├── page       // ページごとにディレクトリを分けて設置(js, jsx)
│       │   └──  { ページ名 }
│       │       └── sp
│       │           ├── xxxxxxxx.jsx
│       │           ├── xxxxxxxx.jsx
│       │           └── xxxxxxxx.jsx
│       ├── model       // ビジネスロジック
│       │   ├── xxxxxxxx.js
│       │   └── xxxxxxxx.js
│       └── util        // 便利関数
│           ├── xxxxxxxx.js
│           └── xxxxxxxx.js
├── bs-config.js
├── package.json
├── webpack-production.config.js
└── webpack.config.js
  • classは1ファイル1クラス
  • 関数は1ファイルに複数可

エントリーポイントの書き方

Reactを導入しているのは一部のページなので、エントリーポイントで必要な場所にrenderしたり、関数を呼び出すよう指定をします。

// 必要なモジュールをインポート
import React from 'react';
import ReactDOM from 'react-dom';
import History from '../../component/pc/history/History.jsx';

// ページごとに実行する関数や読み込むコンポーネントを指定

if (document.getElementById('history-result')) {
  ReactDOM.render(
    <History />, document.getElementById('history-result'));
}

ちなみにスマホサイトについては、jsのファイルの容量が増えることによりパフォーマンスに影響を与える可能性があるため、現時点では対象ページのみReact & ES2015ファイルを読み込んでいます。

今後の課題

まだ手探りで進めていて、今後も順次置き換え・リリース予定です。 今のところ課題は以下のとおりです。

  • メインとなる検索結果ページは、Ajaxでできていることもありjsのコード量が特に多いので、どのようにReact化を進めるか(分割して進められるのか?)
  • リリースフローの見直し(production buildはサーバーへ任せたい)

これらもまた解決したらご報告したいと思います。

gradle-play-publisherを利用したGoogle Play申請の自動化

新年早々ですが、先日 Andriod版「高速バス比較 」アプリをリリースしました。

圧倒的なバス便の掲載数に、わかりやすい最安値比較、細かい条件での検索など、高速バス・夜行バスを探すためにはなくてないらないアプリです。

play.google.com

f:id:lcl-engineer:20170108005847p:plain

なお、iOS版は昨年の7月にリリースしており、現在では10万以上のダウンロードをしていただいております。

高速バス比較 - 国内の路線と最安値を検索するアプリ on the App Store

さて、今回はAndriod版アプリの開発で行った、Google Play申請の自動化について、ご紹介します。

自動化のメリット

申請は、Google Play Developer Consoleから、必要な情報を入力し、画像をアップロード、APKのアップロードをすれば行えます。複雑なUIでもないので、それほど手間でもありません。

ただ、エンジニアであれば、ありとあらゆるデータをgitでバージョン管理して、全ての操作をコマンドで行いたいと考えるかと思います。アプリ説明や画像の軽微な変更する場合、目視では「変更点どこ?」ってなりますが、gitだと明確ですし、コマンド操作ができれば、CIサービスとの連携で誰でも申請ができるようになります。

自動化で利用するツール

Google Play Developer APIを利用することで、申請を自動化できます。申請ツールは、APIを利用して自作もできますが、gradelプラグインであるgradle-play-publisherを利用しました。

GitHub - Triple-T/gradle-play-publisher: Gradle Plugin to upload your APK and metadata to the Google Play Store

弊社であまり凝った利用はしていませんが、gradle-play-publisherではほぼ必要な機能はそろってると感じてます。

fastlaneのsupplyも良いとの噂を聞くので、iOS版のfastlaneに慣れている場合はこちらもおすすめです。

fastlane/supply at master · fastlane/fastlane · GitHub

事前準備

まず、APIの認証キーを取得する必要があります。これが結構面倒です。

以下を参考にJSONキーを取得します。

Getting Started  |  Google Play Developer API  |  Google Developers

アプリの新規作成はできないため、初回はWebコンソール上からアプリの情報を登録しておきます。

gradle-play-publisherのインストール

build.gradleに下記を追加して、gradle syncします。

buildscript {

    repositories {
        mavenCentral()
    }

    dependencies {
        // ...
        classpath 'com.github.triplet.gradle:play-publisher:1.1.5'
    }
}
apply plugin: 'com.github.triplet.play'
play {
    jsonFile = file("json keyのファイルパス")
}

gradlew tasksを実行すると、Play Store tasksが追加されています。( productFlavorsの定義によって追加されるタスクは異なります)

./gradlew tasks

Play Store tasks
----------------
bootstrapProductReleasePlayResources - Downloads the play store listing for the ProductRelease build. No download of image resources. See #18.
generateProductReleasePlayResources - Collects play store resources for the ProductRelease build
publishApkProductRelease - Uploads the APK for the ProductRelease build
publishListingProductRelease - Updates the play store listing for the ProductRelease build
publishProductRelease - Updates APK and play store listing for the ProductRelease build

Google Playに登録されているデータをダウンロードする

bootstrapReleasePlayResourcesタスクを実行すると、現在 Google Playへ登録されているデータをダウンロードできます。ただし、現時点のバージョンでは画像ファイルはダウンロードできません。

./gradlew bootstrapProductReleasePlayResources

実行すると以下のファイル・ディレクトリが生成されます。

f:id:lcl-engineer:20170108220548p:plain

画像はダウンロードされないので、生成されたディレクトリにファイルを追加します。申請に最低限必要なファイルは、以下の3種類です。

f:id:lcl-engineer:20170108233052p:plain

Google Playに申請情報をアップロードする

デフォルトの状態だと、画像ファイルがアップロードされてないため、build.gradleに「uploadImages = true 」を追加します。

play {
    jsonFile = file("json keyのファイルパス")
    uploadImages = true
}

publishListingReleaseタスクを実行すると、各種ファイルで定義された情報・配置された画像がGoogle Playへ登録されます。

./gradlew publishListingProductRelease

Google PlayにAPKをアップロードする

APKは、手動でアップロードをすると、誤ったAPKを登録してしまうミスが発生しうるので、必ず自動化したいところです。gradle-play-publisherでは、publishApkReleaseタスクでAPKをアップロードできます。

./gradlew publishApkProductRelease

build.gradleに「track = 'beta' 」を追加すると、Beta版としてAPKを登録できます。trackには、「'production'o r 'rollout' or 'beta' or 'alpha'」のいずれかが指定できます。

play {
    track = 'beta' 
    jsonFile = file("json keyのファイルパス")
    uploadImages = true
}

まとめ

上記で記載したとおり、gradle-play-publisherでは非常に簡単に自動化が可能です。リリース時のストレスを少しでも下げるために、申請作業は必ず自動化をおすすめします。

Visual RegressionテストによるWebデザイン崩れの防止

明けましておめでとうございます。

エンジニアブログを初めて、ちょうど1年になりました。 去年はなんとか月に1記事が書ける程度でしたが、今年は月に最低5記事は書いていきます。

今年最初の記事としては、弊社で最近取り組んでいる「Visual Regressionテスト」について紹介します。

Visual Regressionテストとは

私自身は馴染みのない言葉でしたが、以下のサイト等を見ると「Webページのスクリーンショットを以前のバージョンと比べて、ピクセルレベルでの差分を検出するテスト手法」と定義されているようです。

HTML,CSSの修正では、予期せぬ部分に影響することがよくあると思います。それを防ぐために手動によるテストだけに頼ると、サイトの規模が大きくなるにつれてコストが増加しますし、軽微な差であれば目視では気づけ無いこともあります。

一方、 Visual Regressionテストは、低コストで確実に差分を検出することが可能です。例えば、以下のような目視では気づきにくい微妙な差も、スクリーンショットを比較すれば明確に分かります。

f:id:lcl-engineer:20161204105203p:plain

実装方法

Visual Regressionテストの実装は、PhantomCSSやBackstopJSを利用すると比較的容易です。

GitHub - Huddle/PhantomCSS: Visual/CSS regression testing with PhantomJS techblog.lclco.com

上記の記事の通り、弊社も以前はBackstopJSを利用していましたが、後述の確認レポート等を作成するために、使い慣れたRubyを中心とした以下の技術を用いています。

  • Ruby + Capybara + Poltergeist
    • Webページアクセス、スクリーンショット取得
  • Rmagick
    • 差分画像の生成、画像の差異情報の取得
  • Amazon S3
    • スクリーンショットの保存
  • PostgreSQL
    • テスト結果の保存

テストの流れ

テストの流れは下記のようになっており、担当者はHubotに命令と、実行結果の確認だけすれば良いだけです。

  1. 実装担当者が、Hubot に命令しテストJOBの実行
  2. 各ページのスクリーンショットを取得し差分比較
  3. 結果をS3,DBに保存
  4. 結果レポートのURLをチャットへ通知
  5. 実装担当者が、結果を確認する

各所について、補足していきます。

テスト実行のタイミング

当初、GitへのPUSHをトリガーに自動でテストを実行していましたが、WIP段階でのPUSHや、関係のないサーバサイドの修正でもテストが実行され、チャットへの通知が煩雑になりました。

現在は、以下のタイミングで実行しています。

  • 開発中は、任意タイミングで実行 ( Hubotに実行命令を出す)
  • Staging環境へのデプロイ時には自動実行

テスト対象ページ

予期しないページへの影響を検出するのが、目的の一つであるため、全てのページをテスト対象にするべきと考えてます。(検索ページなどパターンが多すぎるページは、代表ページのみに絞る)

弊社では、GoogleスプレッドシートでサイトのURL一覧を管理しており、そのシートをからにテスト対象URLを取得しています。新しいページを作成する場合は、このシートをメンテナンスする運用にしているため、シートに追加すれば自動的にテスト対象に加わるようになっています。

比較対象

Visual Regressionテストでは、何を正として比較するのが重要であり、比較対象は以下の条件が必要と考えています。

  • 比較元は、正しい仕様が実装されている
  • 比較元と比較先は、同一のデータソースを元にページが生成される。(動的ページの場合は、データソースを同じにしなければ、データによる差異が大量に発生してしまいます)

弊社では、実行されるタイミングによって、比較対象を変更しています。

  • 開発中の実行時は、現在のブランチの環境*1とStaging環境を比較
  • Stagingのデプロイ時の実行時は、前回のStaging環境での実行結果とデプロイ後のStaging環境の実行結果を比較

テスト対象となる全ての環境は、同一のデータベースを参照しており、Staging環境はProduction環境と同じmasterブランチのコードで動いています。これは、masterブランチのコードは正しい仕様であるという前提に立っています。一度masterに不具合が混入した場合、気づかなければ今後も間違い続けるという問題を抱えており、それをガードする仕組みは現在も検討中です。

スクリーンショットの差分比較

スクリーンショットの取得は、poltergeistのsave_screenshotを利用しています。

GitHub - teampoltergeist/poltergeist: A PhantomJS driver for Capybara

スクリーンショットの差分比較は、Rmagickのcompositeを利用しています。 比較元・比較先の画像から、差分画像が作成されます。

before_image = Magick::ImageList.new("/tmp/before.jpg")
current_image = Magick::ImageList.new("/tmp/current.jpg")

diff_image = before_image.composite(current_image, 0, 0, Magick::DifferenceCompositeOp)

差分画像は、黒のピクセルが一致、黒以外が不一致箇所として生成されます。

f:id:lcl-engineer:20170102195616p:plain

また、differenceメソッドで差異の数値を求めることも可能です。戻り値の結果が0でなければ差異ありと判断しています。

before_image = Magick::ImageList.new("/tmp/before.jpg")
current_image = Magick::ImageList.new("/tmp/current.jpg")
diff_rate = before_image.difference(current_image)
pp diff_rate
> [4129.06884765625, 0.03533638422297351, 1.0]

テスト結果の確認

Visual Regressionテストでは、発生した差分が適切かどうかの最終チェックは目視になってしまうため、できるだけ簡単に結果が確認できる仕組みが重要だと考えてます。

弊社では、テスト結果をS3,DBに保存し、Web上で一覧表示できるようにしています。(テスト完了後に、確認用URLがチャット通知されます)

f:id:lcl-engineer:20170102200308p:plain (左から差分画像、比較元、比較先)

また、ページごとの差分値も表示し、デフォルトは差分なしの画像は表示されません。担当者は、チャットに通知された確認用のURLを開いて、差分がある画像をさっと結果を確認するだけでチェックが完了します。

f:id:lcl-engineer:20170102200937p:plain

以上が一通りの実装の概要です。最後に、実装する上でのTIPSをいくつかご紹介いたします。

Ajaxの実行完了を待つ

Ajaxを利用して非同期でページを組み立てている場合は、全てのAjaxの完了を待ってスクリーンショットを取得する必要があります。 jQueryを利用している場合は、以下のようにjQueryの実行完了までwaitさせるとAjax実行後の画面が取得できます。

loop until finished_all_ajax_requests?(session)

def self.finished_all_ajax_requests?(session)
    return true if session.evaluate_script('jQuery.active').nil?
    session.evaluate_script('jQuery.active').zero?
end

一部のエリアだけスクリーンショットを取得する

save_screenshotメソッドの引数に、CSS selectorを記載すると該当箇所のスクリーンショットが取得できます。

save_screenshot('/path/to/file.png', :selector => '#id').

動的な広告を除外する

ページ上に動的な広告を表示していると、アクセスするたびに表示される広告が異なり、毎回差分として検出されてしまいます。

poltergeist 1.10から追加されたホワイトリスト・ブラックリスト機能で、広告が配信されるシステムのドメインを除去することで、この問題の対策ができます。

弊社では、ホワイトリストに自ドメインと外部JS・画像配信で利用しているいくつかのCDNを追加しています。

session.driver.browser.url_whitelist = ["テスト対象ドメイン","xxx"]

また、余計なファイルのロードも抑制できることで、テスト時間の短縮につながります。

まとめ

Visual Regressionテストを運用してから、テスト工数の削減ができ、何よりも安心してリリースができるようになりました。特にリファクタリングをする際に有用で、思い切ったリファクタリングに取り組めるようになりました。Visual Regressionテストは、一度仕組みを作ってしまえば、あまり運用コストもかからず、一定の品質担保に役立ちます。本記事が、皆様の参考に少しでもなれば幸いです。

*1:ブランチをPUSHすると、テストサーバにそのブランチ専用の環境を生成するようにしています。これについては別途記事を書きたいと思います。

リモートワークへの取り組み

この記事はリモートワーク Advent Calendar 2016 - Adventar 25日目の記事です。

弊社におけるリモートワークの事例や普段の取り組みを見波が紹介したいと思います。

基本的なルール

細かい部分は割愛しますが大まかには以下のようなルールで運用しています。

  • 7:00-22:00の間で8時間働く(11:00-15:00のコアタイムは意識する。オフィスは10:00-19:00)
  • 前日までにリモートワークの予定をカレンダーに登録してチャットで申告する
  • 当日に勤務開始時に予定の勤務時間をチャットで申告する

週一でリモートワークする日を決めている人や午前はリモートで午後は出社してオフィスで働く人もいます。1名(私のことですが)は地方在住でフルリモートで勤務しています。

様々な活用事例

前述のとおりリモートワークといってもやり方や目的が異なります。その具体例を挙げていきます。

集中できる

メンバーから物理的に話しかけられることがなくなるので自分の作業に集中することができます。オフィスの開始時間より早く開始すればチャットによるやり取りも不要になるので重めの設計や実装も捗ります。

時間を有効に使える

まず通勤時間が0になります。これに加えて比較的フレキシブルなルールで運用しているためうまく時間をやりくりすることで自分の時間を有意義に過ごしたり急な家庭の体調不良にも対応ができます。例えば早めの勤務にして家族と夕食を共にしてから勉強会に参加したり、子どもが保育園で熱を出しても中断して迎えに行き、家族が帰ってきたら子守をバトンタッチしてすぐに仕事を再開できます。

地方在住でも働ける

今のところまだ私だけなのですがフルリモートでの勤務も実践しています。弊社は東京にオフィスにあり私は長野県に住んでいます。中途採用でリモートワークを前提として入社しました。最初の数ヶ月はオフィスで勤務をして、その後リモートワークに移行しました。現在は月一くらいの頻度で社内のイベント等に合わせて出社してしています。自分が住みたい場所に住みながらやりたい仕事・魅力的なサービスに携わることができるのは幸せなことです。

どうやって実現しているのか

メリットが多いリモートワークですが、よく挙げられるデメリットとしてオフィスとリモートでの情報の乖離があります。オフィスの口頭でのやりとりがリモートワーカーには共有されない、リモートワーカーの様子や進捗が見えないといった話はよく耳にします。このようなデメリットを下記のように解消しています。

すべてを記録する

弊社ではYouTrack(JetBrains製のIssue Tracking System)を利用しています。これを間接部門も含めて全社的に使用しています。タスクは全てIssueになっておりコミュニケーションは各Issueのスレッド上で行われます。たとえ口頭でのやりとりが発生してもその内容をIssueに記載するので場所に関係なく把握できるようになっています。また、メンバーとのやりとりが無くても状況や進捗にアップデートがあったら細かくIssueを更新します。逆に進捗がないという場合でも行き詰まっている点や確認事項をコメントしてメンバーに助けを求めます。 このようにリモートワークに関係なくすべてを記録することでオフィスでもリモートでもやり方を変えずに仕事をすることができています。

チャットを活用

近頃はSlackが主流のようですが弊社ではChatworkを利用しています。メンバーとの細かな調整やすぐに回答をもらいたいことはチャットで行います。デプロイやテスト環境の起動もチャットのbot経由で行うようにしており誰が何をしているのかがより把握しやすくなっています。また、本番環境等での作業をする場合も何かあったらすぐに他のメンバーがサポートできるように逐次作業内容とその結果をチャットで実況します。

ビデオ会議

リモートからミーティングに参加が必要な場合はZoomを使ってビデオ会議(音声のみ場合もあり)をしています。エンジニアは週一で定例ミーティングを設けているのでリモートワークの私もそれなりの頻度でメンバーの声を聞くことができチームとしての距離を遠くに感じることなくほど良いバランスを保つのに一役買っています。

おわりに

まだまだ課題や改善点は多くありますが、以上のようにオフィスでもリモートでも同じように働けるようなプロセスを確立しています。前回私が出社したときもミーティングが少し円滑になったくらいでリモートワークをしている普段とほぼ変わらぬ仕事の進め方ができました。

おわりのおわりに

個人的にリモートワークによる一番の恩恵は毎日家族と夕食をともにすることができ、子どもと過ごす時間も増えたことです。これまでの社会人生活の中でワークライフバランスが一番良い状態です。 LCLでは家族とごはんを食べたり、自分の時間を有意義に過ごしつつサービスを共に良くしてくれる仲間を募集中です。 ご興味がある方は是非採用ページからご応募お待ちしております。

株式会社LCL(エルシーエル)

Bitrise + GradleでAndroidアプリのCI環境構築

弊社では、Bitriseを利用してAndroidアプリのCI環境を構築しています。まだまだBitriseを利用している事例は少ないので、簡単にご紹介いたします。

なお、Bitriseを利用したiOSアプリのCI環境構築については、以下の記事で紹介しています。 techblog.lclco.com

前提条件

  • Gradle(gradlew)でビルドができること

初期設定

まずは、新規作成〜ビルド(apkの作成)までの手順を紹介いたします。

アプリの作成

Bitriseでは、まずビルド等の対象アプリを登録する必要があります。 [Add new app] をクリックすると、ウィザード画面が表示されます。

f:id:lcl-engineer:20161127155850p:plain

以下のマニュアルに沿って、進めればよいので詳細は割愛します。

Creating your first App on Bitrise · Bitrise

Workflowの作成

アプリ作成後のWorkflowはデフォルトでは、このような状態になっています。この時点で、ビルドに必要な基本的な流れは網羅しています。

f:id:lcl-engineer:20161127162140p:plain

Gradleで実行するタスクは、以下の箇所で設定します。

f:id:lcl-engineer:20161127162343p:plain

上記のテキストボックスにタスクを直接入力してもよいですが、GRADLE_TASKという環境変数に設定することもできます。

f:id:lcl-engineer:20161127162813p:plain

環境変数の設定画面で、ビルドタスクを入力します。複数のビルドを実行する場合は、半角スペースで区切りで入力します。

f:id:lcl-engineer:20161127162925p:plain

ビルドする

以上で設定は完了で、あとは[Start a build] ボタンでビルドが行えます。

f:id:lcl-engineer:20161127163227p:plain

BitriseでSigned APKを作成する

keystoreファイルをBitrise上に登録することで、リポジトリにkeystoreファイルを保存することなく、Signed APKを作成できます。

keystoreファイルの登録

Workflowの[Code signing & Files]メニューから、ファイルをアップロードします。

f:id:lcl-engineer:20161127164313p:plain

ファイルをアップロードするとパスワード等の情報の入力画面になります。 必要な情報を入力し、[Save metadata]で保存します。

f:id:lcl-engineer:20161127165232p:plain

この画面で登録した情報は、各項目に記載されている環境変数( $BITRISED_ANDROID_XXX)でアクセスできます。

File Downloader Stepの追加

Bitrise上に登録したkeystoreファイルは、ビルド時にダウンロードが必要です。その為に、File Downloaderというステップを追加します。

今回は、Git Clone の Stepの後に追加します。(Gradle の実行前ならどの場所でも良いはずです)

f:id:lcl-engineer:20161127170245p:plain

f:id:lcl-engineer:20161127170059p:plain

続いて、 File Downloader Stepへ変数を設定します。

  • Download source url には、 $BITRISEIO_ANDROID_KEYSTORE_URL を設定
  • Download destination pathには、任意パスを設定(ここでは、$HOME/keystores/release.jks を設定)

f:id:lcl-engineer:20161127170431p:plain

build.gradleの編集

Bitrise上で登録したkeystoreを利用するように、signingConfigsを書き換えます。

signingConfigs {
    release {
        keyAlias System.getenv("BITRISEIO_ANDROID_KEYSTORE_ALIAS")
        keyPassword System.getenv("BITRISEIO_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD")
        storeFile file(System.getenv("HOME") + "/keystores/release.jks") // File Downloaderで指定したパス
        storePassword System.getenv("BITRISEIO_ANDROID_KEYSTORE_PASSWORD")
    }
}

以上で完了です。Gradleでリリースビルドを行うと、Signed APKが生成できます。

まとめ

Bitriseを使えば、単純なビルド環境だけならすぐに用意できます。無料プランもありますので、CI環境未構築の方は、最初のステップとして是非利用してみてください。