Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

Kubernetes輪講会を開催しました

こんにちは、Studyplus事業部 サーバーサイドエンジニアの葉坂です。

以前弊社の「Kubernetesを本番導入しました」という記事でも紹介していますが、スタディプラスでは2020年9月にKubernetes本番導入を果たしました。

tech.studyplus.co.jp

それに伴いサーバーチームもKubernetesでの運用をしていく上で必要な知見を高められるようSREチームと合同でKubernetes輪講会を開催しました。今回はこの輪講会について紹介したいと思います。

はじめに

輪講とは一般的に、

「一つの書物の範囲を決め、その範囲の担当者が事前に内容を調べて、参加者に対して説明をする」

というものです。

そもそもなぜ輪講会形式にしたのか

当初はSREチームの有識者による講義形式の開催も検討されていましたが、講義形式だと理解が浅くなってしまうことも多いので、輪講会形式で開催されました。

そうすることで担当者は担った範囲に関して、最低限人に説明できるレベルにまで理解を深める必要が出てきます。実際、「Kubernetesはコンテナ・オーケストレーション・ツールのあれだな」くらいの知識しかなかった私でも、人に説明できるレベルにまで理解を深められました。

使用した参考書

輪講会では、「Kubernetes完全ガイド」を使用しました。Kubernetesは本体の技術要素が膨大かつ、周辺のエコシステムも充実しているため、学習コストはかなり高いように感じるかと思います。

ですが、「Kubernetes完全ガイド」は網羅性が高く、マニフェストのサンプルも充実しています。記載内容のレベルも全般的に一段深いため、Kubernetesだけでなくインフラ周りの知見に乏しい私でも非常に分かりやすい参考書に感じました。 book.impress.co.jp

また GitHub にも、「Kubernetes完全ガイド」で紹介されているマニフェストが公開されているため、試したり写経したりと無限に学ぶことができます。 github.com

運営ポリシー

進め方

  • 事前に範囲と担当者を決める。
  • 担当者は範囲を事前に調べておき、当日参加者が内容を理解できるように説明をする。
    • 説明の仕方は担当者に委ねる。スライド を作ったり、ホワイトボードに書いて説明したり、口頭で済ます等々。
    • 本に書いてあることを全て説明する必要はなく、自分で重要だと思うポイントをピックアップし説明するのでもよい。
    • 担当者が内容を調べる際、不明点やうまくいかない部分があれば slack で質問を投げる。
  • 参加者は事前に範囲を読み、当日に疑問点を担当者に聞いたり、参加者同士でディスカッションを行う。

上記のような流れで輪講会を開催しました。

ちなみに輪講会の準備に関しては業務に関係のあることなので、業務時間内に行っています。

Kubernetes環境

Kubernetesのほとんどの機能は試せるため、簡単にローカルKubernetes環境を構築できる「minikube」を使用しました。ただ、マルチノードクラスタの作成時は「kind」を使用しました。

参加人数

5人(サーバーサイドエンジニアとSREの全員)で開催しました。

開催場所

Zoomで開催しました。

開催回数

週1回、業務時間内で1時間半の枠を確保し、全9回に分けて開催しました。

ちなみに

現在はKubernetes輪講会の開催枠で、もくもく会形式のGolang勉強会を行っています。それはサーバーチームが担当している10あるマイクロサービスのうち1つがGolangで書かれていることや、今後新たなマイクロサービス作成の際の言語の選択肢を増やすためです。

輪講会に参加して分かったメリット

実際に開催してみると、冒頭にて説明した以外にも、輪講会形式には下記のような様々なメリットがあることがわかりました。

  • 分担し合うことで、独学では時間のかかる内容&ボリュームの書籍でも、メンバーの助けを借りて理解することができた。
  • 自分の担当範囲に関しては、内容の説明だけでなく質問を受けた際も想定し疑問点を潰していくため知識も増えた。
  • 参加者への伝え方を考える上で、資料構成や表現方法、アウトプット手法の学びにも繋がった。
  • 同じ内容からでも、自分とは違う解釈を知れたり、そこから派生した知見共有があったりと、独学や講義形式で知識を受けるよりも学びの幅がかなり広がった。

Kubernetesを学んでみて

私がインフラ周りの知見に乏しいこともあり、初めはKubernetesに対してかなり苦手意識がありました。ただ、実際学んでみると、ServiceとIngressの理解には苦しめられましたが、Kubernetesに登場する概念が多いだけで、1つ1つ丁寧に学んでいけば、そこまで難しいと感じることはありませんでした。今では、弊社で運用しているKubernetesマニフェストを難なく読むことができるくらいに成長しました。輪講会様様です。

さいごに

輪講会は開催してみると良いことずくめでした。 題材書籍への学習意欲向上、吸い上げた知識のアウトプット方法の学び、メンバーとの盛んなコミュニケーション等々…。

特に、昨今コロナでのリモートワークが増え、メンバー同士のコミュニケーションが減っていることに懸念や一抹の寂しさを感じている方に大変オススメです。

興味のある方は是非、輪講会を開催してみてはいかがでしょうか!!

We Are Hiring

現在スタディプラスでは、サーバーサイドエンジニアを募集しています! open.talentio.com

Studyplus AndroidアプリでMAD Scoreを計測してみました

新年あけましておめでとうございます。

モバイルクライアントチームの若宮(id:D_R_1009)です。 お正月にようやくポケモンシールドのチャンピオンを倒しました。 本当に強かった……。

さて、昨年末にMAD Scoreが登場しました。

今回はMAD Scoreの紹介をしつつ、弊社Studyplus Androidアプリの計測結果をお見せしたいと思います。

MAD Scoreとは?

developer.android.com

Modern Android Development から MAD を取っているようです。 Androidも世に出てから10年以上経っているので Modern を強調しているのかなと思います。

Android 11では、ついに AsyncTask を非推奨にすると明言されました。 AndroidX Fragment 1.3.0からは onActivityCreatedstartActivityForResult/onActivityResult が非推奨になっています。 いつの間にかKotlinは1.4系がリリースされ、1.4系の機能を利用したJetpack Composeはalpha版になりました。

Kotlin、Jetpack、Android Studio、Android App Bundle など、最新の Android 開発(MAD)は優れたアプリを構築するための基礎となります。

挙げられている4つの項目も、それぞれ10年前とは様変わりしています。 見比べてみると、確かに Modern なアプリ開発な気がしてきませんか?

  • JavaからKotlin
  • SupportライブラリからJetpack
  • EclipseからAndroid Studio
  • Android application package(apk)からAndroid App Bundle(aab)

次からStudyplus Androidアプリのスコアを見つつ、それぞれの項目がどう評価されるのかを見てみます。

Kotlin

f:id:D_R_1009:20201225084805p:plain

Kotlinは、アプリを構成しているKotlinのパーセントが表示されます。 そのほか、Kotlinのバージョンや利用しているライブラリが掲載されていますね。

tech.studyplus.co.jp

上記ブログのように2019年9月末に86%程度だったKotlin率は、2020年6月頭に99%となり、その後はKotlinのみ増減している状態になります。 1ファイルのみ、Paging 2のutilクラスをJavaで利用しているので、Paging 3のリリースと同時に100%となる見込みです。 リリースが待ち遠しい……!

そのほか、KTXやFlowの利用が表示されています。 FlowはRoomの他、Storeライブラリなどで利用しています。Cold Streamを手軽に扱えるのは、本当に重宝しますね。

tech.studyplus.co.jp

github.com

Jetpack

f:id:D_R_1009:20201225085802p:plain

Jetpackは、利用しているライブラリの数が表示されるようです。 思っていた以上に利用していてびっくりしました。

tech.studyplus.co.jp

Studyplusアプリは開発しているエンジニアの数が(利用ユーザーに対して)少なめなこともあり、Jetpackライブラリを活用した開発を重視しています。 機能性が高い、安定したライブラリが提供されていることは、とても幸運なことだと思っています。 今後も活用できそうなライブラリがあれば、積極的に利用していきたいところです!

Android Studio

f:id:D_R_1009:20201225090753p:plain

ちょっとコメントが難しいのですが、利用しているAndroid Studioのバージョンが表示されます。 現時点ではAndroid StudioとAGPのアップデートが一致してしまっているので、Android Studioを更新しにくい環境もあるかもしれません。

AGP 7.0からはAndroid Studioのバージョンと切り離されることになります。 そうするとこの問題も解決しますね。個人的にはR8のバージョンをいい感じにアップデートできるので嬉しいアップデートです。

android-developers.googleblog.com

なお、Android Studioのスコアを取得できるのは今だけ! (かも)

Android App Bundle

f:id:D_R_1009:20201225090833p:plain

1TB と圧のある数字が出ていますが、aabにすることで100万人がDLしたときに削減される容量のようです。 ただGoogle Play Storeをみてみると、Studyplus Androidは 1,000,000+ のDLとのことなので、数年規模で見ると的外れな数字ではないような気もしてきます。

もともとのアプリサイズ、そしてNDKを利用しているかどうかでこの値は変動する気がするので、大小を一概に良い悪いとは言えない値だと思います。 ですが、日々のアプリサイズを小さくする試みの成果をわかりやすく見ることができるのは、気分も軽くなるのではないでしょうか。

まとめ

f:id:D_R_1009:20201225090848p:plain

トータルの評価は G.O.A.T でした! 日々のAndroidチームの頑張りが評価されたようで、とても嬉しいです。

madscorecard.withgoogle.com

Android Studioにpluginを入れると数分で計測することができます。 ぜひ、計測してみてください!

スタディプラスを支えるインフラ技術(2020年)

こんにちは、SREの菅原です。

あっという間に2020年も年末ですね。時が過ぎるのが早い...

今回は今年の振り返りも兼ねて、2020年でSREチームが行ったインフラのリニューアルについて記事にしたいと思います。

以前スタディプラスを支えるインフラ技術(2019年)を投稿したのですが、2020年版という形でインフラ技術を紹介します。

なぜインフラのリニューアルをしているかという理由については、「Kubernetesを本番導入しました」という記事で「スタディプラスのインフラの現状の課題」を説明しているので、気になる方は読んでみてください。

tech.studyplus.co.jp

はじめに

弊社には大きく分けて以下3つのサービスがあります。

  • 学習管理SNS「Studyplus」
  • 教育機関向け学習管理サービス「Studyplus for School」
  • 参考書読み放題アプリ「ポルト」

今回も2019年度の記事に引き続き「Studyplus」のインフラを紹介します。

システム構成

Studyplusは一番大きなメインシステムと複数のサブシステムによって構成されています。

2020年のリニューアルでは、EKSを新規導入しました🎉

現時点では開発環境と本番環境、ステージング環境(本番DBに接続しており、主にリリース前にアプリケーションの最終チェックをするQA環境)の3種類のKubernetesクラスタがあり、マルチテナント構成でいくつかのサブシステムを移行した状態です。

多少簡略化した図になりますが以下のようなシステム構成となっております。

f:id:ksugahara08:20201218183226p:plain
サーバー構成の概要図

将来的にはメインシステムを含む複数のシステムをEKSに移行する予定です。

利用中の主なAWSリソース

弊社で利用しているAWSリソースは以下になります。2019年と比べるとEKS等コンテナ関連リソースを使うようになりました。 ※簡略名称で記載しております。

  • EKS
  • ECR
  • EC2(ALB、Auto Scaling等含む)
  • Elastic Beanstalk
  • Lambda
  • VPC
  • RDS for MySQL
  • Aurora MySQL
  • ElastiCache(Redis)
  • S3
  • CloudSearch
  • SQS
  • SES
  • Glue
  • Athena
  • CloudFront
  • IAM
  • KMS
  • ACM
  • Route53

構成管理

2019年ではAnsibleを使ってAWSリソースの構成管理を行っていました。

しかし、以下のような点からAnsibleではなくTerraformを使って構成管理するように変更しました。

  • AnsibleのAWSモジュールはあまりメンテナンスされていない
  • AnsibleはTerraformと違い、現在の構成との差分が取れないため実行時に何が変更されるかわかりづらい(チェックモードが有効でないモジュールが多く存在する)
  • AWSモジュールを使っている人が少ないため情報が少ない

現在はTerraformの導入を進め、EKSに移行したシステムからTerraformで構成管理するようにしています。TerraformであればAWSの新機能にもすぐに対応が入り、情報も多いためTerraformに移行して良かったと考えています。

Kubernetesのバージョンアップ時にもTerraformで簡単に切り替えられるようにしています。 EKSクラスタ自体を新規作成して、新旧バージョンのクラスタ2つを平行運用後切り替えるのですが、多少のパラメータ変更で済むので本当に助かっています。

www.terraform.io

CI/CD

以前はCircleCIでCIを行い、JenkinsでBuildやDeployを行っていました。 インフラのCI/CDはPacker + Ansibleで実施しており、温もりあふれる手動実行でEC2を起動したりする場面もありました。

2020年ではKubernetes化に合わせてシステムのソースコードのCI/CDをCircleCI + Skaffoldで行うように変更をしました。パイプラインの概要図は以下のようになっています。

f:id:ksugahara08:20210104130059p:plain
デプロイのイメージ図

今回は使い慣れたCircleCIを選択しましたが、今後はGitOpsにしたいのでArgo CD等のツールを採用することも検討しています。

インフラのソースコードのCI/CDはTerraform + Kubernetesに移行したことで、変更点を確認した上でapplyできるようになりました。しかし、terraform applyに関して手動実行なので、今後はGitHub ActionsやTerraform Cloudを使って自動化していきたいと考えています。

監視・検知

メトリクス監視

Kubernetes移行を機にDatadogへ監視機能を移行しています。理由としてはDatadogは高機能で、AutoDiscoveryに対応しているためKubernetesの監視にフィットすると判断したためです。

Prometheus + GrafanaやElastic Cloudも当時検討しましたが、少人数のSREチームで運用していくコストであったり料金面を比較した結果Datadogを選択しました。

アプリケーションのエラー検知

Sentryを利用しています。エラーはSlackに通知され、タスク管理ツールのmonday.comに自動登録されるように設定してあります。

ログ収集

ログはFluentdを使ってS3に保存、Athenaで確認する方法を取っています。以前はElasticsearch + Kibanaを使っていたのですが、検索の柔軟性や運用コスト、料金的にメリットを得難かったため廃止しました。

OnCall

DatadogにTwilioを設定して、担当者に連絡が飛ぶようになっています。

現在の課題

EKSの運用も始まったのですが、SREチームではまだまだやりたいことがたくさんあります。箇条書きですが簡単に紹介したいと思います。

システム構成

現在既存のシステムをKubernetesへ順次移行しています。今後はメインシステムだけでなく他のサブシステムやStudyplus for Schoolの移行など横展開させていきたいと考えています。

それだけでなく以下も検討していきたいと考えています。

  • スポットインスタンスの活用
  • サービスメッシュの導入
  • Progressive Deliveryの導入

etc

また開発環境と本番環境が同じAWSアカウント内に混在してしまっているため、間違って本番リソースを変更/削除してしまうリスクがあります。それだけでなく権限の複雑化、Terraform構成管理の複雑化を招いてしまっています。今後はAWSアカウントの分割を進めて、複雑性の解消を行っていきたいと考えてます。

構成管理

Terraformでの構成管理を進めて来ましたが今後は以下に取り組みたいです。

  • Terraformへ移行できていないシステムへの横展開
  • GitHub ActionsやTerraform CloudによるCI/CDの検討
  • TFLint,​checkov,tfsec等の導入検討
  • GCPリソースのTerraform管理
  • SREチーム以外のメンバーにもTerraformで構成変更を行ってもらえるように社内勉強会を開催

CI/CD

今回CircleCIを選択した理由が

  • ファーストリリースはできるだけミニマムの構成にしたかった
  • チームの学習コストの関係で現在使っているCIツールにした

というものなので今後はGitOpsができるArgo CD等のツールを検討・導入していきたいと考えています。また、開発環境をGitのbranchごとに作成したいという要望もあるため、実現にむけて検討していく予定です

監視・検知

Datadogへの移行を行っていますが、今後は以下に取り組みたいです。

  • Datadogへ移行できていないシステムへの横展開
  • APMによるパフォーマンス監視の設定
  • SLOの社内導入とDatadogのモニタリング設定
  • Deployした日時をDatadog上からわかるようにする

最後に

2020年はKubernetesやTerraform、Datadogなど新しいツールの導入を行い、インフラ技術の刷新を行ってきました。社内に知見が無い状態からのスタートでしたが、チーム内で話し合いながら進められたことは個人的にも大きな学びがあった1年間になりました。

2021年も2020年同様にStudyplusユーザーが期待するサービスの信頼性向上や価値提供のスピードを上げるためのインフラ技術をBlogで紹介できるようにしたいです。

LambdaTestでスモークテストをはじめました

こんにちは、Studyplus for School事業部エンジニアの島田です。

もうすぐ2020年も終わりになりますね。

はじめに

皆さんはスモークテストをしていますか?

スモークテストとは元々「電子機器での発煙がないかをテストしていたこと」を起源とし、そこから転じて「ソースコードの開発・追加・修正を終えたソフトウェアが動作する状態にあるかを確認するテストのこと」となったようです。

Studyplus For School(以下FS)の開発チームでは、こちらの記事でも少しふれているStatic vs Unit vs Integration vs E2E Testing for Frontend Apps にあるEnd to End(E2E)をスモークテストとして位置付けています。

www.itmedia.co.jp

testingjavascript.com

tech.studyplus.co.jp

なぜ導入したか

これまでE2Eテストの導入を検討した事はあったのですが、

  • どこまでをテストすべきか
  • 運用負荷が高そう。UIの変更に追随することが大変ではないか
  • UI変更に対応できなくなるとテストが落ちても気にしなくなり、テストが狼少年になってしまうのではないか

といった懸念がありましたが、上記の記事にあるIntegration testsの線引きを決め、E2Eテストではサービスの重要な機能に絞った最低限のテストにする事を決めました。

また、こちらの記事で少し触れているログイン関連の改修ではリリース後に一部機能の不具合が発覚しました。そのためログインなどのクリティカルな機能ではE2Eによる退行テストが必要だと強く感じるようになりました。

tech.studyplus.co.jp

ツール選定

E2Eテストを検討するにあたっては、まずサービス、テストフレームワークの選定をしました。

様々な候補が出たのですが、それぞれを調査・検討するには数が多過ぎるので、実績や知名度や特性から以下に絞りました。

SaaSとOSS(課金へのアップグレードもある)のそれぞれ2つをリストアップし、4つのサービス、フレームワークの調査・比較をする事にしました。

autify.com www.lambdatest.com playwright.dev www.cypress.io

それぞれについて以下の内容を中心に調査をしました。

  • 選定にあたって
    • 初期コスト(導入、学習等)
    • 運用コスト(課金有無、自前で構成する場合)
    • テストの実装方法

各メンバーで分担して調査した結果を元にチームで検討した概略が以下になります。

  • Autify
    • メリット
      • コードを書かなくて良い。エンジニア以外でも出来る
      • テスト実行までの最初の設定が手軽そう
      • 日本語ドキュメント、サポートがある
    • デメリット
      • 課金が比較的高い
      • テストコードという資産が残らない(他への乗り換えが難しくなる)
  • LambdaTest
    • メリット
      • ローカルで簡単に様々なブラウザでの実行が確認できる
      • ダッシュボードで様々な結果(動画、リクエスト内容)が確認出来る(年間契約: $99/月)
      • 既存の言語・テストフレームワークで書ける
      • 自動テスト以外の機能も充実している
      • Integrationが多い
    • デメリット
      • Seleniumの学習コスト
      • 海外での導入実績はそれなりにあるが、サービスとしての信頼性は未知数
  • Playwright
    • メリット
      • 無料
    • デメリット
      • 学習コスト高そう。実装に慣れるのが大変そうな印象
      • 実行環境の準備・運用
      • 実行結果(キャプチャ)の保存をしたい場合に、自分たちで考える必要がある
  • Cypress
    • メリット
      • ダッシュボードで様々な結果(動画、リクエスト内容)が確認出来る(年間契約: $99/月)
    • デメリット
      • 学習コスト。Playwrightと比べれば低そうではある

これらの内容から、

  • 導入コスト:Playwright > Cypress > LambdaTest > Autify
  • 運用コスト:Autify > LambdaTest > Cypress > Playwright
  • 金額: Autify > LambdaTest , Cypress > Playwright

と判断し、LambdaTest か Cypress が争点となりました。スモークテストのみをするのであれば両者とも大きな差異はないと判断しました。そのため同じ金額を課金するのであれば+α(クロスブラウザチェックなど)が出来るLambdaTestを採用しました。

※ 上記はあくまでFSチーム内での簡易的に調査した内容なので、もしかしたら認識が違っている箇所もあるかと思いますので、もし修正点があればご指摘いただければと思います。

LambdaTestでE2Eテスト(自動テスト)

LambdaTestでE2Eテストをするには、SeleniumのリモートWebDriver経由でLambdaTestのSelenium Automation Gridを利用して様々なブラウザのテストを実行する事ができます。

そのため様々な言語・フレームワークで実現をすることができます。こちらに詳しく記載されています。

ドキュメント以外にもこちらに実装のサンプルがあります。

FSチームでは学習コスト等を考えて、RSpecでテストを実装することにしました。

www.lambdatest.com

github.com

構成とテストの実行タイミング

FSのインフラ構成とデプロイについてはこちらの記事に詳しい説明があります。アプリケーションはSPAでサーバー(API)とクライアント(Web)で構成され、デプロイにはJenkinsを利用しています。

サーバーの環境には、開発・ステージング・本番の3つがあります。

E2Eテストの実行タイミング、フローの概要は、

  1. 各リポジトリ(サーバー or クライアント)にてmasterへマージ
  2. Jenkinsで開発環境にデプロイ
  3. JenkinsからCircleCI経由(APIでpipelineを実行)でE2Eテストを実行(SeleniumのリモートWebDriverでLambdaTestに接続)
  4. 問題なければ、ステージング、本番へリリース

という感じになります。

f:id:yo-shimada:20201214101413j:plain
E2Eテスト概要図

E2Eテストについてはアプリケーションとは別のリポジトリで管理し、通常のbuild(単体テスト、Lint)とは分けてデプロイ後に実行する事としました。そうした理由としては、

  • アプリケーションがサーバー(API)とクライアント(Web)でGithubのリポジトリが分かれて管理しているため、それぞれのデプロイに対してE2Eテストを実行したい
  • E2Eテストは、実行環境(サーバー)にデプロイしてからでないと確認が出来ない
  • スモークテストの位置付けとしては、(本番にリリースされなければ)masterのbranchマージ後に問題が発覚すれば良い

といった事が上げられます。

circleci.com

tech.studyplus.co.jp

E2Eテストコード(RSpec)の実装例

RSpecで実装概略です。

リポジトリ構成

$ tree -L 2 -a
.
├── .circleci
│   └── config.yml
├── .envrc
├── Gemfile
├── Gemfile.lock
├── spec
│   ├── login_spec.rb
│   └── spec_helper.rb
└── vendor
    └── bundle

Gemfile

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'retriable'
gem 'rspec'
gem 'rubocop', require: false
gem 'selenium-webdriver'

spec/spec_helper.rb

  • @driver.execute_script "lambda-status=#{lambda_status}" はLamdaTestからSlackで通知をするために、LamdaTestにRSpecの成功可否を知らせるために設定しています。
  • @driver = Retriable.retriable(on: Net::ReadTimeout) do はLamdaTestにてタイムアウトする事が稀にあるので、その際にリトライをするようにしています。

Slack通知成功
Slackエラー通知

require 'selenium-webdriver'
require 'retriable'
require 'net/protocol'
require 'net/http'

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups

  # Selenium
  config.after(:example) do |example|
    lambda_status = example.exception ? 'failed' : 'passed'
    @driver.execute_script "lambda-status=#{lambda_status}"
  end

  config.around(:example) do |example|
    caps = {
      browserName: ENV['LT_BROWSER'],
      version: ENV.fetch('LT_BROWSER_VERSION') { 'latest' },
      platform: ENV['LT_OPERATING_SYSTEM'],
      name: example.metadata[:description] || example.metadata[:location] || 'RSpec Sample Test',
      build: 'RSpec Selenium Boron',
      network: true,
      visual: true,
      video: true,
      console: true,
      tags: [example.metadata[:file_path].split('/').last&.split('.')&.first]
    }

    @driver = Retriable.retriable(on: Net::ReadTimeout) do
      client = Selenium::WebDriver::Remote::Http::Default.new
      client.read_timeout = 120 # seconds
      Selenium::WebDriver.for(
        :remote,
        http_client: client,
        url: "https://#{ENV['LT_USERNAME']}:#{ENV['LT_APPKEY']}@hub.lambdatest.com/wd/hub",
        desired_capabilities: caps
      )
    end
    begin
      example.run
    ensure
      @driver.quit
    end
  end
end

spec/login_spec.rb

クリティカルなテストケースのみ(今回はログイン)として、なるべくUIの状態に依存しないシンプルな実装(最低限のxpath)を心がけました。

RSpec.describe 'login' do
  describe 'ログイン・ログアウト' do
    let(:email) { 'test@example.com' }
    let(:password) { 'sample' }

    context '未ログインの場合' do
      before do
        @driver.manage.window.maximize
        @driver.get("#{ENV['WEB_URL']}/login")

        @driver.find_element(:xpath, "//button[contains(text(), 'ログイン')]").click

        email_element = @driver.find_element(:name, 'operator[email]')
        email_element.send_keys(email)
        password_element = @driver.find_element(:name, 'operator[password]')
        password_element.send_keys(password)
      end

      subject { @driver.find_element(:xpath, "//button[contains(text(), 'ログイン')]").click }

      context '正しいメールアドレス、パスワード' do
        it 'ログイン出来る' do
          subject
          expect(@driver.current_url).to eq "#{ENV['WEB_URL']}/?login=success"
        end
      end

     ...

    end
  end
end

.circleci/config.yml

実行結果についてはSlackで通知されます。

f:id:yo-shimada:20201203232355p:plain
CircleCIのSlack通知

version: 2.1
orbs:
  slack: circleci/slack@4.1.1
executors:
  default:
    working_directory: ~/test-e2e
    docker:
      - image: cimg/ruby:2.6-browsers
        environment:
          LT_OPERATING_SYSTEM: win10
          LT_BROWSER: chrome
          WEB_URL: https://example.com

commands:
  install_dependencies:
    steps:
      - run:
          name: gem install bundler v1.17.2
          command: |
            gem install bundler:1.17.2
      - run:
          name: bundle install
          command: |
            bundle install -j4 --path vendor/bundle
  notify_failed:
    steps:
      - slack/notify:
          event: fail
          mentions: '@engineer'
          template: basic_fail_1
  notify_success:
    steps:
      - slack/notify:
          event: pass
          custom: |
            {
              "blocks": [
                {
                  "type": "header",
                  "text": {
                    "type": "plain_text",
                    "text": "E2E Test Successful! :tada:",
                    "emoji": true
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    {
                      "type": "mrkdwn",
                      "text": "*Project*:$CIRCLE_PROJECT_REPONAME"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*When*:$(date +'%m/%d/%Y %T')"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Tag*:$CIRCLE_TAG"
                    }
                  ],
                  "accessory": {
                    "type": "image",
                    "image_url": "https://assets.brandfolder.com/otz5mn-bw4j2w-6jzqo8/original/circle-logo-badge-black.png",
                    "alt_text": "CircleCI logo"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": {
                        "type": "plain_text",
                        "text": "View Job"
                      },
                      "url": "${CIRCLE_BUILD_URL}"
                    }
                  ]
                }
              ]
            }
jobs:
  build:
    executor: default
    steps:
      - checkout
      - restore_cache:
          keys:
            - v2-bundler-{{ arch }}-{{ checksum "Gemfile.lock" }}
            - v2-bundler-{{ arch }}-
      - install_dependencies
      - save_cache:
          key: v2-bundler-{{ arch }}-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - persist_to_workspace:
          root: ~/test-e2e
          paths:
            - ./*
      - notify_failed
  rspec:
    executor: default
    steps:
      - attach_workspace:
          at: ~/test-e2e
      - restore_cache:
          keys:
            - v2-bundler-{{ arch }}-{{ checksum "Gemfile.lock" }}
            - v2-bundler-{{ arch }}-
      - install_dependencies
      - run:
          name: run test
          command: |
            bundle exec rspec
          when: always
      - notify_failed
  notify_success:
    executor: default
    steps:
      - notify_success

workflows:
  build:
    jobs:
      - build
      - rspec:
          requires:
            - build
      - rubocop:
          requires:
            - build
      - notify_success:
          requires:
            - rspec

JenkinsからCircleCIのジョブを実行する際のAPI呼び出し例

curl --request POST \
-u '${CIRCLECI_TOKEN}:' \
--header 'content-type: application/json' \
--data '{"branch": "main"}' \
--url 'https://circleci.com/api/v2/project/${vcs-slug}/${org-name}/${repo-name}/pipeline'

LambdaTestのダッシュボード

Seleniumによる自動テストの経過・結果はLambdaTestのダッシュボードで確認することができます。Seleniumのvideo オプション等を有効にしておくと、テストの動画やリクエスト内容などブラウザでの実行を確認することが出来ます。

f:id:yo-shimada:20201203232900j:plain
Lambdatest Automation

さいごに

LambdaTestを導入するまでにかかったコストは低く、実行結果が簡単に分かりやすく確認出来る事は大きなメリットだと感じています。

また自動テスト以外にも、様々なブラウザで検証できるクロスブラウザテスト等の機能が充実しており自動テスト以外でも導入の恩恵は大きいと感じています。

ただ、自動テストで時々タイムアウトになるなど不安定な時がありました。あまりに常態化するとテストが落ちても気にしなくなりテストをしている意味がなくなるので、そこが心配ではありますが現時点ではほぼ発生しないので気になってはいません。

今後はE2Eによるクリティカルな機能のテストケースの実装を追加し、より保守性を向上させると共に手動で実施するテストを減らし生産性を高めていきたいと考えています。

AndroidとiOSの両方開発のメリット

こんにちは、モバイルクライアントグループの隅山です。 普段はAndroid開発を担当しています。最近ではiOS開発にも参加し始めたため、両OS開発についての難しさやメリットについて紹介します。

背景

自分のエンジニア目標としてAndroidとiOSの両方を開発できるスキルを身につけることがあり、スタディプラスのiOSアプリを開発する機会をいただきました。 iOS開発自体は初の試みというわけではなく、元々Objective-Cで簡単なiOSアプリ開発はやったことがありました。 しかし、簡単なアプリ開発と実際のプロダクトにおけるアプリ開発とでは大きな壁があり、そこでのつまづきと学びをここでは紹介します。

iOS開発

開発方針

iOS開発を進め方としていきなり新機能開発を行うとわからないことが多すぎるので、初めにAPIまわりの開発を数件行い、慣れてきた後Viewまわりの開発を行う方針にしました。

まずAPIまわりの開発は3件行い、Swiftコードの書き方など基本的な部分を学ぶことができました。 コードの書き方は調べればわかることが多いので特につまづくことはありませんでした。 APIまわりの開発をスムーズに終わらせることができたので、Viewまわりの開発に手を出そうとしたのですが、そこで多くのつまづきがありました。

内容

f:id:JASON13:20201125190040p:plainf:id:JASON13:20201125190045p:plain
ログイン画面(左)対応前(右)対応後

Viewまわりの開発として、ログイン画面を作り直す実装を行いました。

元のボタンなどは使い回すことができ、テキストを新たに追加するだけなので問題ないと思っていました。 ただ、対応前のログイン画面が数年前のコードからあまり改善されていなかったので下記の対応も追加で行いました。

  • UIViewControllerからUITableViewControllerに変更
  • Xibファイルを使ったレイアウトからコードレイアウトに変更

今回の対応で大きくコードが変わるため、既存画面をリファクタリングするのではなく、新しく画面を作り直して差し替える対応を行いました。 既存のリファクタリングであれば修正該当部分だけ直せばいいのですが、新しく作ったためiOSのViewの作り方を学ぶきっかけにもなりました。

つまづき:ヘッダー・フッターの存在

UITableViewControllerでViewを作る際に、UITableViewのヘッダー・フッターの概念につまづきました。 AndroidではRecyclerViewを工夫して実装する必要があるため、標準で存在するiOSの実装に戸惑いました。 しかし、他のiOSアプリのリストにヘッダーで分類名を追加する場合やフッターで注意書きを追加する場合を見て、ヘッダーやフッターの用途を理解することができました。 どこまでをヘッダーとするかやどこまでをフッターにするかなどはこれからも開発する中で学ぶ必要はありそうです。

今回の画面の場合だとメールアドレスと外部アカウントで二層に分かれているため、2セクションのヘッダーやフッターに文字列を追加する形で実装しました。

つまづき:レイアウトの確認方法

Xibファイルからコードレイアウトでの画面パーツの生成へ変更する対応ではかなりの時間がかかりました。 AndroidではxmlファイルでViewやレイアウトをプレビュー画面で見ながら開発できますが、iOSではコードベースでViewを開発する場合、サイズや位置などを調整する度に毎回ビルドしてシミュレータで確認する必要がありました。 Viewについての知識がなかったので実際に微調整しながら開発していたため、学びながら開発するにはこの方法は向いていなかったようです。

また、Viewまわりで実装漏れが発生するとアプリが落ちるなどViewまわりのエラーはややわかりにくい印象でした。

開発を終えてみての感想

普段Android開発している人がiOS開発する際に身につけるべき知識も多いのは開発前から自明ではあったのですが、それにしてもOSによって結構違う点があるなと感じました。

iOS開発の最初の方は自分でできるだけ行うことを心掛けており、コードを触りながらわからないことは調べることを行っていました。 しかし、終わって振り返ってみると同じグループのiOSエンジニアとのペアプロが一番勉強になりました。 自分一人で開発すると堂々巡りで時間が取られたのですが、ペアプロではその場で気づいたことや疑問点をぶつけることができるので時間もかからず、非常に勉強になりました。

これから違うOSの開発を行う方にはペアプロを強くお勧めします。

両OS開発について

難しさ

両OS開発して感じた難しさを2点あります。

1点目は開発ツールの機能性です。 AndroidはAndroid StudioでiOSはXcodeで開発することが多く、各開発ツールによってできることとできないことを理解することが難しかったです。 例を出すと、自分はAndroid Studioを普段から使っているためメソッドやクラスの呼び出し元に移動するのにCommand+クリックで処理を追うことができましたが、Xcodeではそのような機能がありませんでした。

2点目はOSごとの違いの考慮です。 例を出すと、Androidではマテリアルデザインに従ってレイアウトのマージンを決めますが、iOSではlayoutMarginsGuideなどを用いて読みやすい幅を設定するなどOSごとに推奨のやり方がありました。 また、iOS開発でファイル追加や削除の場合にファイル管理しているproject.pbxprojが変更されるのですが、他の方とコンフリクトが起きた場合の解消方法はかなり難しく感じました。

メリット

上記では難しさを書きましたが、両OS開発によるメリットも多くあります。

  • OSによって仕様の認識ズレがない
  • チームとして開発リソースを削減できる
  • 仕様検討段階で両OSのことを考えられるようになる

まずは二人の開発者でそれぞれ開発するといくらコミュニケーションを取れていようと仕様の認識ズレが生じることはあります。 しかし、一人で開発することによって仕様がOSごとにずれることを防げます。

次に開発リソースを削減することができます。 単純計算すると半分のリソースで新機能開発することができるのでチームとしてはアウトプットを増やせます。

また、各OSの書ける人が増えるということはレビューできる人数も増えるということなので相談できる人が増え、レビューによる指摘も増えるので不具合をより抑えることができます。

まとめ

両OS開発を実現するためには勉強することも多いですしつまづくことも多いと思いますが、メリットも大きいと感じているためこれからもAndroid開発だけでなくiOS開発を続けていきたいと思います。

まず直近やることとしてはプレビューがなく開発時間が取られてしまったためXcode Previewsを導入していきたいと思っております。

GitHub ActionsでSwiftLintをLinuxの上で動かそう

こんにちは、モバイルクライアントグループの若宮(id:D_R_1009)です。 みなさまお元気でしょうか、私はチェンソーマン9巻購読直後のため気持ちが乱れております。どうしてだよ……。

コードの悪魔は厳しいので、そんな気持ちの乱れをコードに持ち込むわけにはいきません。 そんなわけで、iOSチームで取り組んでいるSwiftLintの自動化について書きます。

SwiftLintに期待すること

多くの現場で取り入れられていると思われるので、ご存知の方は次のセクションまで飛ばしてください。

SwiftLintとは

github.com

SwiftLintはeslintktlintのように、Swiftのコードスタイルを矯正指摘してくれるツールになります。

iOSではおなじみ、Realmによってメンテナンスされています。リリースも活発です。 GitHub Releaseのタイトルもちょっと賑やか。

github.com

チーム開発ではSwiftの書き方を揃えてくれるので、書き方の癖によるレビュー時のお悩みを軽減してくれます。 また個人であっても、Swiftのより標準的な書き方を学ぶことができるため助かるツールとなっています。

SwiftLintを動かす

SwiftLintはGitHubのReadmeで紹介されている通り、複数の方法で環境構築することができます。 HomeBrewCocoaPods以外にも、SwiftPMで動かすなどなどケースに合わせて対応できます。

開発マシンに入れることができれば、あとはXcodeのbuild phaseに追加するだけです。 HomeBrewで入れた場合であれば、Xcodeの項目にある通りスクリプトを追加するだけで、Xcode内でビルドのたびにチェックが走るようになります。

f:id:D_R_1009:20201106203321p:plain
盛大に指摘されているところ

一度ビルドを走らせないといけないのが難点といえば難点(特にビルドに時間がかかるプロジェクトでは)と言えるかもしれません。 ただ、コードを書いて実機の動作チェックを行い、コードに戻ってきた時にSwiftLintが指摘してくれている状態になるため、体験としてはなかなか良いものじゃないかなと感じています。

GitHub ActionsでSwiftLintを動かす

SwiftLintを全ての開発者がローカルで走らせ、全てチェックした上で開発をしていれば、CI上で動かす必要はありません。 しかし、実際は見逃しやGitHub上での修正などがあり、見落としが発生してしまいます。 そんなわけで、PRを作成した時にSwiftLintを走らせ、警告が出たらコメントして欲しくなります。

PRへのコメントといえばDangerです。

https://danger.systems

Danger自体はRuby、もしくはJSの環境があれば動きます。 そのため、CI上でSwiftLintを動かす際にネックとなるのは Swift です。

もしもCIサービスとしてBitriseを使われている場合には問題にならないのですが、そのほか多くのCIサービスではLinux上で実行させる方が お得 になっています。

www.bitrise.io

Bitriseは無料プランであれば10分までmacOSの環境を利用できるため、ステップとしてSwiftLintを追加するだけです。 Dangerのステップも用意されているので、あとはGitHubのトークンを用意するだけですね。

問題は他のCIサービスを利用している場合です。 macOSを利用するとLinuxの8〜10倍のコストがかかるため、PRの度に走らせる処理としてはちょっと厳しいものがあります。

そんなわけで、今回はGitHub Actions + LinuxでSwiftLintとDangerを走らせる方法を紹介したいと思います。

SwiftLintをGitHub Actionsで簡単に実行

github.com

一番手軽なのは、GitHub Actionに登録されている Danger Swift を利用することです。 利用方法は上のページの真ん中あたりに書いてあるので、PRをフックとしたGitHub Actionのymlを作れば完了です。

Danger Swift はJS版のDangerを利用し、SwiftファイルからDangerを呼び出せるようにしたライブラリです。

github.com

なおJS版のDangerを利用しているため、Ruby版のDangerにないバグの影響を受けることがあります。 もしもfastlaneからDangerを利用しているような環境では、一部の振る舞いが変わるためご注意ください。

Docker Imageで高速に実行

Danger SwiftのGitHub Actionに問題があるとすれば、それはActionのセットアップに少し時間がかかることです。 Linuxで動いているためコストとしては気になるほどではありませんが、ささっと実行された方が嬉しいものです。 そんなわけで、Danger Swiftを利用できるDocker Imageを利用しましょう。

なお、SwiftをLinuxで動かすための環境構築が難しいため、Docker Imageを利用すると確実な動作が期待できます。 後述のf-meloniさんがDocker Imageを作ったのは、とあるIssueに一時的に対応するためだったりします。 *1

2020年11月6日現在、Danger Swiftのリポジトリは「Docker Imageの配布準備が完了」しているものの「Dcoker Imageのビルドがされていない」状況となります。 Git Tagが打たれた時にビルドがされる設定になりつつ、まだ新規のGit Tagが打たれていないためです。 この記事をみなさまが読んでいる時にはビルドがされていることと思うので、ぜひ次のページから最新のDocker Imageを確認してみてください。

github.com

なお、個人でDocker Imageを作成してくださっているケースもあります。 それぞれSwiftのバージョンなどを確認しつつ、自分のプロジェクトで利用できるかどうかなどを確認してみてください。

github.com

github.com

Studyplus iOSチームの話

最後にStudyplus iOSチームのSwiftLintに関わる話を少しだけ書きたいと思います。

ローカルのSwiftLintをバージョン管理

私がiOSのアプリの開発に参加した頃は、下記のような整理でした。

  • SwiftLintはCocoaPodsでバージョン管理
  • CircleCI + macOSで、fastlaneを介して実行

2020年11月現在は下記のようになっています。

  • SwiftLintは各自のマシーンではHomeBrewで管理、CI上はDocker Imageのバージョンで管理
  • GitHub Actions + LinuxでDocker Imageを介して実行

この「SwiftLintをバージョン管理しなくなった」理由は3つあります。

  1. CIでSwiftLintのバージョンを管理できるのであれば、開発マシンの上のバージョンを管理する強い理由がない
  2. CocoaPodsやSwiftPMで管理するのは、アプリの動作に関係のあるライブラリだけにしたい
  3. リリースビルド時にSwiftLintをスキップできる(実行できないため)

brew update をする頻度が人によって異なるため、そのズレがプロジェクトに影響を及ぼすほどであればバージョンの管理をするべきかもしません。 Studyplus iOSチームではそのような問題が起きるとは考えられなかったため、今回の判断をすることができました。

CI環境構築の知見を共有

GitHub ActionsやCircleCIなど、利用できれば便利なツールであっても、なかなか最初はとっつきづらいものだと思います。 このため、チームの中で知見を共有できるように2つのことに取り組んでいます。

  1. CI関連のセルフレビュー時に、参照したドキュメントのURLをコメントする
  2. ブログとして取り組んだことをまとめ、社内でレビューしてもらう

参考にしたドキュメントやブログなどのURLと一言コメントをPRのセルフレビューで書いておくと、レビューの負荷が下がり、後ほどドキュメントを追いやすくなります。 またCI関連ではよくあることですが、後日ステップの書き方が変わった時、既存の処理をどう書き換えてよいのか判断しやすくなります。 PRレビューで問題があった時に指摘してもらえるようしつつ、知見が偏っている分野では共有する場として利用するようにしています。

ブログは社外への発表であると同時に、社内向けのドキュメントとして書くようにしています。 きっとこのブログも役に立つ……!

終わりに

いかがだったでしょうか?

SwiftLintを導入しているけれどもPRには反映していない、SwiftLintをPR上で動かしているけれどもコストが気になっている、などのお悩みの助けになれば幸いです。 最後までお付き合い頂き、ありがとうございました!

Zoom Marketplaceにアプリを公開するまで

こんにちは、Studyplus for School事業部エンジニアの島田です。

今回は先日リリースしたStudyplus for School(以下FS) のZoomとの連携機能と、そのリリースに至るまでのプロセスを紹介させていただきます。

prtimes.jp

導入の背景

FSを利用している塾・予備校の多くでコロナ禍によりオンライン指導が急速に進んでいます。その中でZoomを導入しているケースが多くありました。オンライン指導をするにあたってミーティング予定を作成するためにZoomを起動する手間を省き、FSからZoomのミーティングの予定をシームレスに登録出来る事が望まれていました。

以上から、開発機能の要件としては

  1. FSからZoomのミーティング予定を登録出来る
  2. ミーティング予定を登録すると、既存の生徒メッセージ機能で予定の内容が生徒へ送信される

となりました。

Zoom OAuth Appについて

Zoomと連携する方法はいくつかありますが、今回の実現方法はZoom Outh App を作成し、FSからZoomのAPI を利用することにしました。

OAuthアプリの作成

アプリの作成については、こちらのページの手順どおりに進めていくだけになります。

基本的にZoomはドキュメントが充実していたので、アプリ作成については大きくつまづくことはありませんでした。

ここではFSのアプリを作成するための主だった設定を抜粋します。

  • App Type: User-managed app
    • 今回は、認証したユーザーに対するミーティング設定なのでこちらとなります。
  • Intent to publish: Yes
    • FSを介して学習塾が利用するためには(App Marketplaceへの)公開が必要となります。
  • Scopes: meeting:write user:read
    • こちらを参考にします。今回はユーザー情報取得とミーティング作成に必要な項目となります。

Rubyでの実装について

上記で作成したアプリケーションのClient IDとClient Secretを利用したRubyの実装サンプルを紹介します。

今回はこのサンプルや本実装でもZoomAPIの処理部分で、それ用のgemは以下の理由で利用しませんでした。

  • ZoomAPIのgemは存在するが、メンテナンスされているgemが少ない
  • 今回、FSで実現するためのAPIは少ないのでgemを利用することの方が今後を考えるとリスク、コストがあると判断

利用するAPIについて

FSでは今回は以下のAPIを利用しました。

APIを利用して、

  • 連携
    1. Zoomとの連携の最初にOAuthでトークンを取得
    2. /me はどのZoomのどのアカウントと紐づいているかをユーザーページで確認するために利用
  • ミーティング作成
    1. FSの生徒メッセージ画面にてミーティング予定を入力
    2. 入力されたミーティング予定でZoomのミーティング作成APIを呼び出す
    3. 作成された予定を生徒にメッセージする といったフローになります。

f:id:yo-shimada:20201105163448j:plain

実装サンプル

RubyでのOAuthを利用したAPI呼び出しを確認するための最小の実装を紹介します。

  1. OAuthトークンを取得する
  2. 上記で取得したトークンを利用して、API(/users/me)でユーザー情報を取得する

概略コード

require "net/http"
require "base64"

class ZoomController < ApplicationController
  CLIENT_ID = ENV["ZOOM_CLIENT_ID"]
  CLIENT_SECRET = ENV["ZOOM_CLIENT_SECRET"]
  REDIRECT_URI = "http://localhost:3000/zoom/oauth"

  def oauth
    uri = URI.parse("https://zoom.us/oauth/token")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"

    query = {
      grant_type: "authorization_code",
      code: params["code"],
      redirect_uri: REDIRECT_URI,
    }

    credential = Base64.strict_encode64("#{CLIENT_ID}:#{CLIENT_SECRET}")
    headers = { "authorization" => "Basic #{credential}" }
    response = http.post(uri.path, query.to_param, headers)

    if response.code == "200"
      body = JSON.parse(response.body)
      # access_token, refresh_tokenを永続化する
      # body
      # => {"access_token"=> "xxx",
      #     "token_type"=>"bearer",
      #     "refresh_token"=> "xxx",
      #     "expires_in"=>3599,
      #     "scope"=>"meeting:read meeting:write user:read user_profile"}
      Rails.logger.debug("#{body.inspect}")

      # 試しに自分の情報を取得する
      profile = users_me(body["access_token"])
      render plain: profile
      return
    end

    render plain: "NG"
  end

  private

  def users_me(token)
    uri = URI.parse("https://api.zoom.us/v2/users/me")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"

    headers = { "authorization" => "Bearer #{token}" }
    response = http.get(uri.path, headers)

    body = JSON.parse(response.body)
    "#{body["last_name"]} #{body["first_name"]}, #{body["email"]}"
  end
end

アプリの申請について

実装が完了したら、いよいよZoomのApp Marketplaceに公開するためアプリの申請をします。申請フローついてはこちらにまとまっています。

チェックリストを元に、レビューのプロセスに必要なページの用意やアプリの設定をします。

大まかにまとめると、

  • マーケットプレイスへ公開するために必要なアプリ情報をZoomのアプリ設定画面から設定する
  • アプリを利用するために必要なドキュメントを整備する
  • Zoomがアプリをレビューするために必要な環境を整える
  • セキュリティアンケートに回答する

となります。

中でも手間がかかったのは、「ドキュメントの整備」になります。 Zoomマーケットプレイスでは英語のアプリ情報のみがサポート(レビュー)対象となっています。そのためアプリ情報に関連する全てのコンテンツ(利用規約、プライバシーポリシー、Zoom連携機能のサポートページ)とアプリ情報に利用する画像や説明文のテキストを英語に翻訳する必要がありました。ページによっては新規に英語版のページを用意することにもなりました。 アプリが特定の地域・国のみでの利用を前提としている場合にも、このコンテンツは英語である必要があります。また、特定の地域のみで利用する場合にはそれをアプリ情報の長文説明に記載する必要があります。

アプリ申請の却下について

アプリ申請が却下される場合には、およそ2~3営業日ほどでメールが来ます。メールには却下理由が丁寧に指摘されたGoogleドキュメントが添付されています。

レビューはこのレビューのプロセス の順番で実施されているようで、途中で不備が見つかるとそこでレビューを終わらせて指摘をしてくるようです。

機能・ユーザビリティテスト

このフェーズで指摘された点は、主に上記で説明した英語化をする箇所の認識の漏れ等がありドキュメント整備の不備によるものが多かったです。

英語化する箇所が多かったものの、難易度としてはそこまで難しいものではありませんでした。

セキュリティテスト

このフェーズでは、セキュリティに関してのアンケートへの回答とサービスの脆弱性チェックがされます。

そこで指摘された内容が、

  • OAuthトークンの暗号化
  • TLS 1.1のバージョンアップ
  • LocalStorageに機密情報を保存しない。機密データを保存する必要がある場合は、httponlysecure の属性を設定したセッションクッキーの利用をする

といった内容になり、かなり細かくチェックを受けました。

この中で対応が大変だったのは、LocalStorageの指摘になります。これを改善するためにFSのログイン方法を大きく変更する必要があり、その為の修正におよそ1ヶ月ほどの時間を要する事になりました。

最後に

  • Zoomのアプリ設定画面やドキュメントはしっかりしていて開発はスムーズに出来た
  • 申請後のZoomからのフィードバックのスピードが早く、開発が間延びする事が少なく助かった
  • 開発以外での英語化対応に時間をとられた
  • セキュリティテストは、下手な脆弱性診断を受けるよりよっぽど良かったのではないか
  • 認証方法の変更については指摘の課題感は以前からあったが優先して対応するタイミングがなかったので、このタイミングで出来た事は良かった(Zoom連携機能のリリースは遅れたが)

以上の事を経て、無事Zoomマーケットプレイスに公開されました。

marketplace.zoom.us