「テストが書きやすくなる」の意味を紐解く

Androidアプリの話です。MVPアーキテクチャAndroidにいれることでどういうリターンがあるんだろう?というのがもやもやしていて、「テストがしやすくなる」というメリットについてあまり実感持てずにいた。単体テストコードを書いていてなんとなくこういうことかなーと考えたことをまとめてみる。当たり前じゃん&間違ってるかもしれないけど、そこはご指摘いただければ…。

notMVPケース

前提

  • FragmentやActivityでAPIをたたいてたりDaoの呼び出しも書くケース
  • コールバックのinnerClassもFragmentに書いたり(privateで)
  • privateのinnerClassで持っているメソッドをテストしたい。
public class SampleFragment extends Fragment{


private static class hogeCallback {
    
    public void onSuccess(boolean ishoge){
            String testCase = ABテスト値取得;
            if(testCase==isA() && ishoge){
                hugaView.setVisibility(GONE)
            }
    }

}

書こうとするときにぶちあたる壁

その時確かめたいテストケースは「testCaseがhogeで、ishogeがtrueの時、hugaViewが表示されていること」だった。これを実現しようとして考えたことが以下。

  • privateのinnerクラスってどうやって呼ぶんだろう
  • assertは何でしたらいのだ?Espressoで表示中かどうかを確かめたらいいのか?
  • テストしたいケースはAPIを叩いた後のコールバック時のメソッド。どうやってその状況を再現する?
  • 単純なテストケースに見えるけど、なんか大変そうだということはわかった。

結局どう書いたのか

  • privateだったinnerClassをpublicにした
  • testCase変数の値によってテスト結果を確かめたかったので、testCase変数をメンバ変数でもたせる(コンストラクタで渡す)か、引数に追加する
@Test
public void hogeTest(){
    String testCase = "A";
    ViewGroup viewGroup = mock(ViewGroup.class);
    when(viewGroup.getVisibility()).thenReturn(View.GONE);
    SampleFragment.HogeCallback hogeCallback = new SampleFragment.HOgeCallback(context, viewGroup, 0, testCase);
    countCallback.onSuccess(0, true);
    verify(viewGroup, times(1)).setVisibility(View.VISIBLE);
}

だいたいこんな感じでテストケースを先輩に習いながら書いた。 正直、自分がこれを書こうと思ったら結構時間かかりそう。実際悩んだし。 結局、テストのためにプロダクトのコードを変える必要があってなんとなく良くないんじゃないか?と思っていた。

MVPで分けてみる

じゃあこれをMVPに書き換えて書いてみるとどうなるか? 簡単に、Viewじゃないところはpresenterにやってもらうようにした。

HogeCountTask task = new HogeCountTask(this).execute();

Fragment内で、API叩いてcallbackをもらう処理はpresenterに移す。

presenter.executeHogeCountTask();

presenterにlistenerをimplementsさせて、presenterの中でコールバックを受け取る

 @Override
    public void onSuccess(int count, boolean ableMoreSelect) {
        String testCase = getHelpMessageTestCase();
        boolean isShowHogeView = isShow(count, testCase);
        view.setVisibleHHogeView(isSHoe);
    }

    @Override
    public void onError(ResultDto resultDto) {

    }

こんなかんじ。で、ここでそもそも今回のケースではlistenerを継承したinnerClassは使わなくてよかったのでは?じゃあFragmentの中に書いてあってもテストの書き方は変わらないのでは?と思ったり。けどそれが気づけてシンプルにできたのならそれもまたいいところということで。

Presenterにロジックを移すということ

AndroidでFragment,Activityのテストをするのは気が億劫だ。(私だけなのだろうか) 正直正解がわからないし、どう立ち上げるのが正解かわからない。 presenterに移しておけば、Viewをモック化するだけでなんだかすごそうなツールを使わなくてもできる。

  @Before
    public void setUp() throws Exception {
        context = InstrumentationRegistry.getTargetContext();
        view = mock(SampleView.class);
        presenter = new SamplePresenter(view, context);
    }

    @Test
    public void 正常_isHelpMessageView() throws Exception {
        Method method = JobTypeSelectNewPresenter.class.getDeclaredMethod("isHelpMessageView",  boolean.class, String.class);
        method.setAccessible(true);
        boolean result = (boolean) method.invoke( true, "SHOW");
        assertTrue(result);
    }

だいたいこんなかんじ。privateメソッドだということ以外は結構スラスラかける。

結果「テストが書きやすい」ってこういうことなのでは?

  • 「これを渡したらこれが返ってくる」って思考をそのままコードにしやすい
  • viewのギリギリまでテストができる、しかも単純な。
  • とりあえずあんま難しいこと考えなくていい

雑なまとめになっちゃった。新しいアーキテクチャいれるって時、何が目的なんだろう、いれたらどんないいことがあるんだろう、って自分は今までアーキテクチャを理解するだけで考えられなかったので、少し違う側面が見えてきたかも?というお話でした。まる