其實關於測試的測試的基本概念昨天已經都提過了,今天我會基於昨天講的東西繼續完成 DataSource
的測試,但是昨天主要著重在一些基礎的內容,我略過了一些實作上的細節。今天提到的大部分都是偏實作的內容,就會比較詳細的說明這部分。
那就直接先建立一個 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
,有別於 runBlocking
, runBlockingTest
不會在啟動時耗費額外的時間。
因為 TasksLocalDataSource.getTask()
回傳一個 Result ,為了快速進行測試,我新增了一個Result.succeeded
方便快速對 Result 進行狀態判斷。
這樣又完成了一個測試,同樣的手法可以套用在 TasksLocalDataSource
的其他測試方法上,因為這部分太細節了就不再繼續講了,有興趣的讀者歡迎到這邊看看。