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

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

View と Controller, Model を結びつける方法は Data Binding 以前から　[Butter Knife](http://jakewharton.github.io/butterknife/) や [Android Annotation](http://androidannotations.org/) の機能を利用してきました。 Activity で findViewById をやっていた頃が懐かしいですね。

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

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

## 結論から

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

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

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


```java
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
<?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](https://qiita-image-store.s3.amazonaws.com/0/6756/6c423f8f-7527-ef29-1ab8-bab98a793f29.gif)


こういったコードのテストを行うためには [espresso](https://developer.android.com/training/testing/ui-testing/espresso-testing.html) を利用するのではないでしょうか。

```java
@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 を利用することでデータの更新と描画の更新を完全に分離することができます。

```java
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
<?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 のテストは次のようになります。

```java
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](https://github.com/numa08/Adventcalendar2016) です。