Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
28
Help us understand the problem. What is going on with this article?
@numa08

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

More than 3 years have passed since last update.

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 です。

28
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
numa08
ソースコードカワイイガチ勢
covelline
もっと楽しい明日をつくる

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
28
Help us understand the problem. What is going on with this article?