fresh digitable

セミコロンたちが躍動する おいらのコードを 皆さんに 見せたいね

RobolectricでRecyclerView.smoothScrollToPosition()を呼ぶとテストが進まなくなる(未解決)

ViewPagerの代わりにRecyclerViewを使ってチュートリアルの紙芝居を実装したのだが、進むボタンをN回押してサインアップへ進む画面に行くテストがいつまで経っても終了しない現象が起きた。諸事情によりprintデバッグを使って二分探索的にどこで止まっているかを調べたところ、どうもDatabindingでRecyclerView.smoothScrollToPosition()を呼んでいるところが怪しいということになりぐぐった結果、次のissueを発見した。

github.com

私の環境ではRobolectricは4.0.2だが、issueが出たのがだいたい1年前で、他の人が4.1-SNAPSHOTでも起きていると言っているので根が深そう。EspressoのswipeLeft()を使ってみたがこれもハングした。しかし、RecyclerViewActions.scrollToPosition()を使うとページを送ってやることはできた。ただ、この方法ではスクロール位置がコールバックに渡って来ないので、「ページ番号が変わっているか」みたいなことはチェックできないかもしれない。

結局、いい回避策が思いつかなかったので棚上げしてしまった。アニメーションが絡むとなんかうまくいかないのかなとか、スクロールした量(xとかy方向の移動量)を計算できないのかなとかいろいろ想像してみるけど全然わからない。知っている人がいたら教えて欲しい。できれば直してくれるとありがたい。

以下は本件とは関係ないけどいい本なのでみなさん買ってください。

peaks.cc

peaks.cc

AndroidのLiveDataとかViewModelについて考えるとりとめのないこと

  • LiveDataはデータホルダなので、覚えておきたいことを覚えておくために使うのがよい。表示したいデータとか、画面の状態など。私は、イベントやメッセージは受けた人が覚えておくべきものではないと考えているので、イベントバスやメッセージパッシングのために使う時にはなんらかの工夫が必要だと思っている。そういうことがしたくなったらReactive extensionとかを使ったほうがいいかもしれない。
  • ViewModelActivityFragmentではなくViewDataBindingに結びついていると考える。バインドしたいデータのソースが複数種類あるならViewModelを継承したインタフェースか抽象クラスをViewDataBindingクラスにセットできるようにしておく。IntentFragmentのArgumentを使ってViewModelのクラス名を渡すなどしてやれば、ActivityFragmentはどのViewModelを取得すればよいかわかる。
  • LiveDataはたとえ同じオブジェクトがset/postされてもObserver.onChanged()を呼ぶ。これが嫌ならObservableXxx系のクラスを使ってバインドするか、BindingAdapterで前回バインドした値を受け取って新たにセットするかどうか決めるのがいいのだろうか。MediatorLiveDataでラップしたものを使うほうがスマートかもしれない。
  • こういう使い方はよくないのかもしれないけど、ViewModelActivity単位で共有するように作ったかとか、Fragmentごとに別々に持っていてもいいのかとか、ProviderのFactoryとかタグは必要なのかとか、やっているとすぐ忘れてしまうので、ViewModelごとにXxxViewModel.getInstance(FragmentActivity, ViewModelProviders.Factory)みたいな感じのメソッドを用意している。
  • RecyclerViewを持っている画面のViewModelをそれぞれのアイテムビュー(RecyclerViewの子要素)に渡したくなることがあるのだけど、なんとなく違和感を覚えてしまう(なんとなくなので杞憂ならそれでいいんだけど)。データだけならAdapterの中でバインドしてしまえばいいのだと思うのだけど、クリックイベントを拾って選択状態にしたいとか画面遷移したいとかいうことをやろうとするとViewModelでイベントをさばきたくなってしまう。RecyclerViewを持っている画面のViewModelって他に何かやることあるのっていう気もしていて、だったら各アイテムビューにViewModel渡してもいいじゃんと思ったり、実はそういう画面にはそもそもViewModelいらないのでは?と思ったりもして迷宮入りしている。

peaks.cc

peaks.cc

TestRuleでテスト対象の初期化処理などをまとめる

テストケースのセットアップ処理を使いまわしたい時、TestRuleに実装するといろんな使いまわしが効いて便利なので最近はそうしている。個人的には、TestRuleの中には本当に基本的な準備や後始末の処理を書いて隠し、テストクラスの方に本質的なテストの前提条件だけ書くような感じで使い分けている。モックオブジェクトのセッティングやよく使うような一連のまとまった処理もTestRuleに書いたりする。UIテストのPageObjectみたいなものだろうか。Baseクラスを継承する方法でもいいと思うが、私はうまくできないので敬遠している。

実際はテストの前や後に呼ばれるメソッドが生えていて使いやすくなっているTestWatcherクラスを継承して作る。TestWatcherクラスの中身を見れば一目瞭然なのだが、ざっくり言うと@Beforeの代わりになるのがstarting()@Afterの代わりになるのがfinished()で、普通はこのメソッドを実装すればよい。そのほかにテストに成功した時と失敗した時に呼ばれる、succeeded()failed()などがある。starting()@Beforeの処理よりも必ず先に呼ばれるし、finished()@Afterの必ず後に呼ばれる。

一つのテストクラスにはテストに必要なだけ複数のTestRuleを置くことができる。Androidのテストを書いていると、InstantTaskExecutorRuleActivityTestRuleあたりは基本的にどのEnclosedなテストケースでも使うことになり、毎回これを書くのは面倒である。こういったTestRuleも、テスト対象のセットアップをするTestRuleの中に入れ込んでしまうとよい。具体的にはRuleChainを使って必要なTestRuleをまとめるなどして、テストケースの為に作ったTestRule.apply()の中でRuleChainのapplyを呼んでやる。

override fun apply(base: Statement?, description: Description?): Statement {
    return RuleChain.outerRule(...)
            .around(...)
            .around(...)
            .apply(super.apply(base, description), description)
}

こんなかんじ。

なんの脈絡もないがAndroidの人なのであやかっておく。 peaks.cc