woshidan's blog

そんなことよりコードにダイブ。

「テストも開発もするモバイルエンジニアのためのXCUITest/Espressoのすすめ」という題でLTをしました

testautomationresearch.connpass.com

要件の細かいことを突いたりテストケースの設計が大好物だったため、そういうのを本職としているテストエンジニアはどういう人たちなのかを知りたかったので「システムテスト自動化カンファレンス2017-2」に参加してきました。

図々しく「テストも開発もするモバイルエンジニアのためのXCUITest/Espressoのすすめ」という題でLTをさせていただいたので、取りいそぎスライドとその補足についてだけでもメモします。

スライド

speakerdeck.com

スライド補足

Appiumはいろんな言語のドライバがあるので好きな言語で書けるという話について

Appiumはブログなどの件数が多いから盛り上がっているという話がありますが、各言語のドライバでQ&Aやブログ記事が割れており、また言語を特定して情報を調べないとある言語の質問について他の言語で返ってくることもあるので*1、そういう数値については3~5分の1に割り引いて考えた方が良いかと思います。

各種言語のドライバの更新状況も言語によって異なっており、たとえば

  • Rubyの最新更新は約3週間前
  • perlの最新更新はREADME.mdで1年前
  • phpの最新更新は5か月前

となっています。不具合があった時に自分でAppiumが利用している各種ツールの状況を調査するコストが取れなければ、更新頻度の少ない言語は避け、いまならCookpad, Mercariが対応すると決めているRubyのドライバを選んだ方がハマりが少ないのではないでしょうか。。

Appiumが安定していなくてつらい話

Appiumは、

  • Appiumクライアント(Appiumクライアントライブラリ=各種言語のdriver, テストスクリプト)
  • Appiumサーバ
  • uiautomator/espresso or instrument/xcuitest

といった構成になっていて*2、テストを動かすために利用しているツールが多いです。

そのせいか、気軽にバージョンアップするとどれかのツールが噛み合わなくて不安定になったり動作が非常に遅くなったりします。

Execution on iOS is extremely slow after upgrading to Appium 1.6.5 · Issue #8717 · appium/appium · GitHub

Appium 1.7.1 Automation is too slow · Issue #739 · facebook/WebDriverAgent · GitHub

前述のドライバの言語がたくさんあることと合わせて関連issueがあちこちに散っており、かつ、大量のログが貼り付けられていることも多く必要な条件が見分けにくいため、調査に時間がかかります、というか、ました。

もともとSelenium系のツールに詳しいチームで対応をとったり、安定するまで待つ*3決定をすれば問題ないかもです。

しかし、現状のEspresso/XCUITestを直接使えば、間にそれなりに大きいツールをはさまないため、その分セットアップや調査が楽でした。

上記の「手間がかかる」「楽でした」という言葉について、調べている最中も悩んでいてこの補足を書いている現在も感じることですが、自分が不勉強で手間を惜しむなんて発想をするから云々みたいなことがずっと頭の中でくるくるしています。

ですが、テストも含めてコードは必要でなくなったら捨てたいです。気軽に捨てられるためには学習コストやメンテナンスコストも含めた手間は小さいほうがよいのでは、ということも一緒にぐるぐるしています。

LTタイトルに「モバイルエンジニア」を含めていますが、自分がアプリ開発者としての環境に慣れ親しんでいるのでEspresso/XCUITestで作ったテストなら気軽に捨てられるなぁ、という感じです。

E2Eテストが全部Espresso/XCUITestでいいかという話

もともと開発者*4の自分としてはAppiumで自動テストが書きやすい範囲とEspresso/XCUITestで自動テストが書きやすい範囲が被っているように感じました。それな らば、自動化しやすい部分は動かしやすいEspresso/XCUITestを使い、サーバとの連携、プッシュ通知であったりapk/ipaの更新時の挙動など自動化しにくい部分は手動テストと組み合わせればよい、と考えていました。

とくに、専用のQAチームを置けるほど規模が大きくないチームでE2Eテストが必要な時は「手動 + Appium」より「手動 + Espresso/XCUITest」の方が楽では~くらいの勢いでした。しかし、会場に行ってみると自分が想像していた以上にリリース用apk/ipaを用いたテストを重視する雰囲気だったため、やや場違いとなりもうしわけなかったです。

ただ、それぞれのプラットフォームのツールに乗っかって開発と同じIDEでテストを書くことにすると、テスト時の動作を入ってる変数付きで再現することができます。これにより、テストが悪いか/コードが悪いかの切り分けがかなり容易になり、開発しながらテストを書くハードルもぐっと下がるのでそれだけはお伝えしたかった(誰に。

遅くなった理由

もうちょっとややこしいレイアウトやもうちょっと新しいバージョンでもう一回Espresso Test Recorderの動作確認しようかと思ったらGradle Projectのリフレッシュがなかなか終わらなくてテンパってました。はい。。

最後に運営の皆様ありがとうございました。

現場からは以上です。

*1:たとえば http://discuss.appium.io/t/how-to-close-application-between-tests/1168 . phpの質問についてRubyJavaのコードがかえってきている

*2:https://github.com/appium/appiumhttp://www.atmarkit.co.jp/ait/articles/1504/27/news025.html を元にしています

*3:このことを考えると、更新が活発な言語を選択した方が良いと思います

*4:というか今もテスト設計の方が割いてる時間が多いだけで社内の肩書きとして開発者

xcodebuild testのテスト対象のデバイスやテストケースをappiumぽく簡単に指定できるラッパースクリプトを書いてみた

この記事はiOS2 Advent Calendar 2017の3日目の記事です。

xcodebuild testのテスト対象のデバイスやテストケースをややappiumみたいに簡単に指定できるラッパースクリプトを書いてみました。 なお、CocoaPodsでライブラリ管理をしているプロジェクトを対象としています。CocoaPodsをご利用でない場合は、適宜 -workspace-project オプションに読み替えていただけると幸いです。

スクリプト

スクリプト本体 xcode-wrapper.sh

#!/bin/bash

# configuration for project, written here
WORKSPACE="ExmapleApp.xcworkspace"
APP_SCHEME="ExmapleApp"
TEST_SCHEME="ExmapleAppTests"
BUILD_CONFIG="Debug"

# configuration for test target device written in caps.json
TARGET_LABEL=$(jq .target < ./caps.json)
DEVICE_NAME=$(jq .[$TARGET_LABEL]."deviceName" < ./caps.json | tr -d '"')
PLATFORM_VERSION=$(jq .[$TARGET_LABEL]."platformVersion" < ./caps.json | tr -d '"')
DEVICE_ID=$(jq .[$TARGET_LABEL]."udid" < ./caps.json | tr -d '"')
DEVICE_TYPE=$(jq .[$TARGET_LABEL]."type" < ./caps.json | tr -d '"')

build() {
  if [ "$DEVICE_TYPE" = "simulator" ]
  then
    # refs: https://qiita.com/roothybrid7/items/19f0aeeee98573dab2a6
    xcodebuild \
      -sdk iphonesimulator \
      -destination "platform=iOS Simulator,name=${DEVICE_NAME},OS=${PLATFORM_VERSION}" \
      -configuration "$BUILD_CONFIG" \
      -workspace "$WORKSPACE" -scheme "$APP_SCHEME" \
      build
  else
    xcodebuild \
      -sdk iphoneos \
      -destination "platform=iOS,id=${DEVICE_ID}" \
      -configuration "$BUILD_CONFIG" \
      -workspace "$WORKSPACE" -scheme "$APP_SCHEME" \
      build
  fi
}

test() {
  if [ "$DEVICE_TYPE" = "simulator" ]
  then
    xcodebuild -workspace $WORKSPACE \
      -scheme "$TEST_SCHEME" \
      -sdk iphonesimulator \
      "$TEST_CASE_OPTION" \
      -destination "platform=iOS Simulator,name=${DEVICE_NAME},OS=${PLATFORM_VERSION}" test
  else
    xcodebuild -workspace $WORKSPACE \
      -scheme "$TEST_SCHEME" \
      -sdk iphoneos \
      "$TEST_CASE_OPTION" \
      -destination "platform=iOS,id=${DEVICE_ID}" test
  fi
}

if [ $1 == "build" ]; then
    build
fi

if [ $1 == "test" ]; then
    i=1
    for OPT in "$@"
    do
        if [ $OPT = "-test" ]; then
            TEST_CASE_OPTION="-only-testing:${TEST_SCHEME}/${@:$(expr $i + 2):1}"
            test
            exit 0
        fi
        i=$(expr $i + 1)
    done
    test
fi

設定ファイル caps.json

"target" の部分を変更してテストを実行するデバイスを切り替えます。

{
  "target": "iPhone5", // テストで使うデバイスの指定
  "iPhone5": {
    "type": "simulator", // シミュレータ
    "deviceName": "iPhone 5",
    "platformVersion": "9.3",
    "udid": "" // シミュレータの指定では使わない
  },
  "iPhone6": {
    "type": "simulator", // シミュレータ
    "deviceName": "iPhone 6",
    "platformVersion": "10.0",
    "udid": "" // シミュレータの指定では使わない
  },
  "iPhone7": {
    "type": "simulator", // シミュレータ
    "deviceName": "iPhone 7",
    "platformVersion": "11.1",
    "udid": "" // シミュレータの指定では使わない
  },
   "iPad": {
    "type": "iphoneos", // 実機
    "deviceName": "woshidanのiPad", "" // 実機の指定では使わない
    "platformVersion": "10.3",
    "udid": "DUMMY_ID"
  }
}

使用例

# 全てのテストを実行する
$ ./xcode-wrapper.sh test

# ExmapleTestクラスにおいたテストをすべて実行する
$ ./xcode-wrapper.sh test -test ExmapleTest

# ExmapleTestクラスにおいたテストメソッドtestAを実行する
$ ./xcode-wrapper.sh test -test ExmapleTest/testA

# アプリをビルドする
$ ./xcode-wrapper.sh build

補足

実行されるテストを指定したい

CLIから見守るだけとはいえ、毎回全てのテストを実行するのは時間がかかります。 xcodebuild test サブコマンドは only-testing オプションを用いることで実行するテストを指定することができます。

https://stackoverflow.com/questions/39427263/how-to-use-xcodebuild-with-only-testing-and-skip-testing-flag

上記スクリプトでは、-test オプションを指定することで only-testing オプションで指定する文字列を与えることができます。スクリプト自体は -test オプションなしでも利用可能で、その場合は全てのテストを実行します。

利用できるデバイスの名前やID, OSバージョンの組み合わせについて

caps.json に書くシミュレータや実機デバイスの名前、OSバージョン、IDなどがわからない場合はXCodeAdd Simulators ... の画面か $ instruments -s devices コマンドより確認できます。

XCodeのデバイス管理画面から

f:id:woshidan:20171202232044p:plain

ターミナルから

$ instruments -s devices
...
iPhone SE (10.0) [7DB19BC4-5A56-4D40-93BD-90F551713043] (Simulator)
iPhone SE (10.3.1) [10FE1E72-D129-4593-A18B-AE85F4D9DC78] (Simulator)
iPhone SE (11.1) [C511B853-09C3-4599-A88F-737296F02DED] (Simulator)
iPhone X (11.1) [95022FBF-DDDB-46A8-8978-0129C0A6142D] (Simulator)
...

もっと色々書こうかと思ったのですが力尽きたので現場からは以上です。

XCode Tools CLIのバージョンアップとビルドに必要なパラメータ、権限の変化について

お仕事の都合で古いOSに対してもXCode Tools CLIを使ってビルドする必要があって調べてみても要領を得なかったんだけど少しわかった気がするので、わかったところまで整理してみます*1

アプリとして実機やシミュレータにインストールできるファイルの種類について

説明
.app xcrun simctl を経由して iPhone simulatorへインストールできる。 xcodebuild build コマンドやXCodeでシミュレータへ向けてビルドすることで作成可能
.ipa アプリストアへリリースしたり、実機へデプロイするときに作成する必要があるファイル形式。 .app.xcarchive ファイルから開発者の証明書を何らかの形でアタッチするようなプロセスを経て作成

参考

XCode7時代の .ipa 作成方法とXCode8時代の .ipa 作成方法

  • XCodeが7の頃は .ipa 作成に関して .app ファイルを渡せば xcrunPackageApplication がうまくやってくれていた
  • XCode8.3. で PackageApplication が廃止されてしまった
    • XCode8.3.2 を使う場合 xcrun: error: unable to find utility "PackageApplication", not a developer tool or in PATH というメッセージが出るようになっている
  • このため、 xcrun PackageApplication の代わりに xcodebuild archive を使う必要がある
    • xcodebuild.xcarchive を作成した後、 .ipa を作成する際に exportProvisioningProfile などを自分で設定する必要がある
    • これの具体的なパス (SampleXXX的なノリなのではなく 'MyMobileApp Distribution Profile' みたいなのりでファイル名などが想像しにくい例が多い) がよくわからなくてつらい

参考

XCode 8.2 -> XCode 8.3 で Automatically manage signing が有効になっているプロジェクトにおいて .ipa へのエクスポートが失敗するように

  • [試せていない] exportOptions.plistmethod の値が development でない場合、XCodeでビルドしているときに、ログインしているアカウントが Admin でない場合、、証明書(開発者登録. キーチェーンに登録している) と Provisioning Profile (開発者とappIdに紐づいている) が揃っていても権限がたりなくて export に失敗する
    • [試せていない] CI用にadminにする?
  • [試せていない] xcodebuild -exportArchive -exportOptionsPlist /PATH/exportOptions.plist で指定する exportOptions.plistmethod の値を development にすればうまくいくかも?
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>development</string>
    <key>teamID</key>
    <string>GLPGBQ39X8</string>
</dict>
</plist>

参考

どうすればいいかわかったら今後追記しますが、詳しい人教えて欲しい感じです。

*1:上長許可あり

ERBとCSVを使って簡単な自分用ドリルを作る

最近シェルの勉強をしたいんですが、以前SQLを勉強した時のように基本的なシェルを反復して書くのが一番では? と思って、ドリルのようなものが欲しくなりました。

そこで、CSVにお題とお題の答えを書き連ねたらアコーディオンで答えを出したり隠したりしてくれるHTMLを出すコードと、そのHTMLを見るアプリ*1を書いたのでポスト。

github.com

使い方は、

$ ruby generator/generator.rb input.csv > drill.html

を実行して drill.html をブラウザで開く、です。アプリで見る場合はさらに

$ cp drill.html cp drill.html CSDriller/app/assets/drill.html

とした後、一緒についているAndroidプロジェクトをビルドしてください。

実際動かしてみると下記のような感じです。

参考

qiita.com

特に上記記事のアコーディオンがよすぎて作りたくなったので多謝。

*1:といってもAndroidのWebViewに突っ込むだけ

Cocos2d-x 2.2.6をMac OS Sierraで動くようにするまでのメモ

仕事でやってて面白かったので許可を取って公開。

環境

スクリーンショット 2017-10-19 13.30.52.png (44.2 kB)

メモ

プロジェクトの作成

python create_project.py -project MyGame -package com.MyCompany.AwesomeGame -language cpp

Eclipse インストーラからJavaIDEをインストールして環境変数を設定

  • Help > Install New SoftWare
    • Work with の欄に https://dl-ssl.google.com/android/eclipse を入力
      • Developer Tools をダウンロード
      • Eclipseを再起動
      • DLしたらどこのAndroid SDK を使うか聞かれるので Andorid Studioで使っているパス /Users/woshidan/Library/Android/sdk あたりでも答えておく (設定した値は Preferences > Android からでも確認可能. Android SDKのバージョンが v26 以上なら後の方に書くようにEclipse用に別途古いAndroid SDKを用意した方が良い)

cocos2dxのプロジェクトをインポートとプロジェクト間の依存関係の設定

  • Import > Andorid > Existing Android Code Into Workspacecocos2d-x-2.2.6/cocos2dxplatform/andorid/java をインポート
    • Copy projects into workspace にチェックしておく
  • Import > Andorid > Existing Android Code Into Workspacecocos2d-x-2.2.6/cocos2dx/ProjectName をインポート
    • Copy projects into workspace にチェックしない
    • ProjectName のAndroidManifest.xml 中の minSDKVersion が小さすぎるので14くらいにあげておく
  • PackageManager 上で ProjectName の上を右クリックして Properties > Android を開き、Libraryのところで、libcocos2dxを指すよう設定する
    • isLibrary のチェックはしない(アプリプロジェクトにisLibraryのチェックをするとapkが生成されない)

アプリ側のプロジェクトのLinked Resourcesの修正

  • プロジェクトのところで右クリックして Properteis > Resources > Linked ResourcesLinked Resources のタブを確認。Classes, cocos2dx, extensions, scropting の壊れているパスを修正

古めのNDKを用意してNDK_ROOTの環境変数を設定

  • 古めのNDK(Android NDK, Revision 10e)を https://developer.android.com/ndk/downloads/older_releases.html からDL
    • EclipsePreferences > Android > NDK の項目の NDK LocationにDLしてきたNDKを配置したパスを書く
  • Preferences > Android > NDK に設定したパスが Eclipse に見えてないことがあるので C/C++ > Build > Environment にも NDK_ROOT として追加
  • Bulid Project した結果、 build_native.sh は走ってくれたが。。

トラブルシューティング

Could not find BuildTest.apk! ...

Unknown error: Unable to build: the file dx.jar was not loaded from the SDK folder!

Eclipse起動時に Failed to get the required ADT version number from the SDK

エミュレータを起動しようとしたら[Start]が押せず詳細を確認したら Google Nexus 5X No longer exists as a device

Description Resource Path Location TypeThe container 'Android Dependencies' references non existing library'cocos2d-x-2.2.6/cocos2dx/platform/android/java/bin/libcocos2dx.jar' ...

  • http://renkaze.seesaa.net/article/405756568.html
  • libcocos2xd のプロジェクトで jar を吐き出すパスが別のパスに設定されている
  • libcocos2dx のプロジェクトで右クリックして Properties > Java Build Path < Source の下側の Default output folderlibcocos2dx/bin/classes に設定

java.lang.IllegalArgumentException: No configs match configSpec というエラーでエミュレータで起動するとクラッシュする

Eclipseエミュレータを起動しようとしたら PANIC: Missing emulator engine program for 'arm' CPUS... と出る

Google Nexus 5X no longer exists as a device とでて Nexus 5 のスキンのエミュレータがつけない

まとめ

そろそろEclipseAndroidを開発するのをやめたほうがいいのでは。

現場からは以上です。

ウェブオペレーション サイト運用管理の実践テクニックを読んだ

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

ウェブオペレーション ―サイト運用管理の実践テクニック (THEORY/IN/PRACTICE)

前職で研修に使われていた記憶があり、いい本なのかなーと思って読みました。 運用系の仕事をしているわけではないので、一ヶ月強かけてなんとか読みおおせた感じです。

各章はかなり生き生きとした文章で、場合によっては「これはもしかして飲み会の書き起こしではないだろうか」「これ開発者の前に出したらグーパンされても文句言えないのでは」と感じた章もいくつかあり、いんたーねっつの進歩を体感することができます。

なんとか読みおおせた印象としては

  • ウェブサービスのサーバ構成について、それぞれのサーバの大まかなイメージが頭に描けるようになった
    • リバースプロキシで静的ファイルを返したりロードバランサの役目もさせて負荷分散をする、一回取得したデータをキャッシュにつっこんで奥のDBにリクエストがいかないようにする、リクエストが大量に来てキャッシュが切れた時DBにリクエストが殺到してダウンする、みたいな
  • 結構前の章でいいよって言われていることが次の章でそうでもないよって言われてたりして、道具を使ったり人の意見を採用する前に要件を確認するのは大事だな、と思えた
  • もともとあまりシェルになじみのない人でなんでもLL言語+RDBMSで考えがちだったのですが、監視のシステムの実装例でawkなどが出てきたり、道具の得意分野に合わせていろんな道具を操ることについて考えさせられた
  • Dynamoの論文の特徴が面白かった
    • Consistent hash, read repair, hinted handoffが書いてあったよと紹介されていて、特に気になったのは read repair.
  • 高可用性(リクエストがあったらいつでも応答できる)と応答速度/パフォーマンス(速くレスポンスが返ってくる)と一貫性(データに矛盾が生じない)が違う、相反しない、という例がくどくどと書かれていて身にしみた
    • 分割耐性はまだ自信がない
  • みんな結構データセンタや社内システムの外からファイアウォールping弾かれてて面白かった
  • サーバの監視システムについて、死活監視・リソース監視(Nagiosなど)・それらの結果の表示(Cacti, Muninなど)、みたいなソフトごとの得意領域があるんだなみたいなイメージが湧いた
  • レスポンスの配信の節で、サーバとクライアントの間に一度に取得できるオブジェクト数の上限があるということを知らなかったため、デバッグコンソールを眺めてみたり

でした。

個人的に特に面白かったのは、6章、9章、12章、15章。

6章の学生時代のサイト運営経験から右往左往しながらシステムを運用していく様子に共感を覚え、9章の急増したトラフィックへの障害対応の文章を読んでリクエストがどのサーバに順に到達するかのイメージを描き、12章でこの人本当にMySQL好きなんだなという気持ちになり*1、15章で高分散型*2NoSQLの監視ソフトウェアの違いや比較的小さい規模で動かすものと大きい規模で動かすものの思想の違いに思いを馳せたり。。

運用経験のない人が一人で読むとどうなのかな、と思いつつ、ここで紹介されている技術は今の時代ではそれなりに枯れていたり廃れていたり流行っていたりするためインターネットにまとめられた記事を適宜参照しながら進めました。しかしまあ、早口でまくしたてるクセがある他部署の先輩の運用談義を聞かされている風味もあり、本開いてる間はなんだか忙しかったのでした...。

前述の文体が気になる点や2011年出版でやや古いところもあるので、この本のポジションに収まる新しい本とかあったらそちらも読んでみたいです。

現場からは以上です。

*1:MySQLコンサルタントの方が書いてるのでそりゃそーだって話なんだけど、これだけMySQL好きな人になら一回コンサルされたいなという感じでした

*2:いまの言い方だとビッグデータ用?

Athenaでクエリの実行結果を使い回すために最近投げたクエリとそのquery_execution_idを記録する

Amazon Athena では

S3上にあるファイルをスキャンした量に応じた料金 + S3をスキャンするためにGETしたリクエスト数(+ファイル容量)

に応じて課金されます。

また、Athenaでは一回SQLを実行した時に query_execution_id が発行され、同じ query_execution_id を使って GetQueryResults に複数回リクエストを送ると二回目以降は一回目の実行結果が再利用され、新しいクエリは実行されません*1

このため、一度検索した結果が使い回せるのであれば、 query_execution_id を控えておいて結果を再利用した方が良いです。

というわけで、雑に CSV で直近で実行した クエリquery_execution_id の対応を控えておいて、実行しようとしたクエリがすでに実行済みであれば教えてくれるスクリプトを書いたのでポスト。

gem にしようかと思ったけどそれほど大きくなかったのでひとまずこれで。

# athena_query_keeper.rb
require "csv"

class AthenaQueryKeeper
  def initialize(csv_path, keepe_count=20)
    @csv_path = csv_path
    @keepe_count = keepe_count
    @keeped_queries = []

    begin
      CSV.foreach(@csv_path) do |row|
        @keeped_queries.push({ time: row[0], execution_id: row[1], query_string: row[2] })
      end
    rescue Exception => ex
      puts ex
      puts "query keeped csv(path=#{csv_path}) was not found or format was broken."
      puts "new csv will be created."
    end
  end

  def keep(excution_id, query_string)
    if @keeped_queries.count >= @keepe_count
      @keeped_queries.slice!(0, @keeped_queries.count + 1 - @keepe_count)
    end

    @keeped_queries.push({ time: Time.now.to_i, execution_id: excution_id, query_string: query_string })
    flush
  end

  def flush
    CSV.open(@csv_path, 'w') do |csv|
      @keeped_queries.each do |query|
        csv << [ query[:time], query[:execution_id], query[:query_string] ]
      end
    end
  end

  def keeped_execution_id_for(query_string)
    keeped_query = @keeped_queries.find { |query| query[:query_string] == query_string }

    if keeped_query.nil?
      puts "query: #{query_string.slice(0, 60)} ... has been not executed recently."
      ""
    else
      puts "query: \"#{query_string.slice(0, 60)} ... \" has been executed recently."
      puts "you should reuse that result unless the result update must be used."
      keeped_query[:execution_id]
    end
  end
end
# test.rb
require 'aws-sdk-athena'
require "#{File.dirname(__FILE__)}/athena_query_keeper.rb"

query_string = ARGV[0]
client = Aws::Athena::Client.new

begin
  query_execution_id = nil
  athena_query_keeper = AthenaQueryKeeper.new("./sample.csv", 20)
  keeped_query_id = athena_query_keeper.keeped_execution_id_for query_string
  if keeped_query_id.empty?
    start_query_response = client.start_query_execution({
        query_string: query_string,
        query_execution_context: {
           database: "mydatabase",
        },
        result_configuration: {
          output_location: "s3://example-woshidan-test/athena_query_result"
        },
    })

    sleep(3)

    query_execution_id = start_query_response.query_execution_id
    athena_query_keeper.keep(query_execution_id, query_string)
  else
    puts "reuse query result for id=#{keeped_query_id}."
    query_execution_id = keeped_query_id
  end

  puts "query_execution_id: #{query_execution_id}"

  get_query_response = client.get_query_results({
    query_execution_id: query_execution_id
  })

  get_query_response.inspect
rescue Aws::Athena::Errors::InvalidRequestException => ex
  puts ex.inspect
ensure
  puts "query request ended."
end
$ ruby test.rb 'SELECT * FROM mydatabase."athena_logs_20171003_app" WHERE user_id = "tester" LIMIT 10;'
query: SELECT * FROM mydatabase."athena_logs_20171003_app" WHERE us ... has been not executed recently.
result_csv_file_key: 32e86285-e0c5-4329-973d-38bd039945e4
#<Aws::Athena::Errors::InvalidRequestException: Query did not finish successfully. Final query state: FAILED>
query request ended.

$ ruby test.rb 'SELECT * FROM mydatabase."athena_logs_20171003_app" WHERE user_id = "tester" LIMIT 10;'
query: "SELECT * FROM mydatabase."athena_logs_20171003_app" WHERE us ... " has been executed recently.
you should reuse that result unless the result update must be used.
reuse query result for id=32e86285-e0c5-4329-973d-38bd039945e4
result_csv_file_key: 32e86285-e0c5-4329-973d-38bd039945e4
#<Aws::Athena::Errors::InvalidRequestException: Query did not finish successfully. Final query state: FAILED>
query request ended. 

参考

*1:管理画面のHistoryなどで確認できます