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

2016年振り返りと2017年へ

2016年振り返りと2017年へ – 徒然なるままに

2016年に新たに読んだ書籍群

こちらにもメモ。

2016年に新たに読んだ書籍群を振り返ってkazucocoa.wordpress.com

Erlang/Elixirの再帰計算におけるnon-tail recursionとtail recursion

ちょっと印象的だったので。

再帰計算を行う関数の最後が、別の関数呼び出しかどうか、という違いです。

non tail recursion

defmodule ListHelper do
  def sum([]), do: 0

  def sum([head | tail]) do
    head + sum(tail)
  end
end

https://github.com/sasa1977/elixir-in-action/tree/master/code_samples/ch03

tail recursion

defmodule ListHelper do
  def sum(list) do
    do_sum(0, list)
  end

  defp do_sum(current_sum, []) do
    current_sum
  end

  defp do_sum(current_sum, [head | tail]) do
    new_sum = head + current_sum
    do_sum(new_sum, tail)
  end
end

https://github.com/sasa1977/elixir-in-action/blob/master/code_samples/ch03/sum_list_tc.ex

Dive to source code

Elixirには、再帰表現をまとめたコードとして Enum.reduce(collection, acc, fun) なんかを提供しています。 この中身を少しみてみましょう。

Elixirの の再帰箇所は以下のように書かれています。

def reduce(collection, acc, fun) when is_list(collection) do
  :lists.foldl(fun, acc, collection)
end

Erlangの資料によると、 :lists.foldl(Fun, Acc0, List) はtail recursionとのこと。 こちら

foldl/3 is tail recursive and would usually be preferred to foldr/3.

ということは、 Enum.reduce(collection, acc, fun) はtail recursionなのですね。

コード追うと Enum.reduce/2Enum.reduce/3 を結局は呼ぶので、reduceはtail recursionで実装されていて、巨大なリストに対してもちゃんと再帰計算ができることを重視されているのですね。 Elixirの List.foldr/3List.foldl/3 もそれらを呼び出しているので、tail recursionなのですね。

利点として、このtail recursionの場合、メモリの消費を増やすことなく、無限に再帰呼び出しができると。 一方、これは手続き型な書き方な側面や、性能的にnon tail recursionよりも遅い場合があります。

なるほどなるほど。


こちらから、Elixirのシンタックスハイライトきくのどれなのだろう、と投稿してみましたが、Qiitaくらいなのですね?なるほど...

Appiumを使ってiOS/Androidの要素を取得する方法の数々

appium ios android software_test mobile

Selenium/Appium Advent Calendar 2014の23番目、12月23日の記事です。少しメモしていたことがちょうどAppiumの話しだったので、ついでに。

テストエンジニアしています @Kazu_cocoaです。普段はこちらに細々ブログを書いているのですが、まとめ的なものなのでこちらに書いてみようと思います。

七転び八起きなAppiumを使ったモバイルテストのたしなみにて、Appiumを使ったモバイルアプリのテスト記述に関して、出くわしたつらみを書きました。

一方で、もっと単純な、 どうやってAppiumを実行した時にその要素を特定するか という箇所に関してまとめて言及されているような資料がないように思えたので、少しそのあたりを書いておきます。いずれも、実際に使ってみてのものなので、これ以上に使い方があるかもしれません。

環境

iOS

1. accessibilityLabelを使う

var accessibilityLabel: String!
@property(nonatomic, copy) NSString *accessibilityLabel

https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIAccessibility_Protocol/index.html#//apple_ref/occ/instp/NSObject/accessibilityLabel

iOSが備える、Accessibilityのテキスト読み上げ機能時に読み上げられる文字列を付加するメソッドです。この要素をコード中に埋め込むことで、Appiumはaccessibility_id strategy時に要素を特定可能です。

2. accessibilityIdentifierを使う

var accessibilityIdentifier: String! { get set }
@property(nonatomic, copy) NSString *accessibilityIdentifier

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAccessibilityIdentification_Protocol/#//apple_ref/occ/intfp/UIAccessibilityIdentification/accessibilityIdentifier

UIAutomationの機構により取得可能な要素を付加するメソッドです。この要素をコード中に埋め込むことで、Appiumはaccessibility_id strategy時に要素を特定可能です。

個人的には accessibilityIdentifier のほうがLabelよりオススメです。 accessibilityLabel は、テキスト読み上げ機能を使った場合、ユーザが認識できる情報になります。

ユーザへの配慮を考えるのであれば、そちらはNSLocalizedStringなどを使い、ローカライズ可能な対象にすべき。一方、accessibilityIdentifier はUIAutomator用に用意された要素の追加なので、ユーザへ配慮する必要のないところです。こちらは、保守性向上のためにローカライズなしで指定することができます。

3. XPathを使う

XPathの利用はAppium提供のInspectorを使うことで、容易に利用することができます。

find_element(xpath: '//UIAApplication[1]/UIAWindow[1]/')

ただし、特定の要素を指定できるわけではなく、要素取得時に表示されている画面に存在するパスへアクセスするだけなので以下のような用途に使えません。

  • xxxの要素が表示されるまでwaitで待ち、表示されたらyyyの操作を行う

もっと柔軟に書くこともできて、

find_element(xpath: '//UIATableCell[@name="Videos"]')

のようにXPathで取得できる要素のうち、特定要素を指定することもできます。ただし、ここら辺までになるとinspectorを使ってどのUIAElementが指定されるのか、とかaccessibility_id指定よりも知見を必要とするので少し面倒です。

4. UIAElementを指定して使う

UIAutomation越しにButton要素などを取得するためのクラスとして、UIAElementというものが定義されています。こちらはAppiumではClassのstrategyを使うことで見つけることができます。

find_element(class: 'UIATextField')

https://developer.apple.com/library/prerelease/ios/documentation/ToolsLanguages/Reference/UIAElementClassReference/index.html

5. UIAutomationをそのまま使う

UIAutomationは、JavaScriptのインタフェースを備えています。そのため、以下のようにいざとなればUIAutomationのJSをそのまま使うことができます。

execute_script("$.mainApp().alert().buttons()['#{text}'].tap();")
execute_script("au.getElementByAccessibilityId('#{element}').scrollToVisible();")

https://developer.apple.com/library/ios/documentation/DeveloperTools/Reference/UIAutomationRef/_index.html

6. 座標の直接指定


他にも要素の取得方法はいくつかありますが、ここらへんがよく使われるものかと思います。個人的には、accessibilityIdentifierを中心に使っています。XPathやUIAutomationの直接指定はちょっと凝ったことをしたい時に使っています。

Android

1. contentDescriptionを使う

contentDescriptionは、音声読み上げ機能をONにしているとき、なんらかの要素タップ時に読まれる、その要素の説明文になります。Appiumではaccessiblity_idを使って要素を特定することができます。

指定方法は以下。

  • Layout.xmlへ指定する
android:contentDescription=”sample”

http://developer.android.com/training/accessibility/accessible-app.html

  • setContentDescriptionして指定する
    • Viewを継承している要素に対しては、以下のsetContentDescriptionを使うことができます。
View.setContentDescription("sample")

http://developer.android.com/reference/android/view/View.html#setContentDescription(java.lang.CharSequence)

これらは以下のメソッドを使うので、API Level 14以上が最低限必要になります。

http://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.html#findAccessibilityNodeInfosByText(java.lang.String)

一方、Appiumの要件が4.2以上を要求するので、このAPI縛りは特に問題はなさそうです。(それ以下はSelendroidを使うことになります)

2. resourceIDを使う

API Level 18から、 findAccessibilityNodeInfosByViewId というAPIを経由することでresource idを取得できるようになったそうです。Appiumでは、以下の通りid strategyを使うことでresource idを直接指定することができるようになっています。こちらはOSでいうと4.4になります。

find_element(id: "#{package}:id/sample")
  • findAccessibilityNodeInfosByViewId
Finds AccessibilityNodeInfos by the fully qualified view id's resource name where a fully qualified id is of the from "package:id/id_resource_name". For example, if the target application's package is "foo.bar" and the id resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".

http://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.html#findAccessibilityNodeInfosByViewId(java.lang.String)

これにより、iOSのaccessibityIdentifierのように、ユーザに影響がない範囲でテスト自動化のための要素特定ができるようになりそうです。

3. android.widget.TextViewなどのクラスを指定する

iOSのUIAElementと同じです。

find_element(class: 'android.widget.TextView')

4. XPathを使う

以下のように続くXPathを指定することでも要素を指定できます。

FrameLayout/View/FrameLayout/View/LinearLayout....

ただし、Androidは画面に見えている要素に対してXPathを割り当てているようで、iOSに比べて動的に割り当てが変わりすぎて使いにくかったです。(使い方が間違っているかもしれませんが)

5. 座標の直接指定

6. ActionBarの要素指定

Androidアプリでは、ActionBarとして以下のクラスを利用することができます。

Action Bar | Android Developers

このクラスで使われる the [1] app icon に該当する箇所、以下のメソッドを使いcontentDescriptionをセットする必要があります。

ActionBar | Android Developers

ただし、これはAPI Level 18以上で対応しています。それ以下のバージョンの場合、日本語では"上へ移動"というcontent descriptionが対応するようになっています。端末を英語環境にすると"up"だった気がします。

7. XPathを経由したtext要素の指定 (2015/01/04追加)

Androidネイティブアプリでは多くの場合、ユーザが触れる文字はandroid.widget.TextViewやandroid.widget.Button要素で表示されます。それらの要素に対しては、以下のXPathにより指定できます。

find_element(xpath: "//android.widget.TextView[@text=\"#{element_name}\"]")
find_element(xpath: "//android.widget.Button[@text=\"#{element_name}\"]")

表示されているテキストベースで要素の取得が可能なので、一番使いやすそうです。一方、言語設定に依存する箇所なので、"1. contentDescriptionを使う"や"2. resoureIDを使う"を優先したほうが良いと思います。

===追記: 2015/02/10====

Appium@1.3.5betaくらいから、以下のようにUiSelectorを使わないと上手いこと要素を取得できませんでした。UiSelectorで特に困っては無いのでコードレベルで深く確認はしていませんが、注意したほうが良さそう。

element_name = %Q("#{element_name}")
find_element :uiautomator, "new UiSelector().className(\"android.widget.TextView\").text(#{element_name});"
find_element :uiautomator, "new UiSelector().className(\"android.widget.Button\").text(#{element_name});"

また、UiSelectorなら、上記のように面倒なことをせずとも、text要素の取得なら以下で済みそう。

element_name = %Q("#{element_name}")
find_element :uiautomator, "new UiSelector().text(#{element_name});"

個人的には、resoirce-idを中心に使う形にしていきたいと思いますが、今は市場規模でいうと4.2もまだまだ多いので、contentDescriptionの利用が主になりそう。Androidでは、XPathは極力使わないで過ごしています。使うなら、uiautomatorが良いかなという感じ。

Appiumを操作するツール/ライブラリの好み

私はTurnipとRSpecの組み合わせを好んで使っています。

普段は、シナリオを書き、その結果としてpass/falseとスクリーンショットを取得するという位置付けのテストとしてAppiumを使っています。そのため、何をテストしているかを理解しやすくするための自然言語によるシナリオと、その実際の処理箇所が分離しているほうがテストとしての保守が容易なためです。また、シナリオと処理が分離していることで、シナリオを変更することなくOS差分を吸収するという実装もできます。それらも踏まえて、CucmberよりもTurnipのほうが好きなので、Turnip使っています。

どのライブラリを使ってAppiumを操作するかは、慣れもあると思います。一方で、テストにはテストレベルやテストタイプに応じた目的や、どのような保守性を求めるかなどの話もあります。そこらへんを総合して、自分のプロジェクトに見合ったライブラリを選ぶと良いと思います。

最後に

残す今年もあと1週間程度。来年もSelenium/Appiumと必要なだけ使い、OSSにも貢献していきましょう!

ウェアラブルデバイスも、Android/iOSはAppium使って簡単な動作確認とかできるのかな...

モバイルアプリのリリースサイクル毎で行っているテストの流れ

mobile software_test

こんにちは。@Kazu_cocoaです。最近、週末によく寒波が来ますね。布団から抜け出せなくなると評判の毛布を買ったのですが、そこから抜け出せなくなりそうです。

これはソフトウェアテストあどべんとかれんだー2014の13日目の記事です。

前日は id:ishikawa-tatsuya さんのFriendly - Windowsアプリのシステムテスト自動化でした。Friendly自体は聞いたことがあったのですが、Windows環境でテストすること自体が無かったので、どういうものか大変参考になりました。

最近、アプリのローカライゼーションが面白いなと感じました。なので、最後にちょこっと、ローカライゼーションの話も書いています。

ネイティブモバイルアプリのテスト?

多くの開発の現場では、ネイティブのモバイルアプリに対するテストって、どういうことをされているのでしょうか。どのような計画を持って、設計して、用意、実施まで行っているのでしょうか。

開発からリリースまでどのような流れに沿ってテストを実施しているかを言及しているところは少ないように思えます。多くは開発者がリリース前に自分たちで触って確認するで終わると聞きます。また、テスト会社や社内の第三者チームに実施を依頼する、ということも聞きます。

以下では、私が実際におこなっているテストの話を主に、どのような形でテスト全体を進めているかを載せてみます。(xxxLevelという表現は、More Agile Testingで使われているテストレベルの用語を持ってきています。)

ツールの話までは言及してないです。テストの呼び名とタイプの区分はちゃんと考えられていないところもあるのですが、目的でざっと把握していただけると助かります...

対象

ここで書いているテストは以下の環境で主に実施していることを書いています。

  • アプリ利用者数
  • リリースサイクル
    • 2週間前後
    • 少ない時で、数千行程度の修正が含まれる
  • クライアントアプリの話が中心

リリースサイクルの期間で実施しているテスト

Review(Task / Story Level)

  • 開発中に行うレビュー
  • GitHubのPR上で主に実施されるようなレビューをさしてます
  • 主には実装に詳しい開発者どうしで行いますが、内容に応じてテストエンジニアも参加します

Developer testing(Task / Story Level)

  • 開発者が実装ロジックを確認するためのテスト
    • xUnit系が多い
  • 主に、MVCでいうとモデル要素に対するテストが中心
  • 自動化されたテスト
Mobile App

Hermetic (GUI) Testing(Story / Feature Level)

  • モックサーバ(フェイクサーバ)を立てて、以下のように閉じた環境で行うテスト
  • UIの崩れであったり、表示文字数の同値/境界値でのレイアウト、nullチェックなどの基本的なHTTPの応答に対するアプリの動作を見ます
  • まだ手動が主。一部自動化され始めた。
Mobile App <=> Mock Server

Feature Testing(Story / Feature Level)

  • リリースサイクル内で開発した機能とそれに付随する周辺機能に焦点をあてて実施するテスト
  • 手動テスト

E2E Testing( Production Release Level)

  • 実際に稼働するサーバ含めた環境で行うテスト
  • 実データ、実ユーザを使い、一通りの画面遷移や画面操作を実施
    • シナリオベースで、必ず実施するものなどを区分し、それらを実施
  • サポートOSや端末を組み合わせて選んだ端末でテストを実施
  • 多くが自動化されている。手動で自動化されていないところをカバー。
  • 接続は、Staging/Productionともに。
Mobile App <=> Staging/Production Server

Exploratory Testing (Production Release / System Level)

  • 以下のような雛形みたく、特定の不具合/改善点を見つけるために一定の時間を決めて行う形式で行っているテスト(time box management)
Explore …
With …
To discover …
  • ユーザビリティを考慮して修正が必要そうだと判断されるような不具合もこちらで検出してます
  • 負荷テストのようなこともこちらの区分で今は実施しています
  • 手動テスト
Mobile App <=> Staging/Production Server

テストの実施者

基本的に社内。理由は、ドメイン知識がある程度なければ手順通りにテストを実施するしかなくなること。単純に手順をたどるだけのシナリオテストであれば自動化され始めているので人手で行う必要性が小さい。

機種依存が強い機能だと確定している機能に関しては、他機種試験のために外部にお願いすることもありますが、最近は機種依存もそこまで警戒するものではなくなってきました。


以上のような流れを経て、アプリを毎回リリースしています。最近この流れまで落ち着いたのですが、Hermetic Testingなんかは比較的最近実施するようになりました。もう少し自動化を進めて効率化を測りたいなと考えています。

自動化ピラミッドにおけるExploratory Testing

蛇足ですが、Exploratory Tesitng含めた自動化ピラミッドで、以下のように定義されているものがあります。そのExploratory Testingの位置づけが個人的にはもっと重要な気がしてます。

http://watirmelon.com/2011/06/10/yet-another-software-testing-pyramid/

モバイルアプリはユーザに最も近い端末で動作するアプリになるので、もっと探索的に色々テストすることの重要性が高いと思うのですよね。そこでダメだと、実装を変更するなんてことが日常茶飯事に発生することなので。

リリース後の監視はしっかりやりましょう

少しテスト自体の話からそれますが、リリース後の監視の話です。テストではリスクの高さによって対応の優先度を判断しています。その優先度判断において、世に出ているアプリの状態を知り、反映できるようにすることは大事なので少し。

ユーザ環境でどれだけクラッシュしたか、クラッシュしないが修正が必要な不具合があるかなど、検出、計測可能な状態にしておくことは非常に重要です。また、HTTPのリクエスト/レスポンスの計測はサーバサイドではありますが同様に大事です。

クラッシュなんかは目に見えやすく、ユーザの体験を著しく損ないます。

モバイルアプリをテストする場合に求められる知見

実際にモバイルアプリをテストして、ここらへんの知見が必要だと感じたものを載せてみます。

番外: ローカライゼーション

ローカライゼーションって、どんなことをすると思いますか?私も翻訳すれば良いと思っていた時期がありました。Localizationとあるように、その地域に対してカスタマイズすることが必要になります。

例えば、サウジアラビア周辺では、文字を右から左に読むので、基本的に左右逆でアニメーションをつくる必要があります。GoogleAppleなど見ているとOSレベルでそこらへんの取り組みが考慮されていることがわかります。 また、Facebookの記事How Facebook Makes Mobile Work At Scale For All Phones, On All Screens, On All Networksで書かれているように、20種類のAPKを組み合わせて、特定の地域に必要なアプリを構築するとか、そのくらいの取り組みまでする必要も出てきそう。APKのつながりもあるので、組み合わせによるテストが大変になることはわかりますが、ローカライゼーションとはそういうところまで行き届いたものをさすのだなと感じました。

面白そうですね。

次回

次は id:kyon_mm さんによる「負荷テストについて」とのことです。サーバサイドの話になるのでしょうか。楽しみですね。

First Entry !!

WordPressにて、前々からAppium関連から、テスト/品質保証的な観点の話をいろいろと書いているのですが、ほかBlogサービスも触ってみようかと思い。

http://kazucocoa.wordpress.com/

 

どちらを更新し続けるかはまだ不明ですが、ひとまず初投稿!!