2

しばらくの間、私たちは Kotlin を使用しており、現在注力していることの 1 つは、コルーチンを使用して、非同期で実行したい操作を処理することです。

使用例は明確で機能しますが、これをアーキテクチャ内にきれいな方法で統合するにはいくつかの問題があります。ドメインに焦点を当てたクラスのメソッドの実装を見ると、読みやすく、非同期機能からの「ノイズ」ができるだけ少なくなるという考えがあります。実際に使用しないと、非同期を使用できないことはわかっています。だから、このようなものを書くことは私が望むものです:

val data = someService.getData().await()
// work with data

しかし、これは私が防ぎたいことです:

launch(UI) {
  val data 
  val job = async(CommonPool) {
    data = someService.getData()
  }

  job.await()
  // work with data 
}

つまり、これらのドメインに焦点を当てたクラスの実用的な単体テストと組み合わせたいのですが、実際には機能しません。例を見てみましょう:

// Some dependency doing heavy work
class ApiClient {
    suspend fun doExpensiveOperation(): String {
        delay(1000)

        return "Expensive Result Set"
    }
}

// Presenter Class
class Presenter(private val apiClient: ApiClient,
                private val view: TextView) {

    private lateinit var data: String

    fun start() {
        log("Starting Presenter")
        runBlocking {
            log("Fetching necessary data")
            data = apiClient.doExpensiveOperation()
            log("Received necessary data")
        }

        workWithData()

        log("Started Presenter")
    }

    fun workWithData() {
        log(data)
    }

    private fun log(text: String) {
        view.append(text+"\n")
    }
}

// In an Activity
val presenter = Presenter(ApiClient(), someTextView)
presenter.start()

それは機能します (スクリーンショット: https://imgur.com/a/xG9Xw )。では、テストを見てみましょう。

class PresenterTest {
    // ... Declared fields

    @Before
    fun setUp() {
        // Init mocks (apiClient, textView)
        MockitoAnnotations.initMocks(this)

        // Set mock responses
        runBlocking {
            given(apiClient.doExpensiveOperation()).willReturn("Some Value")
        }

        presenter = Presenter(apiClient, textView)
    }

    @Test
    @Throws(Exception::class)
    fun testThat_whenPresenterStarts_expectedResultShows() {
        // When
        presenter.start()

        // Then
        Mockito.verify(textView).text = "Some Value\n"
    }
}

現在、このテストは理想的とは言えませんが、lateinit var データが初期化されていないため、意図したとおりに機能することを確認できるところまで到達することはありません。最終的には、ドメイン クラスの美学と可読性は、私がどこまで行きたいかということです。私は満足している実用的な作業例をいくつか用意しています。しかし、私のテストを機能させるのは難しいようです。

現在、この種のものについてオンラインでいくつかの異なる記事がありますが、実際には何もうまくいきませんでした. これ ( https://medium.com/@tonyowen/android-kotlin-coroutines-unit-test-16e984ba35b4 ) は興味深いようですが、プレゼンターのコンテキストを起動する呼び出しクラスのアイデアは好きではありません。 turn には、非同期処理を行う依存関係があります。抽象的な考えとしては、「プレゼンター、あなたが何をしても、UI コンテキストについて私に報告してください」というアイデアが好きですが、それはむしろ物事を機能させるための修正として感じられ、異なるオブジェクト間での非同期機能に対する共通の懸念につながります.

とにかく、私の質問: 短い例から離れて、単体テストを使用して、より大きなアーキテクチャ内にコルーチンを統合する方法について何か指針を持っている人はいますか? また、「物事を機能させたい場合は、犠牲を払わなければならない」とは異なるレベルで説得力があるため、物事の見方を変えさせる議論にも非常にオープンです。この質問は、例を機能させるだけではありません。これは孤立した例にすぎないため、大きなプロジェクト内での真の堅実な統合を探しています。

ご入力をお待ちしております。前もって感謝します。

4

2 に答える 2