LoginSignup
30

More than 5 years have passed since last update.

なぜ我々はData Bindingを利用するのか、テストの観点から考えてみる

Posted at

Android アプリ開発の現場に DataBinding が導入されて久しく経ちます。

View と Controller, Model を結びつける方法は Data Binding 以前から Butter KnifeAndroid Annotation の機能を利用してきました。 Activity で findViewById をやっていた頃が懐かしいですね。

Data Binding は非常に便利で variable タグによってレイアウト定義ファイルからデータへアクセスすることが可能になりますし、また Observable を利用すればデータとレイアウトの双方向の通信が可能になります。

Data Binding はコードの削減につながって便利ですが、それだけでしょうか?今回はテストを行うという観点から Data Binding を利用する利点を考えてみましょう。

結論から

ロジックと View を完全に分離できるためユニットテストを書きやすくなる。

簡単なアプリのテストをやってみる

例えばボタンを押すとメッセージを更新し、表示をするアプリを考えてみます。

public class MainActivity extends AppCompatActivity {

    String message;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message = "not clicked";
        textView = (TextView) findViewById(R.id.text);
        updateMessage();
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                message = "clicked";
                updateMessage();
            }
        });
    }

    void updateMessage() {
        textView.setText(message);
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="net.numa08.adventcalendar2016.MainActivity">
    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:text="text"
        />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/text"
        android:text="button"
        tools:ignore="HardcodedText" />
</RelativeLayout>

cap.gif

こういったコードのテストを行うためには espresso を利用するのではないでしょうか。

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    @Rule
    public final ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void change_message() {
        ViewInteraction textView = onView(withId(R.id.text));
        textView.check(matches(withText("not clicked")));

        ViewInteraction appCompatButton = onView(withId(R.id.button));
        appCompatButton.perform(click());

        textView.check(matches(withText("clicked")));
    }

}

テストを書くことはできましたが、一体これは何をテストしているのでしょうか? MainActivity はボタンをクリックすると

  1. メッセージが更新される
  2. 描画が更新される

2つの動作を行います。そのため、テストの対象が曖昧になってしまいます。

データの更新と描画の更新を完全に分離する

このアプリのコードは ViewModel を利用することでデータの更新と描画の更新を完全に分離することができます。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        final MainActivityViewModel viewModel = new MainActivityViewModel();
        binding.setViewModel(viewModel);
    }

}

public class MainActivityViewModel extends BaseObservable {

    @Bindable
    private String message;

    public MainActivityViewModel() {
        setMessage("not clicked");
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        notifyPropertyChanged(BR.message);
    }

    public void onClickButton(View view) {
        setMessage("clicked");
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="net.numa08.adventcalendar2016.MainActivityViewModel"/>
    </data>
    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="net.numa08.adventcalendar2016.MainActivity">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.message}"
            tools:text="text" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/text"
            android:text="button"
            android:onClick="@{viewModel::onClickButton}"
            tools:ignore="HardcodedText" />
    </RelativeLayout>
</layout>

このように ViewModel とレイアウトで双方向の通信が可能なります。この ViewModel のテストは次のようになります。

public class MainActivityViewModelTest {

    @Test
    public void message_change_on_click() {
        final MainActivityViewModel viewModel = new MainActivityViewModel();
        assertThat(viewModel.getMessage(), is("not clicked"));
        viewModel.onClickButton(null);
        assertThat(viewModel.getMessage(), is("clicked"));
    }

}

さきほどとくらべて大きな変化はないようにも思えますが、「クリックをするとメッセージが変わる」テストの部分が「特定のメソッドを呼ぶとメッセージが変わる」に変更されました。

DataBinding に与える ViewModel を定義することで UI に描画を変更する部分とデータの状態を変更する部分を切り離すことが可能になりました。

そのためテストでは UI のテストではなく ViewModel をテストすることで描画されるデータの内容をテストできるようになります。

まとめ

Data Binding が登場は findViewById からの解放だけではなく、ロジックの構造、アプリの設計を行う上で大きな変革となりました。コードの削減だけではなく、今まで以上にテストのしやすい設計を心がけていきたいですね。

サンプルリポジトリは numa08/Adventcalendar2016 です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30