iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
0
自我挑戰組

Android Architecture 及 Unit Test系列 第 20

[Day 20] Test:Part 2 DataSource

  • 分享至 

  • xImage
  •  

其實關於測試的測試的基本概念昨天已經都提過了,今天我會基於昨天講的東西繼續完成 DataSource 的測試,但是昨天主要著重在一些基礎的內容,我略過了一些實作上的細節。今天提到的大部分都是偏實作的內容,就會比較詳細的說明這部分。

Create TasksLocalDataSourceTest

那就直接先建立一個 TasksLocalDataSourceTest 吧!同樣開在 sharedTest ,今天一樣會用到 DB 的東西所以我們照樣先把 Database 創建出來,不過因為測試內容稍大一些所以把這個測試標記成 MediumTest

接著在 @Before 建立要測試的目標 TasksLocaldataSource ,由於測試時是以同步執行的,所以這裡我們無法使用 Dispatchers,IO ,Coroutines 為此提供了一個 TestCoroutineScope ,可以將 Dispatchers.Main 切換成 Unit Test 專用的狀態。

因此這裡要在 TasksLocaldataSource 的 constructor 放入 Dispatchers.Main ,並且實作一個 Rule 來把 Dispatchers.Main 切換成測試專用的 TestCoroutineDispatcher

首先建立 MainCoroutineRule 把 CoroutineDispatcher 切換成 TestCoroutineDispatcher

@ExperimentalCoroutinesApi
class MainCoroutineRule : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(this.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
    }
}

之後就可以在測試中使用:

@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@MediumTest
class TasksLocalDataSourceTest {

    private lateinit var localDataSource: TasksLocalDataSource
    private lateinit var database: ToDoDatabase
    
    @ExperimentalCoroutinesApi
    @get:Rule
    var mainCoroutineRule = MainCoroutineRule()

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        ......
        localDataSource = TasksLocalDataSource(database.taskDao(), Dispatchers.Main)
    }

    @After
    fun cleanUp() {
        ......
    }
}

我們先開一個測試,目標是:

先存一個 Task 到 Database ,並且執行 localDataSource.getTask() 驗證結果

同樣 follow Given-When-Then 的原則完成,下面我先附上完成的測試:

    @Test
    fun when_save_task_then_retrieves_task_success() = runBlockingTest {
        // GIVEN
        val newTask = Task("title", "description", true)
        localDataSource.saveTask(newTask)

        // WHEN
        val result = localDataSource.getTask(newTask.id)

        // THEN
        assertThat(result.succeeded, `is`(true))
        result as Success
        assertThat(result.data.title, `is`("title"))
        assertThat(result.data.description, `is`("description"))
        assertThat(result.data.isCompleted, `is`(true))
    }
    
val Result<*>.succeeded
    get() = this is Success && data != null

這裏由於要讓測試可以同步執行,我使用了 Coroutines 建議的 runBlockingTest ,有別於 runBlockingrunBlockingTest 不會在啟動時耗費額外的時間。

因為 TasksLocalDataSource.getTask() 回傳一個 Result ,為了快速進行測試,我新增了一個
Result.succeeded 方便快速對 Result 進行狀態判斷。

這樣又完成了一個測試,同樣的手法可以套用在 TasksLocalDataSource 的其他測試方法上,因為這部分太細節了就不再繼續講了,有興趣的讀者歡迎到這邊看看。


上一篇
[Day 19] Test:Part 1 Datebase Dao
下一篇
[Day 21] Test:Part 3 Repository
系列文
Android Architecture 及 Unit Test30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言