agri-note inside

ウォーターセル株式会社 スマート農業システム開発部のブログです。

React Hooks 勉強会。第1回の振り返りと 第2回について

無事に第一回のReact Hooks勉強会が開催されました。予想外に多くの方に視聴していただき、手応えを感じています。

第二回やります

まず、第2回についてですが、28日(木)の12時半〜開催されますので、ぜひご覧ください。

water-cell.connpass.com

第2回からは、基礎としてReact Hooksの初学者がよく使うAPIを超えて、全APIの内容になります。これどこで使うんだ?というものの解説にもなっていきますのでご期待ください。

筆者個人としても、学びが多くなりそうで、面白そうに感じています。ぜひ一緒に学びましょう。

第一回目の内容についてのおさらいやハイライト

前回の内容はYoutubeにアーカイブされています。

https://youtu.be/Fw6PFKVqYWQ

  • useState
  • useEffect
  • useContext

という3つの基本APIについて、杉山くん@sug1t0m0_agrictの方から発表してもらいました。

特に印象的だった話題を紹介します。時間指定リンクを添えておきますので、気になった方は動画のほうもご覧になってください。

useStateは複数の状態を持てるよ

https://youtu.be/Fw6PFKVqYWQ?t=700

ステートの計算が高価なときは関数を渡してあげたほうが良い

https://youtu.be/Fw6PFKVqYWQ?t=872

useEffectでクリーンアップにアローファンクションを渡すか問題について

デバッグのためなのでは?という結論に達しました。

https://youtu.be/Fw6PFKVqYWQ?t=2445

React Hooksの基本についてよくまとまっており(公式ドキュメントがわかりやすいのもあるが)、React Hooksを使う一歩としては良い内容だったと思います。

登壇者の紹介

杉山くんは大学院の研究時代に農家のバイトをして、その後の進路として農業現場を経験し、その後に弊社で農業アプリケーションを製造するという経歴の持ち主です。

こんな話をするとウォーターセルのエンジニアが全員そのような経歴を持っているかの印象があるかもしれませんが、彼のほうが特殊であるということは伝えておいたほうが良さそうですね。

会社の紹介

この勉強会の目的は採用のブランディングと定義していますので、改めて会社についても紹介させてください。

ウォーターセルはアグリノートを中心に農業界の課題をITの力で解決すべく、日々開発を行っています。ぜひご興味のある方は、下記ページをご覧の上、ご応募ください!

water-cell.jp

最後にもう一度次回の告知を

React Hooks勉強会第2回は5/28(木)の12:30〜です。よろしくお願いします!

water-cell.connpass.com

React hooks勉強会を始めます。公開配信します。

ウォーターセル社では初めてブログを書くid:sakapunと申します。
今回、弊社で始める新しい取り組み勉強会を公開することについて告知も兼ねて書いてみます。

React hooksについての社内勉強会を公開ストリーミングします

React Hooks 勉強会 vol.1 @ agri-note inside
2020/5/21 (木) 12:30〜 のスタートです

それではなぜこの試みをしてみるかを解説したいと思います。

なぜやるか(題材)

まず、なぜReact Hooksを題材にしたのかについてです。

弊社のフロントエンドアプリケーションは主にReactを採用しています。
しかし、まだ多くのプロダクトはReact hooksが使える環境にアップグレードしていません。
なのでReact Hooksを本格的に習得する必要はありませんでした。

じゃあなぜ勉強会を立ち上げようかというと、GW中に思っていたよりカスタムHooksが便利と気づいたことがきっかけです。
GW中に何をやったかのミーティングでReact Hooksについての話題をだしたところ、社内のメンバーは積極的に学ぶ必要がないので全機能を押さえているわけではなく、これは一度みんなで学ぶ機会がありそうだと感じました。

公開するということ

色々と複数の要因があります。

1. アウトプットによる自社のアピール

昨今の情勢で今後はどの場所にいても働けるということは更に加速するでしょう。
弊社は新潟という地方都市に拠点を置いています。
現状は全社員新潟に住んでいますが今後はその必要性がなくなる可能性も模索しており、新潟県内だけへのアピールだけでなく全国的なアピールの機会と捉えました。

2. 弊社特有の課題についての議論ではなく、ピュアな技術の話題なので社外に発信しやすい。

公開した場合、社内の口外できない課題について語るということができなくなるのがデメリットです。
今回のReact Hooksについてはそういう性質の内容が少ないはずです。

3. 最近のJAWS-UG新潟第7回で勉強会で、リモート勉強会のストリーミング公開についての知見をインプットしたこと

前から勉強会ってストリーミングすべきだよなぁということは考えていました。
そこにこのリモートの波で知見が増え、画面共有越しに行うので撮影する機材の必要もなく公開できる時代になりました。
先日のJAWS-UG新潟の勉強会でOBSによる勉強会の配信方法を知り、さらに弊社の雑談でzoomアカウントを利用することでYoutubeストリーミングも手軽にできるということに気づきました。
https://medium.com/penguin-lab/zoomのproアカウントでyoutube-liveに配信する方法-832a943d0fed

メリットの方が大きそうだし、やってみよう

大きな反対もなく、大きなリスクも見当たりませんでした。
ならば、まずやってみる!
ということで、React Hooks 勉強会 vol.1 @ agri-note insideをよろしくお願いします。

ウォーターセル開発部の在宅勤務について

f:id:Nkzn:20200417170917p:plain:w480

こんにちは。開発部の中川@Nkznです。

4月16日夜に、非常事態宣言の対象地域が全都道府県になりました。

www.nikkei.com

というわけで、弊社がある新潟県も対象地域になりました。

良い機会なので、弊社開発部(スマート農業システム開発部)の在宅勤務への取り組みについて紹介します。

目次

3月初めから原則在宅勤務に切り替え

まずは、弊社のコロナ対策について。

おそらく新潟県内では早いほうだと思うのですが、弊社では3月から「原則として在宅勤務」に移行しました。もう1ヶ月半ほど在宅勤務を続けていることになります。

業務上の必要に応じて出社は許可されていますので、在宅だと無理のある業務を行う場合は、上長と相談の上、各自の判断で(主に車で)出勤しています。とはいえ、出社している人自体がかなり少ないので、社内でのSocial Distancingはできていそうです。

2018年からBCPの一環として在宅勤務の準備をしていた

在宅勤務やリモートワークの制度を用意している会社は多くありますが、その目的は福利厚生であったり、作業効率向上を謳っていたりと、まちまちです。

弊社では、2018年から在宅勤務の制度を運用しています。少し面白いのは、弊社ではこれをBCP(事業継続計画)の一環として位置付けているということです。

出社困難な天候への対処をBCPで定める

弊社オフィスのある新潟市は、新潟県内では比較的雪が少ない地域です。しかし、それでも数年に一度は交通網が麻痺するような大雪が降ります *1 。そういった場合に無理に出勤させて、社員が大怪我でもしようものなら、事業継続に影響が出るかもしれません。

そういった事態を回避するため、大雪等で出社が困難になることが予期される場合に、事前に機器を持ち帰って在宅勤務できる制度が、BCPの一環として整備されました。機器持ち出し申請等のワークフローも用意され、ISMSの審査にも耐える、しっかりした制度になっています。

恒常的な在宅勤務をBCP訓練として位置付ける

さて、これだけだと緊急時のための制度なのですが、ここで経営企画職の@ksonwpさんが良いことを言いました。「常日頃から運用していないと、いざというときにも運用できない」と。

これが上手いこと経営層の支持を得まして、弊社では在宅勤務が”恒常的に実施するBCP訓練”としての立場を獲得したのでした。フルリモートを実現するのが目的ではなかったので、火曜日〜木曜日の間で自由に在宅勤務ができる制度としていました。月曜と金曜に対面で打ち合わせができるので、これはこれで良かったなと思います。

制度を運用し始めた当時は、在宅勤務による事業継続を実施する日々がこんなに早く来るとは予想もしていませんでしたが、BCP訓練の成果としては上手くハマったな、という印象です。

ウォーターセルの開発を支えるコミュニケーションインフラ

在宅勤務の制度を整えていく過程で、インフラエンジニアの@kam1nchuさんをはじめとした多くのメンバーの努力により、社内インフラの調整が行われました。

開発部目線では、次のようなツールで開発時のコミュニケーションを行なっています。

ツール 用途
GitLab ソースコード管理・CI
VPN 社内ネットワーク内のリソース利用
Backlog 課題管理・タスク管理
Gmail メール(社外連絡用)
Googleドライブ 各種ファイル置き場
Scrapbox ナレッジベース
Adobe XD デザイン共有
Chatwork テキストチャット
Chatwork Live 少人数でのビデオチャット・音声チャット
Google Meet 少人数でのビデオチャット

すべての社内システムをクラウド化しているわけではない弊社では、特にVPNの整備が大きな役割を果たしました。BCP訓練としての実績を重ねていく中で、アカウントの払い出しワークフローが固まっていき、注意事項が洗い出されました。在宅勤務に切り替える時点で「WindowsやmacOSのOSアップデートをするときにはVPNを切って実施してほしい」という話が出てくる程度に経験値が溜まっていたのは、素晴らしいことだったと思います。

また、社内ではZoomの利用実績もありますが、比較的大人数でのミーティングに限った利用になっています。セキュリティ面での世論から、採用見直しの声もちらほら上がっていますが、今のところは東京大学 情報基板センター様のペーパーを参考にしながら、これに準拠した運用を心がけています。

営業系の部門でも案件管理等でクラウドサービスを導入し、出張時や在宅勤務時にも情報共有が可能な体制を作っているようです。

みんなの自宅の作業デスク情報が集まってくる魔法のスレ

在宅勤務ならではの楽しい取り組みもあります。ペパボさんが面白いことをやっているのを見つけたのがきっかけでした。

tech.pepabo.com

これは面白いということで、社内Scrapboxにも同様のページができました。

f:id:Nkzn:20200417140045p:plain
Scrapboxのページができました

筆者の環境は3段階ほどモデルチェンジしており、そのたびに追記をしています。

f:id:Nkzn:20200417142038p:plain
筆者のデスク(上下とも)

もともとPC環境を整えていた人もいれば、ノートPCだけでなんとかしている人もいました。会社から借りたディスプレイ *2 で何とか環境を底上げしたり、だんだん自分でもディスプレイが欲しくなってきて買っちゃった、という人もいました。

仕事環境の格差が生まれやすい状況に課題

この取り組みの副産物として、趣味の範囲で自宅のPC環境を整える人もいれば、そうでない人もいることが見えてきました。Web系の企業に勤務しているITエンジニアだからといって、自宅に高速なインターネット回線や、開発に集中できる環境を整えているわけではありませんし、仕事のためにこれらを自費で整えなければいけない義務もありません。

こうして在宅勤務が長期間に及ぶと、オフィスに比べて仕事環境に格差が生まれやすい状況が、今後の課題になっていきそうです。

仕事のやり方は変わった?

やはり、雑談やミーティングの在り方は変わったように思います。

雑談用のGoogle Meet部屋も用意していますが、やはりオフィスに比べると雑談の量は減っているように思います。

ミーティングについては、ホワイトボードの代替を上手く見つけられていないものの、「共有したい内容を事前にScrapboxに書いておき、議事録はミーティング中に追記していく」というスタイルを2019年のうちに構築しておいたおかげか、思ったよりも回っているように思います。

他の部分では、あまり変わっていないようにも思います。仕様の議論は、以前からBacklogやScrapbox、Adobe XDなどで共有しながら実施していましたし、コードレビューはGitLab上で実施していました。本番リリースの工程で手作業が入る部分については、Google Meetの画面共有で立ち合いする文化もできていました。

BCP訓練という建前の在宅勤務を推進しながら、仕事ができる環境づくりをしてきたのが、功を奏したのだと思います。

まとめ

ウォーターセル開発部の在宅勤務への取り組みについてご紹介しました。

  • BCP訓練として在宅勤務を運用していたのが、実際にBCPの履行に繋がった
  • 各種クラウドツールで業務を回していたのがうまくいった
  • クラウド化していないシステムにはVPNを運用して経験値を貯めておいたのが良かった
  • 開発環境の個人差は今後の課題になるかもしれない
  • 雑談やミーティングのやり方にも慣れていく必要がある

といったところでしょうか。

まだまだ模索している最中ではありますが、社会の現状に合わせて、なんとか踏ん張っていきましょう。

*1:毎年大雪が降るわけではないので、街としてあまり積極的な対策がされていないのです

*2:もちろん持ち出し申請をしています

AndroidX対応時期の目安を考える

こんにちは、開発部の中川@Nkznです。今回はAndroidの話をします。

Googleから提供されているAndroid拡張パッケージ、サポートライブラリの後継としてAndroidXが登場してから、1年半が経ちました。皆様の移行状況はいかがでしょうか。

恥ずかしながら、弊社では移行が進んでおりません。有名ライブラリがAndroidX対応やサポートライブラリ非対応を進めていくニュースを日々目にしながら、圧を感じているところです。

とは言っても「移行しなくても直ちに死ぬわけではないしなー。それよりはビジネス要件のほうが先だからなー」という気持ちもそれなりにあり、腰が重くなっているのが現状です。

しかしながら、AndroidXの導入時期にはひとつの目安があることに気が付きました。本記事では、サポートライブラリとAndroidXの特性を確認しながら、移行時期に目安がある理由について解説します。

サポートライブラリとは

まずは、サポートライブラリがどんなものだったのかをおさらいしておきましょう。

developer.android.com

サポートライブラリを使わない、素のままのAndroidの標準ライブラリでは、 compileSdkVersion に応じたAPIが利用できます。より新しいバージョンを指定してあれば、新しく追加されたクラスやメソッドを利用することができるわけです。

こうして作られたアプリは minSdkVersion で示されたバージョンまでなら、下のバージョンのAndroidでも起動される可能性があります。 minSdkVersioncompileSdkVersion より古い場合、「コード側では最新のAPIが使えるけれど、実際に動作するAndroidにはそのAPIが用意されていない」という状況になることでしょう。何の対処もしなければ、クラッシュを引き起こします。

これを回避するためには、古いバージョンのAndroidでは新しいAPIを実行しないようにしたり、代替処理を用意する必要があります。 Build.VERSION.SDK_INT で条件分岐する方法は、現在でも有効な方法ですね。

しかしながら、いちいち代替処理を用意するのは面倒ですし、どこのアプリでも ActivityFragment 等に対して行う代替処理の内容は、似たものになります。そのため、バージョンごとの条件分岐や代替処理を内包しつつ、見た目上は最新版の標準ライブラリと似たような使い方ができるようなラッパークラスを提供する目的で、サポートライブラリが生まれました。

途中から、標準ライブラリに載せると取り回しが悪くなりそうなUIライブラリもサポートライブラリとして提供されるようになったりして、当初の目的よりも広いものになりましたが、来歴としてはこんなところです。

サポートライブラリの課題

さて、サポートライブラリは、バージョニングについて少々特殊なルールを持っていました。

v24.y.z というバージョンのサポートライブラリは compileSdkVersion: 24 でビルドしなければならない」といったように、バージョンの一番上の数値と compileSdkVersion の値を合わせる必要があるのです。

何かの都合で片方を上げるときには、必ずもう片方も上げなければいけないというのは、メンテナンスコストを増やす要因になっていました。

これがアプリの内部だけで済む話ならよかったのですが、サードパーティ製のライブラリがサポートライブラリに依存している場合は、そちらのバージョンも考慮に入れなければならないケースもあり、やはり辛さに繋がっていました

また、これはセマンティックバージョニングに沿わない命名ルールなので、 yz の桁が上がるということが、何を表すのかも明確ではありませんでした。 z の桁が上がるときに機能追加が行われた記憶もあります(要出典)。

AndroidXの登場

サポートライブラリが抱えていた、バージョニングのわかりづらさ、依存関係の取り回しの悪さ等の課題を解決するために、後継として登場したのがAndroidXだというのが筆者の認識です。

developer.android.com

AndroidXは、セマンティックバージョニングを厳格に適用しつつ、 v1.0.0 から再スタートを切りました。

運用上の課題にフォーカスした後継として登場しているため、機能面での破壊的変更はありません。これはつまり、 v1.x.x はサポートライブラリと全く同じクラスやメソッドを持つことが保証されるということです。

移行手順のページでは、Android Studioでの自動移行の機能も紹介されており、かなりお膳立てしてもらえているという印象です。

たとえ手動で移行することになったとしても、基本的にはimport文を書き換えるだけで済みます。

AndroidX移行のデッドライン?

さて、移行作業そのものは難しくなさそうですが、実際に移行しても大丈夫でしょうか。Twitterを眺めていると、「手元で使っているライブラリがまだサポートライブラリにロックインしていて移行できない」といった意見も見かけます。現実はシュッと移行して終わりというわけにはいかないようです。

しかしながら、「そのうち変えればいい」とも言っていられない時期が迫ってきていることに気が付きました。

targetSdkVersionによるリリース制限

2018年から、Google Playにリリースできる targetSdkVersion に制限がかかるようになりました。

developer.android.com

対象APIレベルの要件のページにもある通り、毎年 targetSdkVersion を上げるのが、Google Playのルールになってきています。

APIレベルの要件 開始日
Android 8.0(API レベル 26)
  • 2018 年 8 月 1 日: 新規アプリで必要
  • 2018 年 11 月 1 日: アプリのアップデートに必要
Android 9(API レベル 28)
  • 2019 年 8 月 1 日: 新規アプリで必要
  • 2019 年 11 月 1 日: アプリのアップデートに必要

このまま順当にいけば、2020年の夏〜秋にはAndroid 10(APIレベル29)が必須になることが予想されます。

APIレベル29のためのサポートライブラリは出ない

それでは、ここでAndroidXのドキュメントを眺めてみましょう。

バージョン 28.0.0 がサポート ライブラリの最終リリースです。android.support ライブラリの今後のリリースはありません。

https://developer.android.com/jetpack/androidx

そう、 compileSdkVersion: 29 に対応するサポートライブラリは現れない のです。

運用ルール次第では、AndroidX対応をするまでリリースができなくなる

targetSdkVersioncompileSdkVersion を揃えて上げていく運用をしているチームでは、これは問題になります。具体的には、次の条件が競合します。

  • targetSdkVersion を29にしないとリリースができなくなる
  • サポートライブラリを使っている限りは compileSdkVersion を28までしか上げられない

より強い都合を持っているのはGoogle Play側の要件なので、この問題に遭遇した場合には「2020年の秋までにAndroidXに対応する」というのが順当な選択肢になるでしょう。

逃げ道はある

デッドラインという表現を使いましたが、おそらくそこまでマストなものではありません。

compileSdkVersion: 28 かつ targetSdkVersion: 29 といった組み合わせはできるはずなので、いったんこれでお茶を濁すという運用回避が現実的かもしれません。

そもそも両者を揃えるメリットはあるかという議論もあります*1。弊社でも現在は28で揃っていますが、少し前まではずれていました。この辺はチームごとの運用によって変わるはずですので、各チームで話し合いましょう。

まとめ

  • サポートライブラリの運用上の課題を解決するのがAndroidX(のv1)
  • 現代のGoogle Playでは targetSdkVersion を上げる強制力が強い
  • サポートライブラリを使う場合に選択できる compileSdkVersion はAPIレベル28まで
  • targetSdkVersioncompileSdkVersion を揃える運用をし続けたいなら、2020年秋までにAndroidX対応を終えましょう

結果的に、絶対に今すぐ取り組むべき理由はないという結論になりました。ただ、強制力は弱めとはいえ「2020年秋」という期限が見えたので、ビジネス上の問題やライブラリの相性問題がなければ、それまでに移行しておくのがいいのではないでしょうか。

追記:アンケート結果

実際に皆さんがどのくらいの意識で targetSdkVersioncompileSdkVersion を揃えたり揃えなかったりしているのか気になったので、意識調査をしてみました。

特に理由がなければ揃えるけれど、現実にはうまくいかないこともある、くらいの結果になったかなと思います。

WebdriverIO + Appium + AWS Device FarmでCordova製iOSアプリをテストする(後編)

こんにちは、開発部の中川@Nkznです。

前回はWebdriverIO + AppiumでCordova製iOSアプリをテストする手法について解説しました。これをAWS Device Farmで動かす場合のコツを見つけたので、今回は後編として、こちらを解説します。

AWS Device Farm

AWS Device Farm(以下、Device Farm)はAmazonが管理しているAndroid/iOSデバイスを、リモートで借りて、アプリのテストに利用できるサービスです。

aws.amazon.com

iOSデバイスで自動テストを実行する場合は、次のテストフレームワークがサポートされています

  • Appium Java TestNG
  • Appium Java JUnit
  • Appium Node.js
  • Appium Python
  • Appium Ruby
  • Calabash
  • UI Automation
  • XCTest for iOS と AWS Device Farm の使用
  • XCTest UI

Node.jsでAppiumを動かすオプションがあるので、これを使えば前回のノウハウがそのまま使えそうですね。

Appiumサーバーの扱いでハマった

まずは公式ドキュメント↓のとおりに進めてみました。

https://docs.aws.amazon.com/ja_jp/devicefarm/latest/developerguide/test-types-ios-appium-node.htmldocs.aws.amazon.com

Device Farmではipaファイルとテストコードのファイル(npm-bundleコマンドでまとめたtgzから作ったzip)を別々にアップロードする必要があるため、CordovaプロジェクトとWebdriverIOプロジェクトを別々に分けました。

wdio.conf.js は特に変更なしで、テスト仕様のyamlに npm test だけを追加しました。

すると、次のようなエラーが起きました。

[DeviceFarm] echo "Start Appium Node test"
Start Appium Node test
[DeviceFarm] npm test

> helloworld@1.0.0 test /private/tmp/scratchBFwg0o.scratch/test-packageRV57J1/node_modules/helloworld
> wdio wdio.conf.js


Execution of 1 spec files started at 2019-11-12T06:08:30.524Z

2019-11-12T06:08:36.017Z ERROR @wdio/appium-service: Appium exited before timeout (exit code: 2)
[35m[HTTP][39m Could not start REST http interface listener. The requested port may already be in use. Please make sure there is no other instance of this server running already.

@wdio/appium-service がAppiumの起動に失敗しているようです。

Appiumサーバーの適切な起動方法はDevice Farmのほうが詳しい

Device Farmのテスト仕様のyamlをよく見ると、Device Farm側が用意した処理の中で、Appiumコマンドを準備して、Appiumサーバーを立ち上げるところまでが済んでいます。

phases:
  install:
    commands:
      - export APPIUM_VERSION=1.9.1
      - avm $APPIUM_VERSION
      - ln -s /usr/local/avm/versions/$APPIUM_VERSION/node_modules/.bin/appium  /usr/local/avm/versions/$APPIUM_VERSION/node_modules/appium/bin/appium.js
# ...
  pre_test:
    commands:
      # We recommend starting appium server process in the background using the command below.
      # Appium server log will go to $DEVICEFARM_LOG_DIR directory.
      # The environment variables below will be auto-populated during run time.
      - echo "Start appium server"
      # The default WDA used is at DEVICEFARM_WDA_DERIVED_DATA_PATH_V1 (Supports versions iOS 12 and below), it is using commit f865d3. See (https://github.com/appium/appium-xcuitest-driver/tree/f865d32e78a5a8a15469bee30ed2f985d378575d)
      # If you need an older WDA version or need support for node modules, use the WDA at DEVICEFARM_WDA_DERIVED_DATA_PATH_V0. (This version does not suport iOS 12)
      - >-
        appium --log-timestamp --device-name $DEVICEFARM_DEVICE_NAME
        --platform-name $DEVICEFARM_DEVICE_PLATFORM_NAME --app $DEVICEFARM_APP_PATH
        --udid $DEVICEFARM_DEVICE_UDID --automation-name XCUITest
        --default-capabilities "{\"usePrebuiltWDA\": true, \"derivedDataPath\":\"$DEVICEFARM_WDA_DERIVED_DATA_PATH_V1\"}"
        >> $DEVICEFARM_LOG_DIR/appiumlog.txt 2>&1 &
# ...  

これが先に起動していたため、 @wdio/appium-service が後から起動してもポートを取れなかったという流れだったようです。

手元と同じ感覚ではダメだった

本来やりたかったのは、手元で実行したときと同じ、次の図のような流れでした。

f:id:Nkzn:20191126175059p:plain
Appium + WebdriverIOをローカル環境で実行する

しかし、どうやら @wdio/appium-service にAppiumサーバーのハンドリングを任せるのは、Device Farm環境では上手い方法ではないようです。おそらく$DEVICEFARM_****** な環境変数をwdio.conf.jsの中で呼び出す方法もありますが、デフォルトのテスト仕様yamlを大幅に書き換えることになりそうなので、避けたいところです。

また、よく考えてみると、Device Farm環境ではテストコードを実行するNode.jsと、テスト対象のデバイスが、同じホストにあるのかどうかすら、筆者は知らないことに気がつきました。ローカル環境と同じ感覚で進めても上手く行くはずがなかったのです。

Device Farmは、Device Farmに合った形で前処理・後処理を行いながら、適切なパラメータを設定してAppiumサーバーを起動してくれています。これはそのまま活かしたほうがよさそうだと判断しました。

デバイスの扱いをDevice Farmに任せる

Device FarmのAppium Node.js環境に、デフォルトで用意されているテスト仕様yamlを活かすとして、WebdriverIOの設定はどうすればよいのでしょうか。

試行錯誤の末、次のような順番で動かせばよさそうなことがわかりました。

f:id:Nkzn:20191127154757p:plain
Appium + WebdriverIOをAWS Device Farm環境で実行する

Device Farm側の動きとしては、ほとんどデフォルトのままなので、テスト仕様yamlで追加したのは npm test だけです。

# 略
# The test phase includes commands that run your test suite execution.
test:
  commands:
    - echo "Navigate to test source code"
    - cd $DEVICEFARM_TEST_PACKAGE_PATH/node_modules/*
    - echo "Start Appium Node test"
    - npm test # <= 自分で追加したのはここだけ
# 略

$ npm test で実行されるのは $ wdio wdio.conf.js なので、テストの実施はWebdriverIOに任せる形になります。

Device Farmに合わせてWebdriverIOを設定する

さて、Device Farmの環境に合わせてwdio.conf.jsを編集していくわけですが、キモとして押さえておきたいのは、次の3点です。

  • 使用するデバイスはDevice Farm側で決まる
  • Device Farm標準の方法で立ち上がったAppiumサーバーは http://0.0.0.0:4723 に配置される
  • WebdriverIOはAppiumのことをほとんど知らないまま http://0.0.0.0:4723 にWebDriver仕様のサーバーがあると信じてテストを実行する

これを踏まえて、前回作成したwdio.conf.jsを編集すると、次のような形になります。

exports.config = {
  // 略
  capabilities: [{
    platformName: 'iOS',
    bundleId: "com.example.appid",
    autoWebview: true, // $()が繋ぎにいくコンテキストをWebView優先にする
    // 使用するデバイスの指定はDevice Farm側の設定に任せる
  }],
  // services: ['appium'], // Appiumの制御はDevice Farmの標準に任せる
  port: 4723, // Appiumサーバーのデフォルトポート
  baseUrl: 'http://0.0.0.0', // Device FarmのAppiumサーバーは0.0.0.0:4723に配置される
  maxInstances: 1, // 同時に起動できるAppiumサーバーはひとつだけ
  // 略
}

デバイスの指定がなくなったため、Desired Capabilitiesがだいぶスッキリしましたね。

services の項目をなくしたので、 @wdio/appium-service は使われなくなりましたが、 baseUrlport が設定されていれば、そこにWebDriver APIがあると信じてリクエストしてくれるようです。

これで、前述の「Appium + WebdriverIOをAWS Device Farm環境で実行する」のシーケンス図の順で処理が実行されます。

余談:Appiumのバージョンをv1.15.1にしたら失敗した

本記事の執筆時点で、Appiumの最新バージョンはv1.15.1です。一方、Device Farmでデフォルトで使用されているAppiumのバージョンはv1.9.1です。

テスト仕様yamlの中では、次の場所のバージョン表記を変えることで、Appiumのバージョンを指定できます。

phases:
  install:
    commands:
      - export APPIUM_VERSION=1.9.1 # <= ここを変えるとAppiumのバージョンを変えられる
      - avm $APPIUM_VERSION
      - ln -s /usr/local/avm/versions/$APPIUM_VERSION/node_modules/.bin/appium  /usr/local/avm/versions/$APPIUM_VERSION/node_modules/appium/bin/appium.js

ローカル環境ではv1.15.1で作業していたので、Device Farmでもv1.15.1を使うことにしました。

すると、次のようなエラーが発生しました。

f:id:Nkzn:20191127175727p:plain
デバイスの検出に失敗する

どうやらデバイスが見つからなかったようです。Appiumのバージョンを戻すと元通りに動いたので、ひとまずv1.9.1で運用することにしました。

AppiumのバージョンとXcodeのバージョン(と使用可能な最新のiOSバージョン)に依存があるみたいなので、Device Farmで用意できているXcodeやiOSデバイスのバージョンが、そこまで新しくはないということかなと想像しています。

まとめ

前後編に分けて、Cordova製のiOSアプリにUIの自動テストを導入する場合のツール選択について解説しました。

WebdriverIO + Appiumの記事や、Appium + Device Farmの記事はあったのですが、すべて組み合わせた場合の記事が見つからずにだいぶ苦しみました。何とかやり方が見つかってよかったです。

WebdriverIO + AppiumでCordova製iOSアプリをテストする(前編)

こんにちは、開発部の中川@Nkznです。

Webサービスやモバイルアプリの品質保証を実施するにあたり、機種依存の不具合を見つけるために、複数のブラウザや複数のモバイルデバイスでテストを行うことがあります。弊社でも、品質管理チームが手作業で複数ブラウザ・複数デバイスへのテストに取り組んできました。

しかしながら、事業の拡大や機能の複雑化に伴い、テストにかかる時間も大きくなってきています。人を増やすという手もありますが、まずはUIテスト(主に回帰テスト)の自動化をしてみようということになりました。

そんなわけで、品質管理チームの主導で、UIテストのツールを調査しています。

Cordova製のiOSアプリをテストする

弊社ではCordova製のiOSアプリをメンテナンスしています。こちらも自動UIテストの対象になりますが、さて、どうやってWebViewにアクセスすればよいのでしょうか。

XCUITest をSwiftでゴリゴリと弄って、WebViewのインスタンスを直接触ればどうにかなりそうな気もしていますが、できれば品質管理チームの学習コストを減らしたい気持ちもあります。 Write OnceLearn Once くらいの学習コストで済みそうなソリューションが欲しいところです。

AppiumでWebViewがテストできる

いろいろ調べてみたところ、Appiumを切り口にするとよさそうなことがわかってきました。

Appiumといえば、ネイティブUI向けの自動UIテストのツールとして広く知られていますが、どうやらWebViewへの対応も進んでいるようです。まずは通常のAppiumの使い方について確認してから、WebViewの扱いについてお話ししましょう。

WebDriverの話

Appiumについて理解する上で避けて通れないのが、UI操作を自動化するための標準仕様であるWebDriverの話題です。

WebDriverは、ブラウザのUI操作を自動化するためのツールとして生まれたSeleniumの一部が標準化される形で、W3Cにより策定された仕様です。

w3c.github.io

SeleniumでのWebDriverドキュメントのひとつであるUnderstanding the components :: Documentation for Seleniumによると、いくつかの運用方法があるようですが、ここでは本記事と関係の深い Remote WebDriver についてのみ言及します。 Remote WebDriver の構成を取った場合、WebDriverの利用イメージは次の図のような形になるそうです。

f:id:Nkzn:20191122175221p:plain:w480
Remote WebDriver構成(公式ドキュメントより抜粋)

図の右下にある Remote WebDriver はWebDriver仕様を満たすREST APIを持つWebサーバーです。このWebサーバーは特定のブラウザを操作できる Driver を扱うことでブラウザの操作を実現します。

このREST APIを利用するクライアントが、図の左側にある WebDriver です(名前が紛らわしいですね)。

任意の言語や任意のテスティングフレームワークから WebDriver ライブラリを利用することで、E2Eテストを実施します。実際に利用する場合の全体図としては、次のようなイメージです。

f:id:Nkzn:20191125114916p:plain
WebDriverの世界観

XxxDriverService が前の図でいう Remote WebDriver にあたるはずです(違っていたらご指摘ください)。ChromeDriverのGetting StartedのJUnitでのサンプルを見ると、雰囲気を掴みやすいかもしれません。

各ブラウザを実際に操作する(おそらく複雑な)処理は XxxDriver が隠蔽してくれる上に、Remote WebDriver構成であれば、そこに指示を出す方法もWebDriver APIによって標準化されているため、テストコードからはブラウザの違いを意識することは少ないでしょう。最終的にWebDriver APIにアクセスできればよいので、多くの言語やライブラリが利用できるのも魅力的なところです。

また、WebDriverの仕組みが標準化され、Seleniumから切り離された結果として、WebdriverIOのようにNode.js上で動く(Selenium以外の)WebDriver向けテスティングフレームワークも登場しており、これが本記事のキモになっています。

通常のAppiumの使い方

WebDriverの話をしましたので、ようやくAppiumの話ができます。Appiumは、WebDriver API仕様のREST APIを提供するWebサーバーです*1

iOS向けのXCUITest DriverやAndroid向けのEspresso Driverを内部で扱うことで、モバイルアプリのネイティブUIに対して指示を出します。次の概要図でいうと、右半分にあたります。

f:id:Nkzn:20191125113603p:plain
Appiumの概要

指示は、外部のWebDriverクライアントからWebDriver APIに対してHTTPリクエストを行うことで実現します。

ただ、ブラウザ向けと完全に同じプロトコルというわけではなく、ブラウザ版がJSON Wire Protocolで通信するのに対して、Appiumサーバーと通信するためには、JSON Wire Protocolのモバイル向け拡張であるMobile JSON Wire Protocolでの通信をサポートする必要があります。そのため、ブラウザのUI操作の自動化とまったく同じライブラリが利用できるわけではありません。

Appiumサーバーにをサポートしているクライアントライブラリの一覧として、2019年11月現在、次のライブラリが紹介されています(ドキュメントから抜粋)。

Language/Framework Github Repo and Installation Instructions
Ruby https://github.com/appium/ruby_lib, https://github.com/appium/ruby_lib_core
Python https://github.com/appium/python-client
Java https://github.com/appium/java-client
JavaScript (Node.js) https://github.com/admc/wd
JavaScript (Node.js) https://github.com/webdriverio/webdriverio
JavaScript (Browser) https://github.com/projectxyzio/web2driver
Objective C https://github.com/appium/selenium-objective-c
PHP https://github.com/appium/php-client
C# (.NET) https://github.com/appium/appium-dotnet-driver
RobotFramework https://github.com/jollychang/robotframework-appiumlibrary

AppiumのOrganizationで管理されているものだけではなく、サードパーティー向けもいくつか存在していますね。

ここにもWebdriverIOが出てきました。

AppiumでWebViewをテストする

実は、AppiumにはWebViewのテストを行うための機能も搭載されています。

appium.io

Appiumの起動パラメータでもあるDesired Capabilitiesとして、autoWebview: true を設定しておくと、優先的にコンテキスト*2がWebViewに向いた状態でテストコードを実行できます。このモードについて、上記のサイトでは次のように言及されています。

Once the test is in a web view context the command set that is available is the full Selenium WebDriver API.

コンテキストがWebViewに向いている状態でテストを実施する場合、SeleniumのWebDriver APIがすべて使えるとのことです。ここでいうWebDriver APIは、ブラウザ向けの Remote WebDriver だと思っておけばよさそうです。

本記事での課題である、Cordova製iOSアプリ向けに使う場合には、次の図のような流れになるようです。

f:id:Nkzn:20191126111053p:plain
Appium経由でWebViewを操作する

Cordovaの中にあるWebViewに対して、ブラウザと同じAPIで操作ができそうな道筋が見えてきました。

WebdriverIOでAppiumを扱う

Appiumはどうにでもなりそうなのがわかってきたので、前述の図で左側にいた WebDriver の枠を埋めるツールを選びます。

今回は、WebdriverIOを利用することにしました。

webdriver.io

Appium側のドキュメントにも記載があったとおり、WebdriverIOはAppiumのクライアントとして利用できるライブラリです。WebdriverIOのサイトにも、Appiumのサポートが明記されています。

f:id:Nkzn:20191126114432p:plain
WebdriverIOはAppiumをサポート

構成のイメージとしては、次のような形になるでしょうか。

f:id:Nkzn:20191126115534p:plain
WebdriverIOでAppiumを操作する

概念図が具体的なツール名で埋まりました。それでは実際の使い方の話題に移っていきましょう。

利用の流れ

基本的な流れは公式のGetting StartedでChromeDriver Serviceを動かす方法と大きくは変わりません。

  1. NPMプロジェクトの devDependencies@wdio/cli をインストールする
  2. wdio.config.js を作成・整備する
  3. mochajasmineなどでテストコードを書く
  4. コマンドラインで $ npm test を実行してテストを実施する
    • $ npx wdio wdio.conf.js$ yarn wdio wdio.conf.js でも可

個別の設定の要点も見ていきましょう。

package.json

WebdriverIOからAppiumを操作する場合は、chromedriver@wdio/chromedriver-service の代わりに appium@wdio/appium-service を使用します。

// package.jsonの例
{
  // 略
  "scripts": {
    "test": "wdio wdio.conf.js"
  },
  "devDependencies": {
    "@wdio/appium-service": "^5.16.5", // <= 追加
    "@wdio/cli": "^5.16.6",
    "@wdio/local-runner": "^5.16.6",
    "@wdio/mocha-framework": "^5.16.5",
    "@wdio/spec-reporter": "^5.16.5",
    "@wdio/sync": "^5.16.5",
    "appium": "^1.15.1", // <= 追加
    // 略
  }
}

@wdio/appium-service はWebdriverIOがAppiumサーバーをハンドリングできるようにするためのプラグインです。次に出てくる wdio.conf.js でAppiumサーバーに関する設定をできるようになります。

wdio.conf.js

WebdriverIOの設定ファイルである wdio.conf.js の内容は、ブラウザ向けのそれとはかなり違った雰囲気になります。

まず、@wdio/appium-service のドキュメントにあるとおり、 servicesport を明示する必要があります。

// wdio.conf.jsの例
exports.config = {
  // 略
  // Appiumサーバー(WebDriver API)についての設定
  services: ['appium'], // @wdio/appium-serviceを利用してAppiumサーバーを立ち上げる
  port: 4723, // Appiumサーバーのデフォルトポート
  maxInstances: 1, // 同時に起動できるAppiumサーバーはひとつだけ
  // path: '/', // pathはデフォルトの挙動('/wd/hub')に任せる
  // 略
}

maxInstances: 1 は、Appiumが4723ポートに複数起動するのを防ぐために設定します。(参考: WebdriverIOでAppiumを使う勘所 - DeNA Testing Blog

次に、テスト対象についての設定であるDesired Capabilitiesを設定します。iOSシミュレータをiOS 13.2のiPhone 8で起動して、テストを実施したい場合の書き方です。

// wdio.conf.jsの例
exports.config = {
  // 略
  capabilities: [{
    // プラットフォーム設定
    platformName: 'iOS',
    automationName: 'XCUITest',
    // 使用するデバイスを指定する
    platformVersion: '13.2',
    deviceName: 'iPhone 8',
    // テスト対象のアプリの情報
    bundleId: "com.example.appid",
    app: 'path/to/MyCordovaApp/platforms/ios/build/emulator/HelloCordova.app',
    // テスト中の設定
    autoWebview: true, // コンテキストをWebView優先にする
  }],
  // 略
}

app は相対パスでも構わないため、WebdriverIOによるテストプロジェクトは、Cordovaプロジェクトと同居していても別々でも問題ありません。AWS Device Farm等でテストする場合は、別々のほうが都合がいいケースもあります。

autoWebview: true は既に解説したとおり、「アプリ内のWebViewを操作できるWebDriver API」が提供される、魔法の呪文です。

細かい調整は各々の手元で必要になるかとは思いますが、大枠としてはこのような設定があれば、WebdriverIOで実行したテストコードがAppiumを経由してCordovaアプリ内のWebViewに繋がります。

WebdriverIOの書き味が面白い

アプリ内のWebViewに繋がってしまえば、あとはChromeDriver等を使ってテストをする際と、ほとんど変わらない使用感になります。実際のテストは、次のような形になります。

const assert = require('assert');

// test/specs/button.js
describe('Button', () => {
  it('ボタンを押すとtextBoxが見えなくなる', () => {
    $('#hideButton').click();
    const textBox = $('#textBox');
    assert(textBox.isDisplayed() === false);
  });
});

jQueryのような記法でセレクタを記述すると、WebDriver上でのElementを表現できます。Elementから生えている click()を実行することでWebDriver APIに対してクリックコマンドを発行し、それを受けてAppiumがWebViewを操作する形です。

内部ではWebDriver APIをゴリゴリと叩く非同期処理になっているはずなので、本来はこういった処理を行う場合はasync/awaitを使うことになり、テストコードがawaitだらけになることでしょう(実際にAsync modeにするとそうなります)。

しかし、 @wdio/sync をインストールしておくことで、テストコードを同期的に記述できるようになっています。

{
  // ...
  "devDependencies": {
    // ...
    "@wdio/sync": "^5.16.5",
    // ...
  }
}

ドキュメントによれば、node-fibersを利用して、同期的な実行を実現しているそうです。

AndroidのEspressoのように、テストコードでやりたい操作を素直に記述できるため、とても好ましいと感じました。

To be continued...

手元のNode.jsで実行したテストコードで、Cordovaアプリ内のWebViewを操作する道筋ができました。この方法であれば、普通のWebアプリをテストする際とも共通項が多いため、学習コストを下げる効果が期待できます。

また、この方法であれば、AWS Device FarmのAppium Node.js環境でもテストが実行できます。後編の記事で話題にできればと思います。

後編の、Device Farmで利用する際のコツについて書いた記事を公開しました。

watercelldev.hatenablog.jp

サンプルコード

次のリポジトリでサンプルコードを公開しています。

github.com

参考文献

*1:公式ドキュメントのAppium Conceptsより

*2:Appiumが操作するターゲット

MOBILE CREW NIIGATA 2019にウォーターセルのエンジニアが登壇しました #mcniigata

開発部の中川@Nkznです。

2019年10月11日(金)に、MOBILE CREW NIIGATA 2019がホテルメッツ新潟で開催されました。(もう1ヶ月経ったんですね)

www.mobilecrew.jp

このイベントは、クーネルワークさん、フラー新潟支社さんと弊社の3社が共催したもので、私もスタッフとして微力ながら関わりました。

ウォーターセル社員の発表

ウォーターセルからは、モバイルエンジニアとして、渡邊と私の2名が登壇しました。スライドを簡単にご紹介します。

LiveData and DataBinding実用レポート

渡邊からは、LiveDataとDataBindingについて発表しました。もう5年近く運用されている、アグリノートのAndroidアプリに、LiveData(とViewModel)とDataBindingを組み込んだ事例の紹介です。

状態管理は多くのアプリにとって煩雑になりがちな課題です。公開から何年か経ち、十分にこなれてきた便利なライブラリたちに興味を持ってもらえれば幸いです。

地方IT企業の戦略を広げる 技術選択としてのReact Native

中川からは、アグリテックという特殊な環境におけるB2B(2B)という分野で、どんな技術選択をして戦ってきたかというお話をしました。

speakerdeck.com

発表後のパネルディスカッションでも話しましたが、モバイル運用(MobileOps)エンジニアをどうやって育てたり採用したりすればいいんだろう、というのがこの発表の先にある課題かなと思いました。

また、React Nativeだけやっていると、それはそれでスキルセットが偏りそうなので、チームメンバー全体の成長を考えると、継続的にメンテナンスするネイティブ開発のアプリも1つくらいは持っていた方がよさそう、といったことも最近は考えています。

良いイベントでした

首都圏のイベントではなかなか聞けない話題ばかりの、良いイベントになったと思います。

地方で生きているIT企業だからこそ、地方の課題を身近なものとして理解して、解決に向けて寄り添っていける。そういう企業がどんどん増えていけばいいなと思いました。

できたら来年もまたやりたいですし、そのときはまた多くの参加者の方々とお会いできるのを楽しみにしています。