May 1, 2017

EspressoでExpandableListViewをクリックして、遷移先が正しいActivityかテストする

メモです。テスト対象のActivityにExpandableListViewがあり、子をクリックすると特定のActivityに遷移する。という想定です。
package my.app.Activity;

import android.app.Activity;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.core.deps.guava.collect.Iterables;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import android.support.test.runner.lifecycle.Stage;
import android.test.ActivityInstrumentationTestCase2;

import my.app.R;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static android.support.test.espresso.action.ViewActions.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
    private MainActivity mActivity;

    public MainActivityTest() {
        super(MainActivity.class);
    }

    @Before
    public void setUp() throws Exception {
        super.setUp();
        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
        mActivity = getActivity();
    }

    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    public void test_ExpandableListView() throws Throwable {
        // 遷移前のアクティビディを確認する
        assertThat(mActivity, instanceOf(MainActivity.class));

        // 親をクリックする
        String groupText = "TARGET_GROUP_TITLE";
        onView(withText(groupText)).perform(click());

        // 子をクリックする
        String childText = "TARGET_CHILD_TITLE";
        onView(withText(childText)).perform(click());

        // 遷移先のアクティビディを確認する
        assertThat(getCurrentActivity(), instanceOf(TargetActivity.class));
    }

    // @see http://stackoverflow.com/questions/24517291/get-current-activity-in-espresso-android
    private Activity getCurrentActivity() throws Throwable {
        getInstrumentation().waitForIdleSync();
        final Activity[] activity = new Activity[1];

        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                java.util.Collection<Activity> activites = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
                activity[0] = Iterables.getOnlyElement(activites);
            }
        });

        return activity[0];
    }
}
ついでにEspresso関係で app/build.gradle に追記した箇所もメモ。
android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    ...
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
}
これで良いのだろうか。

April 28, 2017

Google Maps Android APIで開発時には地図が表示されるのに本番時に表示されない

メモです。

Android Studioで所定の手順を踏むと、Google Maps Android APIのAPIキーを設定する"google_maps_api.xml"というファイルが生成されます。

Developers Console側で発行されたAPIキーを設定すれば、アプリ内で地図を表示できるようになるはずなのですが、本番用のapkに限って地図が表示されませんでした。

原因ですが、実は"google_maps_api.xml"は2つ生成されていました。

app/src/debug/res/values/google_maps_api.xml
app/src/release/res/values/google_maps_api.xml

Android Studio上のツリーで表示されているのは前者で、開発用です。本番用のファイルがどういうわけか表示されていないので、見落としていました。(表示する方法はあるんですかね?)

後者が本番用で、このファイルを正しく編集すると、本番用のapkでも地図が表示されました。

AndroidManifest.xmlに直書きはなんかかっこ悪いなーと思っていたので、解決できて良かったです。


April 20, 2017

AndroidのActionBarでアイコンだけを表示するメモ

久々にAndroidアプリを触っていて、ActionBarのタイトルを消しつつアイコンだけ表示させたいなーと思い少しハマりました。

いろいろ調べたのですが上手くいかず、以下の方法でとりあえずシンプルに実現できました。

-- AndroidManifest.xml --
<application>のandroid:labelを空にして、起動Activity(?)のandroid:labelにアプリ名をセットしています。これで、起動Activityとアプリ一覧でのみタイトルが表示されるようになりました。
<application
    ... 略
    android:label="">
    <activity android:name=".MainActivity" android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity> 
-- MainActivity --
setTitle("")してタイトルを消しています。これで、アプリ一覧でのみタイトルが表示されるようになりました。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    setTitle("");
    ... 略
もっとスマート(標準的)な方法があれば教えて下さい。。。

--
追記: この方法だと起動時に一瞬表示されてしまいますね。。。

というわけで改善策が以下です。

編集するのは AndroidManifest.xml だけでよく、<application>のandroid:labelを空にする。起動Activityの<intent-filter>にandroid:labelを指定する。でした。
<application
    ... 略
    android:label="">
    <activity android:name=".MainActivity">
        <intent-filter android:label="@string/app_name">
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
参考(というかそのまんま): http://stackoverflow.com/questions/3488664/android-launcher-label-vs-activity-title

April 12, 2017

vagrant reload --provisionで"Shared folders that Chef requires are missing on the virtual machine."エラー

メモです。

Vagrant 1.7.2で
$ vagrant reload --provision
しようとしたら
Shared folders that Chef requires are missing on the virtual machine.
This is usually due to configuration changing after already booting the
machine. The fix is to run a `vagrant reload` so that the proper shared
folders will be prepared and mounted on the VM.
とエラーが出てしまったので、調べてみると
http://stackoverflow.com/questions/27975541/vagrant-chef-error-in-provision-shared-folders-that-chef-requires-are-missin
が見つかりました。

書いてある通りに
$ rm .vagrant/machines/default/virtualbox/synced_folders
して、再度実行したらうまくいきました。

April 10, 2017

Laravelのクエリスコープをテストするメモ

以下の"Query Scopes"をいかに簡単にテストするか考えてみました。
http://laravel.com/docs/4.2/eloquent#query-scopes

Laravel5を使っていますが、Laravel4でも考え方は同じなはずです。

Illuminate\Database\Eloquent\Model を継承した Fooクラス が、以下のクエリスコープを持っているとします。
public function scopeBar(Builder $q, $baz)
{
    return $q->whereHas('qux', function ($q) use ($baz) {
        $q->where('quxx', $baz);
    });
}
とりあえず、このクエリスコープが組み立てるSQLを、バインドされるパラメータ含めて確認できれば良かったので、以下の方法でやってみました。

1.
storage/database.sqlite を作成。コネクションエラーを回避したいだけなので、空ファイルでOKです。

2.
config/database.php を編集。
'default' => env('DB_DRIVER', 'mysql'),
3.
phpunit.xml に追記。
<env name="DB_DRIVER" value="sqlite"/>
4.
テスト。
public function testScopeBar()
{
    $q = (new Foo())->bar(100);
    $sql = $q->toSql();
    $bindings = $q->getBindings();

    dd($sql, $bindings); // これをテストする
}
より良い方法があれば教えて下さい。