DATE : 2012/10/10 (Wed)
Androidアプリの開発でテスト用のスクリプトを書いていると、特定のログが出力されるまで待機したい、ということがあります。しかし、Android SDK Revision 20の時点では、adbにはそのような機能はありませんし、monkeyrunnerにもそのようなAPIはありません。
そこで、指定したログがlogcatから出力されるまでスクリプトを待機させるPythonモジュールを作りました。以下のように使うと、Activityが起動するまで待機できます。
import logmatcher logmatcher.start() # ... (Activityの起動) logmatcher.wait( 'START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]')
以下の環境でスクリプトは動作します。
- Python 2.7
- monkeyrunner (Android SDK Revision 20以上)
- Jython 2.5
GitHubでスクリプトを公開しています。「Downloads」からアーカイブをダウンロードしてください。ライセンスはApache License, Version 2.0です。
特定の文字列がログに現れるのを待機するほかに、特定の正規表現パターンにマッチする文字列がログに現れるのを待機することもできます。詳しくは、READMEをご覧ください。
DATE : 2011/11/15 (Tue)
背景
Jenkinsを使ってAndroidプロジェクトで継続インテグレーションを行おうとしたのですが、JUnitのXML形式でテスト結果を出力する標準的な方法が見当たりませんでした。テキスト形式では出力できます。しかしXML形式でテスト結果が出力できれば、テストが何件成功して何件失敗したのかが一目でわかるほか、その件数がビルドを経るに従ってどのように推移したのかがグラフでわかりテスト結果が見やすくなります。
調査したなかでは、android.test.InstrumentationTestRunnerを拡張してXML形式を出力するようにすれば解決はできるようでした。実際、「Android JUnit Report Test Runner」というプロジェクトでそのようなtest runnerを含むJARライブラリが提供されています。しかしテストのために新しいライブラリを導入するのは気が引けました。元のソースコードには手を入れずにXML形式のテスト結果を出力する方法が欲しかったのです。
そこでAndroi SDKに含まれているddmlib.jarを使用して、テストを起動し、おのおののテストメソッドの結果を受け取り、XML形式にテスト結果を出力するJythonスクリプトを作成しました。スクリプトの形にすることで、ソースコードを変更しなくてもテスト結果をXML形式で出力することができるようになりました。
使い方
本スクリプトの実行にはAndroid SDKとJython 2.5系が必要です。実行前には、CLASSPATH環境変数にddmlib.jarのパスを設定してください。ddmlib.jarのパスは<Android SDKのパス>/tools/lib/ddmlib.jarとなるはずです。
Androidデバイスもしくはエミュレータにテスト対象のアプリケーションとテストアプリケーションをインストール後、以下を実行してください。android.test.InstrumentationTestRunnerを実行し、結果をXMLで標準出力に出力します。なお以下の実行例はbash上のものです。
./run_tests_for_xml.py -a <adbのパス> <テストアプリケーションのパッケージ名>
「<adbのパス>」はAndroid SDKに含まれているadbのパスに置き換えてください。<Android SDKのパス>/platform-tools/adbとなるはずです。「<テストアプリケーションのパッケージ名>」は、テストプロジェクトのAndroidManifest.xmlにある、manifest要素のpackage属性の値に置き換えてください。
特定のtest runnerを実行したい場合には次のようにスクリプトを実行します。
./run_tests_for_xml.py -a <adbのパス> <テストアプリケーションのパッケージ名> <test runnerの名前>
「<test runnerの名前>」は、テストプロジェクトのAndroidManifest.xmlにある、instrumentation要素のandroid:name属性の値に置き換えてください。現在のスクリプトでは実行するtest runnerはひとつだけ指定できます。
特定のデバイスやエミュレータ上で実行したい場合には次のようにスクリプトを実行します。
./run_tests_for_xml.py -a <adbのパス> -s <デバイスもしくはエミュレータのシリアルナンバー> <テストアプリケーションのパッケージ名>
「<デバイスもしくはエミュレータのシリアルナンバー>」には「adb devices」で出力されるシリアルナンバーを指定します。Jenkins上でAndroid Emulator Pluginを使用している場合は次のようにしてエミュレータを指定します。
jython25 run_tests_for_xml.py -a <adbのパス> -s $ANDROID_AVD_DEVICE <テストアプリケーションのパッケージ名>
スクリプトのダウンロード
GitHub上に置いてあります。ライセンスはApache License 2.0です。
build.xmlへの統合
Android SDKが生成するbuild.xmlへ本スクリプトを使用するターゲットを追加するには、GitHub上にあるrun-tests.xmlの-test-xmlターゲットや-test-coverage-xmlターゲットを参照してください。
DATE : 2011/07/24 (Sun)
FindBugsは、バグらしきところをJavaバイトコードを解析して検査するツールです。AndroidアプリにもFindBugsをかけることができるため、FindBugsを実行するAntのtargetを書きました。
本targetを使用する手順は以下の通りです。
- FindBugsをインストールする。
- Antのライブラリディレクトリにfindbugs-ant.jarをコピーする。
- Androidプロジェクトのディレクトリの直下にあるbuild.xmlの<setup />の下に、以下をコピーアンドペーストする。
<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" /> <target name="findbugs" depends="compile" description="Run FindBugs."> <findbugs home="${findbugs.home}" output="${findbugs.output}" outputFile="${findbugs.output.file}" excludeFilter="${findbugs.exclude.filter}"> <sourcePath path="${source.absolute.dir}" /> <sourcePath path="${gen.absolute.dir}" /> <auxClasspath path="${android.jar}" /> <class location="${out.classes.absolute.dir}" /> </findbugs> </target>
target中の以下のプロパティは適切なものを設定するか、置き換えください。findbugsタスクの詳細は、「Using the FindBugs™ Ant task」を参照してください。
- findbugs.home
- FindBugsがインストールされているディレクトリへのパス。
- findbugs.output
- レポートの形式。htmlを指定するとHTML形式で、xmlを指定するとXML形式で出力される。
- findbugs.output.file
- レポートを出力するパス。ファイル名も含む。
- findbugs.exclude.filter
- 以下に示すXMLファイルへのパス。
自動生成されるR.javaの内部クラスの名前は小文字で始まるため、NM_CLASS_NAMING_CONVENTIONが指摘されます。そこで以下のXMLファイルでレポートの対象外とします。
<?xml version="1.0" encoding="UTF-8"?> <FindBugsFilter> <Match> <Class name="~.*\.R\$.*" /> <Bug code="Nm" /> </Match> </FindBugsFilter>
これで、「ant findbugs」と実行するとFindBugsをかけることができます。
レポートが文字化けする場合は、以下のいずれかの対策を取ります。
- findbugsタグの属性として「jvmargs="-Duser.language=en"」を追加し、レポートの言語を英語に変更する。
- FindBugsに含まれている日本語のメッセージファイルのエンコーディングをUTF-8に変換する。詳しくは「UTF-8環境での eclipse findbugs-plugin の文字化け解消」などを参照。
DATE : 2011/05/22 (Sun)
android.view.GestureDetectorを使用する場合、生成時に指定するGestureDetector.OnGestureListenerとしてGestureDetector.SimpleOnGestureListenerを使用すると余分なメソッドを実装せずに済むため便利です。しかしダブルタップとシングルタップとを区別しようとして、シングルタップを通知するメソッドの取り違えを起こしやすくもあります。
- SimpleOnGestureListener#onSingleTapUp(MotionEvent)は、ダブルタップであっても、シングルタップ2回分として受け取ってしまいます。
- SimpleOnGestureListener#onSingleTapConfirmed(MotionEvent)は、ダブルタップの場合には呼び出されず、シングルタップの場合のみ呼び出されます。
これは、前者がGestureDetector.OnGestureListenerに定義されているメソッドで、後者がGestureDetector.OnDoubleTapListenerで定義されており、GestureDetector.SimpleOnGestureListenerはその両者を実装しているためです。GestureDetector.OnGestureListenerは、ダブルタップを考慮しません。
そのため、ダブルタップとシングルタップとを区別したい場合は、GestureDetector.SimpleOnGestureListener#onSingleTapUp(MotionEvent)ではなくGestureDetector.SimpleOnGestureListener#onSingleTapConfirmed(MotionEvent)を使用すべきです。
なお、前者はMotionEvent.ACTION_UPの動作を通知し、後者はMotionEvent.ACTION_DOWNの動作を通知することにも注意が必要です。
DATE : 2011/05/08 (Sun)
Androidには、以下の2つの方法でスレッドの優先度を変更できます。
両者に違いはあるのでしょうか。気になったので調べてみました。結論としては、両者の動作に違いはありません。
android.os.Process.setThreadPriority()は、android.os.Processに定義されているANDROID_PRIORITY_*定数をnice値としてsetpriorityシステムコールを呼び出します。処理の流れは以下の通りです。
- android.os.Process.setThreadPriority()はネイティブメソッドで、frameworks/base/core/jni/android_util_Process.cppのandroid_os_Process_setThreadPriority関数が実行されます。
- android_os_Process_setThreadPriority関数がframeworks/base/libs/utils/Threads.cppのandroidSetThreadPriority関数を呼び出します。
- androidSetThreadPriority関数は、android.os.Processに定義されているANDROID_PRIORITY_*定数をnice値としてsetpriorityシステムコールを呼び出します。
java.lang.Thread.setPriority()は、java.lang.Threadに定義されているスレッド優先度をnice値に変換してsetpriorityシステムコールを呼び出します。処理の流れは以下の通りです。
- java.lang.Thread.setPriority()がjava.lang.VMThraed.setPriority()を呼び出します。
- java.lang.VMThraed.setPriority()はネイティブメソッドで、libcore/vm/native/java_lang_VMThread.cのDalvik_java_lang_VMThread_setPriority関数が実行されます。
- Dalvik_java_lang_VMThread_setPriority関数は、dalvik/vm/Thread.cのdvmChangeThreadPriority関数を呼び出します。
- dvmChangeThreadPriority関数は、java.lang.Threadで定義されているスレッド優先度をnice値に変換してsetpriorityシステムコールを呼び出します。
結果、両者の効果は同じです。ただし、android.os.Processではスレッドの優先度が用途ごとに定数として定義されているため、まずandroid.os.Process.setThreadPriority()の使用を検討すべきでしょう。