隠語大好き14歳

言えるわけないじゃないそんなこと。

男もすなる Unit Test といふものを、女もしてみむとてするなり。

とりあえず読む

kotlin.test - Kotlin Programming Language

  • @BeforeTest くらいかな。 mock を reset するのに使ってる。 reset のあたりのドキュメント読んだ感じだと @Test の各論の最中に reset かますような無粋なコード書いてるとひとつのテストの中でいろんなことやりすぎじゃん?って書いてあった、ホントそうだと思う。
    • でもよく考えるといまデフォルトでは @Test のそれぞれを実行するために別個のテストインスタンスが作られてるから、 reset するのって実は意味ないんじゃんね? JUnit 5 への移行を見越してそう書いてる、とでも言い訳しておくのかな、ハハハ・・・
  • なんにも考えないで IDE のおっしゃるがままに補完してて気が付かなかったけど、 whenever って mockito-kotlin が定義している DSL なのね 。完全に when = 一回こっきり / whenever = 何度呼んでも、みたいなふるまいの格差があるのかなと思ってたけど名前で勘違いして思い込んでただけだった、 kotlin でテスト書くときには whenever つかおう。

blog.philipphauer.de

  • いろいろなしがらみもあって JUnit 4 なんだけど、 JUnit 5 使おうって気になるねえ。
  • @Test それぞれを実行するために、デフォルトではテストクラスのインスタンスをそれぞれ作ってる、やっぱりそうなんだ。そのかわり並列に実行できるってことかな。再利用するようにして @BeforeTest で reset するようになったらインスタンス生成は押さえられるけど直列に実行することにはなるよねえ? 2 s vs 250 ms in the example とは書いてあったので再利用によって 1/8 くらいに時間節約される方が効くよってことかな。
  • data class の toString() はたしかにステキ。
data class Item(val itemId: Long, val itemName: String)
interface FooBarContract {
  interface Presenter {
    fun showItem(itemId: Long)
  }

  interface ViewProxy {
    fun bind(presenter: Presenter)
    fun showItemName(itemName: String)
    fun showItemId(itemId: Long)
    fun showError(message: String)
  }

  interface Repository {
    fun fetchItem(itemId: Long): Single<Item>
  }
}
class ItemNotFoundException(val message: String): Exception(message)
class FooBarPresenter(
  private val repository: FooBarContract.Repository,
  private val viewProxy: FooBarContract.Presenter,
  private val uiScheduler: Scheduler = AndroidSchedulers.mainThread(),
  private val disposables: CompositeDisposable = CompositeDisposable()
) : FooBarContract.Presenter {
  init {
    viewProxy.bind(presenter)
  }

  override fun showItem(itemId: Long) {
    repository.fetchItem(itemId)
      .observeOn(uiScheduler)
      .subscribe(
        this::showItem,
        { e -> viewProxy.showError(e.message)}
      ).let(disposables::add)
  }

  @VisibleForTesting
  internal fun showItem(item: Item) {
    viewProxy.showItemName(item.itemName)
    viewProxy.showItemId(item.itemId)
  }
}
class FooBarPresenterTest {
  private val item1L: Item = Item(1L, "existing_item")
  private val repository: FooBarContract.Repository = mock {
    on { fetchItem(1L) }.thenReturn { Single.just(Item1L) }
  }
  private val viewProxy: FooBarContract.ViewProxy = mock()
  private val uiScheduler: Scheduler = Schedulers.trampoline()
  private val disposables: CompositeDisposable = mock()

  private val presenter = FooBarPresenter(repository, viewProxy, uiScheduler, disposables)

  @Test
  fun `viewProxy should be bind to presenter when initializing the presenter`() {
    verify(viewProxy).bind(presenter)
  }

  @Test
  fun `showItem(itemId) should show an Item with the ID if exists`() {
    val presenter = this.presenter.let(::spy)

    presenter.fetchItem(1L)
    
    verify(presenter).showItem(item1L)
  }

  @Test
  fun `showItem(itemId) should show error if not exist`() {
    val presenter = this.presenter.let(::spy)
    
    val itemId = 2L
    val errorMessage = "item ID $itemId is not found"
    doReturn(Single.error<Item>(ItemNotFoundException(errorMessage))
      .whenever(repository).fetchItem(itemId)

    presenter.fetchItem(itemId)
    
    verify(presenter, never()).showItem(any())
    verify(viewProxy).showError(errorMessage)
  }

  @Test
  fun `showItem(item) should show the item`() {
    presenter.showItem(item1L)

    verify(viewProxy).run {
      showItemName(item1L.itemName)
      showItemId(item1L.itemId)
    }
  }
}

この場でテキトーに書いてるから、 import とか書いてないケド許して。

ひとこと

お題「もしもお年玉で100万円貰ったら」

えーっと、なんだろう、ホットカーペット買おうかなっ