シェアフル Advent Calendar 2018 17日目の記事です。
ある程度アプリの機能が揃ってきたので、そろそろテスト追加フェーズかなという感じでe2eの実装してみた
react-nativeでe2eの実装
react-nativeではDetoxを使ってe2eを実装していきます
また↓を使えばexpoでもe2eが可能です
GitHub - expo/detox-expo-helpers
detox-tools/packages/expo-detox-hook at master · expo/detox-tools · GitHub
Detox実装
事前準備
ここにある通りでOK
Detox/Introduction.GettingStarted.md at master · wix/Detox · GitHub
DEMOを動かしてみよう
こちらが、動かせるサンプルです
READMEの通りにセットアップすれば、こんな感じに動きます
早速アプリ組み込んでみる
インストール & 設定
mochaとjestどちらでも使えますが、ここではDEMOと同じでmochaを選択
npm i -D detox detox-expo-helpers expo-detox-hook mocha
初期テストコードをcliから追加
npm install -g detox-cli detox init -r mocha
package.jsonにdetoxの設定を追加
"detox": { "configurations": { "ios.sim": { "binaryPath": "bin/Exponent.app", "type": "ios.simulator", "name": "iPhone 7" } } }
package.jsonにe2eのコマンドを追加
"scripts": { "e2e": "detox test --configuration ios.sim"
これで初期設定はOK
テストコード
テストの書き方
アプリ側に 「testID」を埋め込んでテストコード側で、そこを対象に操作していきます
テキストの判定
■アプリ
<View> <Text>プランの登録はありません</Text> </View>
■テストコード
await expect(element(by.label("プランの登録はありません"))).toBeVisible();
ボタンをタップする
■アプリ
<TouchableOpacity onPress={this.props.onCreate} testID="addSchedule"> <Ionicons name="ios-add-circle" size={80} color="#4DB6AC" /> </TouchableOpacity>
■テストコード
await element(by.id("addSchedule")).tap();
テキストを入力する
■アプリ
<TextInput placeholder="タイトルを入力" placeholderTextColor="#ffffff" onChangeText={title => this.setState({ title })} defaultValue={this.props.title} testID="inputTextScheduleDetailTitle" />
■テストコード
await element(by.id("inputTextScheduleDetailTitle")).replaceText("新宿駅");
大体、これくらい覚えておけばOK
実装
スケジュール登録を一通り操作してスクリーンショットを取る形式で実装したら、こんな感じになった
https://github.com/wheatandcat/Shiori/blob/master/ShioriNative/e2e/firstTest.spec.js
■ShioriNative/e2e/firstTest.spec.js
const { reloadApp } = require("detox-expo-helpers"); const { takeScreenshot } = require("./helpers"); describe("Example", () => { beforeEach(async () => { await reloadApp(); }); afterEach(async () => { takeScreenshot(); }); it("初期表示", async () => { await expect(element(by.label("プランの登録はありません"))).toBeVisible(); }); it("スケジュール追加", async () => { await element(by.id("addSchedule")).tap(); await element(by.id("inputTextTitle")).tap(); await element(by.id("inputTextTitle")).replaceText("葛西臨海公園"); await element(by.id("completion")).tap(); takeScreenshot(); await element(by.id("addScheduleDetail")).tap(); takeScreenshot(); await element(by.id("inputTextScheduleDetailTitle")).tap(); await element(by.id("inputTextScheduleDetailTitle")).replaceText("新宿駅"); await element(by.id("inputTextScheduleDetailMemo")).tap(); await element(by.id("inputTextScheduleDetailMemo")).replaceText( "8:00に西口に集合する" ); takeScreenshot(); await element(by.id("saveScheduleDetail")).tap(); await element(by.id("addScheduleDetail")).tap(); await element(by.id("inputTextScheduleDetailTitle")).tap(); await element(by.id("inputTextScheduleDetailTitle")).replaceText( "葛西臨海公園" ); await element(by.id("inputTextScheduleDetailMemo")).tap(); await element(by.id("inputTextScheduleDetailMemo")).replaceText( "行く場所:砂浜、観覧車、水族園" ); await element(by.id("saveScheduleDetail")).tap(); await element(by.id("addScheduleDetail")).tap(); await element(by.id("inputTextScheduleDetailTitle")).tap(); await element(by.id("inputTextScheduleDetailTitle")).replaceText( "葛西臨海公園水上バス" ); await element(by.id("saveScheduleDetail")).tap(); await element(by.id("addScheduleDetail")).tap(); await element(by.id("inputTextScheduleDetailTitle")).tap(); await element(by.id("inputTextScheduleDetailTitle")).replaceText( "浅草寺二天門前" ); await element(by.id("saveScheduleDetail")).tap(); takeScreenshot(); await element(by.id("saveSchedule")).tap(); }); });
e2e実行
以下のコマンドで実行される
npm start
npmrun e2e
こんな感じで動作します
実装してみた感想
ポジティブ
- 予想以上に手頃に実装できる
- 「testID」はキモいと思っていたが、完全にテスト用と割り切ってしまえば意外と気にならない
- スタイル崩れチェックぐらいならスクリーンショットのチェックだけでもOKかも
ネガティブ
ciで実行できるのかな?その辺が、まだよく分かってなので、もう少し調べる予定