Cat Dev It!

develop apps

Android Test Test Test

Androidで案件やってるとテスト用、本番用の設定が多々必要になります。
Cでは#ifdefとかでコードを除外でき、リバースエンジニアリングもされにくい。でもAndroidは怖いです。プリプロセッサのディレクティブが使えないのですべてソースコードに含まれます。
Javaなので仕方ないのですが逆コンパイルも簡単にできちゃいます☆(ゝω・)vキャピ
こんなのでリリースできねぇ!開発サーバーの攻撃とか休日返上になってしまう!

というわけで対処します。


  1. ソースコードで分岐
    なんかもう色々と気にしない潔い人用。わかちこわかちこ
    if (BuildConfig.DEBUG)の嵐。

  2. 設定ファイルを差し替える
    設定ファイルは差し替えればいいよ!リリースビルド前に設定差し替え!いつの日か差し替え忘れで・・・αβοοη..._φ(゚∀゚ )アヒャ

  3. 設定ファイルを差し替えるその2
    設定ファイルは差し替えればいいよ!adbで直接突っ込もう!プログラマしかいじれないブラックボックス。root端末しかダメだし、ダメダメ

  4. 設定ファイルを差し替えるその3
    BuildConfig.DEBUGでprefs.xml、prefs_test.xmlとか読み分ければいいね!
    これもapkに含まれても気にしない潔い人用。

  5. プロジェクトを分ける
    ifdefがないならプロジェクトを分ければいいじゃない。(まりーあんt
    prefs.xml以外マージしていけばめんどいけどそれなりじゃね?マージし忘れてコンフリクトとか楽しい!!!真っ赤に燃えた競合だから♪

  6. 別アプリを作る
    別アプリの設定をBuildConfig.DEBUG見て読めばいいじゃん!
    別アプリがなければ設定読めなくて例外が発生するから、別アプリのインストール必須でふとした拍子の本番設定とかも防げる!
    リリース時はフラグが降りるから当然自分のアプリの設定が読まれるよ!

いけてる方法。イケメソ。それは・・・別アプリを作る!
結構大それたように感じるし、上司からも怪訝な顔されることうけあい。もうセキュリティとかいいじゃんみたいなことに。
でも簡単なんです。SharedPreferencesで設定を読むだけ!
まあググればすぐに出るんですけど
createPackageContext(CONTEXT_IGNORE_SECURITY)
getPreferences(MODE_WORLD_READABLE|MODE_WORLD_WRITABLE)
みたいなやつです。テスト設定アプリはPreferenceActivityとかで軽く作れちゃうし。

ただし注意があります。ここからなぜかあまり情報がないので書いておきますがテストアプリと、本番アプリで設定ファイル名を変えておく必要があります。
設定ファイル名が同じだとひっそりとキャッシュされたSharedPreferencesのインスタンスが返ります。これにはなんだかとてもはまった。
明らかにテストアプリのコンテキストから取得してるのにデバッグログを見て絶望。
調べたら/(^o^)\ナンテコッタイ
設定の書き込みは本番にとかコンテキストを使い分けてやっていたからそのタイミングで先にキャッシュされてしまっていたのでしょう。
次に注意すべき点。
自身のコンテキストではないSharedPreferencesへの書き込みはミスります。
Couldn't rename file *** to backup file ***みたいなログが出る。
試しにchmodでパーミッション変えるとうまくいく。なんだよMODE_WORLD_WRITABLEって!!!ワールドイズマインじゃねえか!!!
というわけでandroid:sharedUserIdを使いましょう。
まあSharedPreferencesが楽だからこんな感じです。別アプリのデータ共有って意味ではContentProviderを使うのがベストプラクティスみたいな。

暇があればサンプルコードでも置いときます。

AIDE (Android Java IDE) JUnit Test Tutorial part2

前回に引き続き、JUnit。
さすがにあのやり方はだるすぎます・・・
せめて、Terminal Emulatorを使わずにやってみます。
f:id:catdevit:20120811154357p:plain

package com.mycompany.myapptest;

import android.app.*;
import android.os.*;
import android.widget.*;
import java.io.*;

import java.lang.Process;
import android.view.*;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        getDefaultTextView().setText(executeTest());
    }
    
    private String executeTest()
    {
        String result = "";
        try
        {
            ProcessBuilder pb = new ProcessBuilder(
                "am", "instrument", "-w", "-e", "class",
                "com.mycompany.myapptest.HelloTest",
                "com.mycompany.myapptest/android.test.InstrumentationTestRunner");
            Process p = pb.start();
            InputStream input = p.getInputStream();
            byte[] buffer = new byte[1024 * 4];
            int readBytes = 0;
            StringBuilder sb = new StringBuilder();
            
            while ((readBytes = input.read(buffer)) > 0)
            {
                sb.append(new String(buffer, 0, readBytes));
            }
            result = sb.toString();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        return result;
    }
    
    private TextView getDefaultTextView()
    {
        ViewGroup root = (ViewGroup)findViewById(android.R.id.content);
        LinearLayout main = (LinearLayout)root.getChildAt(0);
        TextView textView = (TextView)main.getChildAt(0);
        return textView;
    }
}

こんな感じ。

Yeah!
できました!
f:id:catdevit:20120811154403p:plain

AIDE (Android Java IDE) JUnit Test Tutorial

素晴らしい、Android Java IDE。

https://play.google.com/store/apps/details?id=com.aide.ui&hl=ja

開発ぐぐたす

https://plus.google.com/101304250883271700981/posts

 

今回、ユニットテストをAndroid端末単体で行ってみます。

TerminalEmulatorをインストールする必要があります。

きっとこのアプリ使う人は入ってると思うので説明は省略します。

自分の環境はACER ICONIA TAB A100, Honeycomb 3.2で実行しています。

 

1. メインプロジェクトを作成

ここではデフォルトのMyAppとします。

AppName: MyApp

Package Name: com.mycompany.myapp

f:id:catdevit:20120729012552p:plain

 

2. テストプロジェクトを作成

AppName: MyAppTest

Package Name: com.mycompany.myapptest

f:id:catdevit:20120729012842p:plain

 

3.テストプロジェクト設定

テストプロジェクトのAndroidManifest.xmlを編集します。

以下のコードを追加していきます。

 

どこでもいいと思いますがuses-sdk要素の後ろに書きます。

<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.mycompany.myapp" />

 

次にapplication要素の中に

<uses-library android:name="android.test.runner" />

を書きます。

 

f:id:catdevit:20120729013529p:plain

f:id:catdevit:20120729013536p:plain

 

4.テストコード作成

次に/mnt/sdcard/AppProjects/MyAppTest/src/com/mycompany/myapptest以下に
HelloTest.javaを作成します。

f:id:catdevit:20120729013822p:plain

テストを先に作ったのでHelloとかエラーですがとりあえず保存します。

 

5. テストプロジェクト設定2

/mnt/sdcard/AppProjectsに戻り

MyAppディレクトリを長押ししてメニューからAdd library to projectを選択します。

MyAppTestからMyAppプロジェクトが参照されます。

 

6. コード作成

MyAppプロジェクトを開きます。

Hello.javaを作成します。

f:id:catdevit:20120729014130p:plain

この状態でRunします。インストールします。
でも実行はしません。Activityが立ち上がるだけです。

 

7. テストプロジェクトインストール

再度MyAppTestプロジェクトを開きます。

HelloTest.javaを開きます。

やったね!先ほどのエラーが出てません!当たり前ですね。。。

 

この状態でRunします。インストールします。

でも実行はしません。Activityが立ち上がるだけです。

 

6. テストコマンド実行

TerminalEmulatorを立ち上げます。

立ち上がったらamコマンドを実行してみます。

f:id:catdevit:20120729015054p:plain

テストに使用するコマンドです。

詳細は以下のドキュメントを参照してください。

http://developer.android.com/reference/android/test/InstrumentationTestRunner.html

 

以下のコマンドを実行します。

am instrument -w -e class com.mycompany.myapptest.HelloTest com.mycompany.myapptest/android.test.InstrumentationTestRunner

 

f:id:catdevit:20120729015736p:plain

テストで失敗しています。 Hello Hogepiyoが期待されていたのですが、Helloしか返していないからです。

 

8. コード修正

AIDEに戻ってコードを修正します。

MyAppプロジェクトを開き、Hello.javaを編集します。

"Hello"を"Hello Hogepiyo"と修正します。

Runします。インストールします。

実行しません。

 

9. 再度テストコマンド実行

f:id:catdevit:20120729020056p:plain

 

テスト成功しました!!!ヒャッハー!!!!!!!!!!!

 

Android端末単体でここまでできるのは素晴らしいことです。

ですが、さすがに一苦労ですね。

次回は若干ステップを減らす方法を書きたいと思います。