fresh digitable

めんどくさかったなってことを振り返ったり振り返らなかったりするための記録

テスト対象を初期化する際にはlazyを使う

前置き

JavaでJUnit4のテストを書いていたころは、@Beforeを付けた(setup()のような名前の)メソッドの中にテスト対象やその依存関係の初期化処理を書き、テストケースの中で使うもの(テスト対象など)はフィールドに定義してsetup()メソッドの中でフィールドにセットしていた。

private Foo sut = null;

@Before
public void setup() {
  final FooChild child = new FooChild();
  sut = new Foo(child);
}

個人的には、このテストクラスの文脈として共有したいことをsetup()に書いて整理するというスタイルが気に入っていたのだが、Kotlinでテストを書くようになってからはそれをやめて、テスト対象やその依存関係をテストクラスのプロパティとして書くようになった。

private val sut: Foo = Foo(
  FooChild()
)

多くの場合はこれで何の問題もないと思うが、気を付けておきたいのはTestRuleを使ってテストのセットアップを行う場合で、特にAndroidアプリの開発においては、テスト対象がLiveDataのプロパティを持っているようなときに使うInstantTaskExecutorRuleや、Kotlin coroutineのテストをするときにTestCoroutineDispatcherを使うようなときである。

初期化の順序に気を付けよう

ポイントは初期化処理の実行順で、テストではざっくり言って次のようになる。

  1. (テストクラスの@BeforeClass関数)
  2. テストクラスのコンストラクタ(プロパティの初期化処理、TestRuleのコンストラクタの処理もここで行われる)
  3. TestRule.apply()Statement.evaluate()より前に実行される処理(TestWatcherを実装しているならstarting()
    • InstantTaskExecutorRuleではここでLiveData内部で使うExecutorをテスト用のモックに差し替えている
    • TestCoroutineDispatcherを使ったセットアップではこのタイミングでDispacherをテスト用のモックに差し替える
  4. テストクラスの@Before関数
  5. テストケース

先の例ではテスト対象の初期化はリストの2番目で行われるが、テスト対象の初期化の中でLiveDataがアクティブになったり、coroutineのDispacher.Mainなどにアクセスするようなことをやっているとその時点でモックしなさい例外*1が出てクラッシュする(androidx.lifecycle.liveData()など)。なので、テスト対象の初期化はリストの3番目の後に行われるようにしなければならないが、さりとてsetup()の中で生成してlateinit varなプロパティにセットするというのも忍びない。手っ取り早く解決するにはlazyを使うのがいいだろうか。

private val sut: Foo by lazy {
  val child = FooChild()
  Foo(child)
}

こうしておけばsetup()やテストケースの中で初めてsutにアクセスしたときに初期化されるので、モックしなさい例外を回避できる。ただし、lateinit varの方がすっきり書けるという場合にはそっちの方がいいと思います。テストクラス用のTestRuleを自前で用意してその中でテスト対象を初期化するようなケースでは、lateinit varの方をバッキングフィールドにして隠すという手も使えるでしょうし。

*1:個人的にそう呼んでいる

2020年に見たアニメ

いつものやつをやります。

前回: akihito104.hatenablog.com

いつもは帰省して暇になった時間に書くんだけど今年は帰省しなかったので書きそびれていた。例によって五十音順です。もしそうなっていないところがあったら私のミスです。カッコ[]付きは未視聴(録画消化待ち)です。

冬クール

  • 異種族レビュアーズ
  • ID:INVADED イド:インヴェイデッド
  • インフィニット・デンドログラム
  • 映像研には手を出すな!
  • A3! SEASON SPRING & SUMMER
  • 推しが武道館いってくれたら死ぬ
  • 織田シナモン信長
  • 恋する小惑星
  • 地縛少年花子くん
  • SHOW BY ROCK!!ましゅまいれっしゅ!!
  • ソマリと森の神様
  • ドロヘドロ
  • へやキャン△
  • へんたつ
  • マギアレコード 魔法少女まどか☆マギカ外伝
  • 魔術士オーフェンはぐれ旅

春クール

このあたりから次以降のクールにずれ込む作品が出始めた。今回は最初に放送したクールに入れている。

夏クール

  • 宇崎ちゃんは遊びたい!
  • うまよん
  • GREAT PRETENDER
  • デカダンス
  • ノー・ガンズ・ライフ 第2期

秋クール

映画

参考: 2020年度のおすすめアニメランキング【あにこれβ】

robolectric4.5-alpha-1かそれより新しいバージョンを使ってSQLiteのCommon Table Expressionを使ったテーブルのJVMテストをしよう

事の発端:

RepositoryのJVMテストを書くために、Room.inMemoryDatabaseBuilder()を使ってDBを作りテスト対象に注入したところ、テスト実行時に次のような例外が出た*1

Caused by: com.almworks.sqlite4java.SQLiteException: [1] DB[1] prepare() CREATE VIEW `view_foo` AS WITH bar AS ( ... [near "WITH": syntax error]

で、ぐぐったらズバリのissueが上がっていた。

github.com

このissueはrobolectric 4.1の頃に開かれたらしいが、コメントによるとこのころのrobolectricが依存していたsqlite4javaのバージョンは0.282で、最新のバージョンはCTEをサポートしたsqlite3.8.7に対応している1.0.392とのこと。

sqlite4javaのバージョンアップに関するPRはrobolectric4.4のリリース以降にマージされたようで、4.5-alpha-1より新しいバージョンに含まれている様子。今回は4.5-alpha-3が最新だったのでこれを使ってテストが通ることを確認した。

確認していないが、過去に書いた記事: akihito104.hatenablog.com に関しても影響があるかもしれない。あるといいなあ。

*1:実行時のrobolectricのバージョンは4.4