iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0

上一篇講了測試怎麼做,這一篇來寫測試
因為本篇內容都需要context所以都是使用instrumented test

測試ViewModel

因為這個應用的ViewModel已經和DataBase綁定了,因此寫起會比較複雜

@RunWith(AndroidJUnit4::class)  
class MyDatabaseTest {
	// Tests
}

首先生成一個模板,並在其中加入三個需要用到的變數

private lateinit var db: AppDatabase  
private lateinit var dao: TodoDao  
private lateinit var vm: GlobalVM

之後要先設定好開啟與結束

@Before  
fun setup() {  
    val context = ApplicationProvider.getApplicationContext<Context>()  
    db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)  
        .allowMainThreadQueries()  
        .build()  
    dao = db.todoDao()  
    vm = GlobalVM(dao)  
}  
  
@After  
fun teardown() {  
    db.close()  
}

在Test裡面的context需要用Test模擬出的,之後用inMemoryBuilder,建立暫時的資料庫
在這裡的vm不需要使用factory初始化,直接參數丟入就好

測試讀寫

fun insertAndRead() = runTest {  
    val todo1 = TodoItem(0, "a", "a", "1", true)  
    val todo2 = TodoItem(0, "b", "b", "2", true)  
    vm.submitTodo(todo1)  
    vm.submitTodo(todo2)  
    assertEquals(2, vm.getGroupInfo().first().size)  
    assertEquals("a", vm.getGroupTodo("1").first().first().title)  
}

測試更改

雖然更改功能還沒有時做在UI裡面,但這不妨礙先測試邏輯

@Test  
fun change() = runTest {  
    val todo1 = TodoItem(0, "a", "a", "1", true)  
    val todo2 = TodoItem(0, "b", "b", "2", true)  
    vm.submitTodo(todo1)  
    vm.submitTodo(todo2)  
    assertEquals("a", vm.getGroupTodo("1").first().first().title)  
    val target = vm.getGroupTodo("1").first().first()  
    vm.focusTodo(target)  
    target.title = "c"  
    vm.submitTodo(target)  
    assertEquals("c", vm.getGroupTodo("1").first().first().title)  
}

測試UI

測試UI必須要有composeRule

@get:Rule  
val composeTestRule = createComposeRule() // 宣告Compose Rule

這是Android的示範程式

@Test
fun counterTest() {
    val myCounter = mutableStateOf(0) // State that can cause recompositions.
    var lastSeenValue = 0 // Used to track recompositions.
    composeTestRule.setContent {
        Text(myCounter.value.toString())
        lastSeenValue = myCounter.value
    }
    myCounter.value = 1 // The state changes, but there is no recomposition.

    // Fails because nothing triggered a recomposition.
    assertTrue(lastSeenValue == 1)

    // Passes because the assertion triggers recomposition.
    composeTestRule.onNodeWithText("1").assertExists()
}

從這邊就可以看出來,UI Test比一潘的邏輯測試牽扯的層面更多更廣,也因此不太好寫

var navController: TestNavHostController

這個可以模擬navController,但是需要引入dependency,這邊不多說

測試TodoItemBox

這邊是測試顯示還有按鈕有沒有用

@Test  
fun testTodoBox() {  
    val todoItem = TodoItem(0, "a", "content", "A", true)  
    var check = false  
  
    composeTestRule.setContent {  
        TodoItemBox(  
            todoItem = todoItem,  
            onChecked = { check = it },  
            onModified = {},  
            onDelete = {}  
        )  
    }  
  
    // 1. 驗證文字有顯示  
    composeTestRule.onNodeWithText("a").assertExists()  
    composeTestRule.onNodeWithText("content").assertExists()  
  
    // 2. 驗證 checkbox 狀態正確 (因為 todoItem.done = true)    
    composeTestRule.onNode(isToggleable()) // Checkbox 是 toggleable
	    .assertIsOn()  
  
    // 3. 模擬點擊 checkbox
	composeTestRule.onNode(isToggleable())  
        .performClick()  
  
    // 驗證 callback 被呼叫  
    assert(!check)  
}

為何不做大的UI test

其實我一開始也有想要寫較大的UI test的,但是專案的各種資料嚴重耦合,所以難以測試
我大概說明

  • ViewModel資料來源綁定DB
  • 大部分元件都依靠ViewModel的函數功能
    這樣就導致一個test裡面會加入各種DB、ViewModel等的內容,也會影響到原本資料
    如果要避免這個問題
  • ViewModel的資料來源變成一個Repository,與資料庫分開
  • 元件不直接依靠ViewModel的函數取得資料,通過參數傳遞
    但是這對於這個小專案而言有點太超標了,不夠經濟,所以沒寫,但是一些非常簡單的Test還是可以的

上一篇
Day 26:使用Test確認程式的邏輯
系列文
現代Android jetpack compose開發入門27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言