Androidアプリ担当の高畑です。
社内でぷよぷよテクニックを披露したら軽く引かれました。ショックです。
ぷよぷよのおじゃまぷよを消すのは得意ですが、お腹のおじゃまぷよはなかなか消せません。
今回のブログネタですが、robotiumについて書こうと思います。
知っている人にとっては今さらrobotium?って感じかもしれませんが、僕は最近初めて使ってみました。
そして、robotiumのwaitForメソッド系が何かと便利だったのでそれに重点を置いて書いていきます。
robotium準備
こちらからrobotiumのライブラリをダウンロードします。
後はテストプロジェクトにrobotium.jarをビルドパスを通すだけで使用準備は完了です。
導入は非常に手軽です。
後はInstrumentationTestCase2で以下のように書くだけでセットアップは完了です。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SampleActivityTest extends ActivityInstrumentationTestCase2<SampleActivity> { private Solo solo = null; public SampleActivityTest() { super(SampleActivity.class); } @Override protected void setUp() throws Exception { solo = new Solo(getInstrumentation(), getActivity()); } } |
waitForメソッドシリーズ
冒頭でも触れたのですがrobotiumには便利なwaitForメソッドがあります。
サンプルソースに移る前にどんなものかをいくつかピックアップして紹介します。
waitForActivity
引数にクラス名とタイムアウト時間を渡すと指定した時間内に指定したActivityが表示されるかを判定します。
1 2 |
//2秒以内にHogeActivityが表示されるかどうか waitForActivity(HogeActivity.class,2000) |
waitForFragmentById
引数にレイアウトのIDを指定します。
指定したレイアウトIDにFragmentが指定時間内に追加されているかを判定します。
1 2 |
//2秒以内にR.id.fragmentに何かしらのFragmentが追加されるかどうか waitForFragmentById(R.id.fragment,2000) |
waitForFragmentByTag
引数にFragmentのタグ名を指定します。
指定したタグ名のFragmentが指定時間内に追加されているかを判定します。
1 2 |
//2秒以内にタグ名がhogeのFragmentが追加されるかどうか waitForFragmentByTag("hoge",2000) |
waitForFragment系を使う時はandroid-support-v4.jarが必要になるので注意してください。
waitForText
引数にした文字列が時間内に表示されているかを判定します。
1 2 |
//hogeという文字列が2秒以内に表示さているかどうか waitForText("hoge",2000); |
waitForCondition
なんでもありのwaiForメソッド
引数にConditionを指定する。指定したConditionになるかを判定します。
1 2 3 4 5 6 7 |
//2秒以内にボタンがenable状態になるかどうか solo.waitForCondition(new Condition() { @Override public boolean isSatisfied() { return button.isEnabled(); } },2000); |
その他のwaitForメソッド
waitForDialogToOpen
waitForDialogToClose
waitForView
waitForLogMessage
など色々ありますが、どれも基本的に使い方は同じです。
サンプルソース
robotiumでテストするための簡単なサンプルを用意します。
Activityを2ファイルと
Fragmentを2ファイル用意しました。
まず始めにメインとなる画面です。
onCreateでListFragmentを追加するのと
ボタンのクリックでFragmentを入れ替える機能を持っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class SampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.fragment, new SampleListFragment(), "list"); ft.commit(); findViewById(R.id.changeFragment).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { changeFragment(); } }); } public void changeFragment(){ FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.fragment, new SampleGridFragment(), "grid"); ft.addToBackStack(null); ft.commit(); } } |
次にSampleActivityに紐づけるFragmentです。
ただのListFragmentです。
ItemをクリックするとNextActivityに選択した文字列を渡して画面遷移します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class SampleListFragment extends ListFragment implements AdapterView.OnItemClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List<String> list = new ArrayList<String>(); list.add("東京"); list.add("大阪"); list.add("愛媛"); list.add("香川"); list.add("福岡"); list.add("沖縄"); ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), R.layout.simple_list_item_1, list); setListAdapter(adapter); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); getListView().setOnItemClickListener(this); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(getActivity(), NextActivity.class); ArrayAdapter<String> adapter = (ArrayAdapter<String>) getListAdapter(); intent.putExtra("data", adapter.getItem(position)); startActivity(intent); } } |
もうひとつのFragmentはGridViewを表示するFragmentです。
データはどこからダウンロードしてくると仮定して、非同期取得してGridViewに反映させます。
SampleListFragmentと同様に、Itemを選択するとNextActivityに選択した文字列を渡して遷移します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
public class SampleGridFragment extends Fragment implements AdapterView.OnItemClickListener { private List<String> dataList = null; private GridView gridView = null; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.grid_fragment,null,false); dataList = new ArrayList<String>(); ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(), R.layout.grid_layout, dataList); gridView = (GridView) view.findViewById(R.id.gridView); gridView.setOnItemClickListener(this); gridView.setAdapter(arrayAdapter); DownLoad downLoad = new DownLoad(); downLoad.execute(); return view; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Intent intent = new Intent(getActivity(), NextActivity.class); ArrayAdapter<String> adapter = (ArrayAdapter<String>) gridView.getAdapter(); intent.putExtra("data", adapter.getItem(position)); startActivity(intent); } //非同期でデータをダウンロードするクラス public class DownLoad extends AsyncTask<String, Integer, List<String>> { @Override protected List<String> doInBackground(String... params) { //何か重い処理 try { Thread.sleep(3000); } catch (InterruptedException e) { } List<String> list = new ArrayList<String>(); list.add("東京"); list.add("大阪"); list.add("愛媛"); list.add("香川"); list.add("福岡"); list.add("沖縄"); return list; } @Override protected void onPostExecute(List<String> strings) { super.onPostExecute(strings); ArrayAdapter<String> adapter = (ArrayAdapter<String>) gridView.getAdapter(); adapter.addAll(strings); adapter.notifyDataSetChanged(); } } } |
最後にNextActivityです。
intentで文字列を受け取って、非同期で取得した文字列と連結させて表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class NextActivity extends Activity { private TextView textView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.next); textView = (TextView) findViewById(R.id.textView); textView.setText(getIntent().getStringExtra("data")); DownLoad downLoad = new DownLoad(); downLoad.execute(); } public class DownLoad extends AsyncTask<String, Integer, String > { @Override protected String doInBackground(String... params) { //何か重い処理 try { Thread.sleep(3000); } catch (InterruptedException e) { } return "hogehoge"; } @Override protected void onPostExecute(String string) { super.onPostExecute(string); textView.setText(textView.getText() + string); } } } |
サンプルのテストソース
いよいよrobotiumを使ったInstrumentTestです。
waitForメソッドの返却値はboolean型なので
assertTrueと絡めるとスッキリします。
どんな動作や確認をしているかはコメントから読み取ってください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public class SampleActivityTest extends ActivityInstrumentationTestCase2<SampleActivity> { private Solo solo = null; public SampleActivityTest() { super(SampleActivity.class); } @Override protected void setUp() throws Exception { solo = new Solo(getInstrumentation(), getActivity()); } public void testInstrument() { //SampleListFragmentが表示されることを確認する assertTrue(solo.waitForFragmentByTag("list")); //changeFragmentボタンを押す solo.clickOnView(solo.getCurrentActivity().findViewById(R.id.changeFragment)); //SampleGridFragmentが表示されることを確認する assertTrue(solo.waitForFragmentByTag("grid")); //SampleGridFragmentのGridViewのデータが追加されることを確認する assertTrue(solo.waitForCondition(new Condition() { @Override public boolean isSatisfied() { SampleActivity SampleActivity = (SampleActivity) solo.getCurrentActivity(); GridView gridView = (GridView) SampleActivity.findViewById(R.id.gridView); return gridView.getAdapter().getCount() > 0; } }, 5000)); //三番目のGridViewをクリックする solo.clickInList(3); //NextActivityが表示されることを確認する assertTrue(solo.waitForActivity(NextActivity.class)); //テキストビューにGridViewで選択した文字とhogehogeを結合したものが表示されることを確認する assertTrue(solo.waitForText(solo.getCurrentActivity().getIntent().getStringExtra("data") + "hogehoge")); //NextActivityを終了する solo.getCurrentActivity().finish(); //SampleActivityに戻ってくることを確認する assertTrue(solo.waitForActivity(SampleActivity.class)); } } |
最後に
テストコードを書くためにソースコードが多くなってしまったので
今回のサンプルプロジェクトをgithubにあげておきました。
今回のブログ結局何を言いたかったのかというとwaitForメソッドを使えば、非同期処理でガチガチのアプリでも
シンプルにInstrumentTestが書けるということです。
iOSに浮気を始めたAndroidエンジニア? Androidはほとんど書いてない…