1

私たちが現在取り組んでいることのうち、コトリンと一緒に作業している間に、Coroutinesを使用して非同期で実行したい操作を処理しています。単体テストサポートのKotlinでのコルーチンの使い方

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

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)は興味深いようですが、私は呼び出し側クラスがプレゼンターのためのコンテキストを起動するというアイデアは好きではありません。なぜなら、それは順番に非同期作業を行う依存関係を持っているからです。抽象的な考えとして、私は「あなたが何をしていても、UIのコンテキストで私に報告してください」というアイデアが気に入っていますが、物事を機能させるための修正として感じられ、異なるオブジェクト間での非同期機能。

とにかく、私の質問 短いサンプルから離れて、作業ユニットテストで、より大きなアーキテクチャ内でコルーチンを統合する方法について誰かが指摘していますか?私は、物事を見たいと思えば、あなたは犠牲を払わなければなりません。この質問は単なる孤立した例であるため、大きなプロジェクト内で本当の強固な統合を模索している間に、サンプルを作成するだけではありません。

あなたのご意見をお待ちしております。前もって感謝します。

+0

どのバージョンをお使いですか?一般的なアプローチは、kotlinx.coroutinesバージョン0.19とMockitoバージョン2.10.0で動作します(私はApiClientをモック可能にするために 'mock-maker-inline'を設定しなければなりません) –

+0

良い質問。 私はMockito 2.8.9とCoroutines 0.18を使用しています。私は後者を更新して、それが物事を少し助けたらどうなるか見てみることができると思う。 –

+0

Mockitoを更新する必要があります。私のメモリがうまく機能していれば、そのバージョンは機能を一時停止する機能を持っていませんでした。 –

答えて

1

私はある種のAsyncRunnerインターフェイスを持つアプローチを提案し、このAsyncRunnerインターフェイスの2つの実装を持っています。 1つはlaunch(UI)を使用してAndroidの実装になり、もう1つはブロックの実装になります(runBlockingを使用)。

正しいタイプのAsyncRunnerをapp内で実行し、ユニットテストで実行するコードを依存性注入によって実行する必要があります。あなたのコードでは、コルーチンを直接使用するのではなく、注入されたAsyncRunnerを使用して非同期コードを実行します。このAsyncRunner

実装例は次のようになります。taskパラメータは、タスクからデータを取得し、何かを行います(APIからデータをフェッチするなど)のコードとcompletionパラメータをブロックしたスレッドを表し

interface AsyncRunner { 
    fun <T>runAsync(task:() -> T, completion: (T) -> Unit) 
} 

class AndroidCoroutineAsyncRunner: AsyncRunner { 
    override fun <T>runAsync(task:() -> T, completion: (T) -> Unit) { 
     launch(UI) { 
      completion(async(CommonPool) { task() }.await()) 
     } 
    } 
} 

class BlockingCoroutineAsyncRunner: AsyncRunner { 
    override fun <T>runAsync(task:() -> T, completion: (T) -> Unit) { 
     runBlocking { 
      completion(async(CommonPool) { task() }.await()) 
     } 
    } 
} 

をそれらと一緒に。

関連する問題