iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0

配合分支:day19/start,Repo: 按我

想起以前 Bug 的正式中文翻譯是「臭蟲」,太 Y2K 了先讓各位知道。好的,回到今天要討論的範疇,就是「檢查是否能夠加入的日記」的判斷,抽成另外一個 Class,例如 DiaryValidator 之類的。

為何要這麼做呢?其實這類判斷文字是否是空的,完全可以直接放在 ViewModel 就好,另外抽出來反而把事情弄的太複雜了,(開發界有句話:「預先做的最佳化,是萬惡之源。(Premature optimization is the root of all evil.)」)所以各位不要看到這種四散的判斷就小警總爆發進行大改動喔。
今天為了教學用途,試著請各位想想,如果照片日記未來發展之後,除了標題、內容是否為空以外,還需要檢查更多的東西,例如:日期可調整但需要檢查是否合理,未來發展了 PRO 功能需要判斷使用者有沒有付費⋯ 等等。

先將文字檢查抽出來作為 DiaryValidator

https://ithelp.ithome.com.tw/upload/images/20260907/2014161551XSF8pRqY.png

接著,只要調整 ViewModel 裡頭判斷日記是否合理的地方就行了。可以在 PhotoDiaryViewModel 之中建立 Validator 即可。

private val diaryValidator = DiaryValidator()

然後更新 PhotoDiaryViewModel

fun saveDiary(title: String, note: String) {
    val normalizedTitle = title.trim()
    val normalizedNote = note.trim()
    // 👇🏼 改成使用 validate
    val validationMessage = diaryValidator.validate(normalizedTitle, normalizedNote)

    // 👀 如果有任何錯誤,就回傳錯誤訊息就好
    if (validationMessage != null) {
        validationError = validationMessage
    } else {
        // ... 省略
    }
}

這類抽出來的程式,不含 Android Context(只有用到 Kotlin 內建的函式),就可以寫 JVM Unit Test,不需要開 Android 模擬器!前面提過 Kotlin 和 Java 關係難分難捨,Kotlin 在 Android 上會編譯成 Java bytecode 讓 Java 虛擬機器(就是 JVM!Java Virtual Machine)來跑,Android 是跑在 Google 手作的 Java 的虛擬機器上跑的 (相容於 Java 但不是 Java,這也是為何 Oracle 每次會來找碴的原因⋯),只要可以用 JVM 的就能直接跑!好處是跑測試的速度超快

補充:Android 用的是 Google 自家的 Android Runtime (ART),不是真正的 JVM;本機JVM 測試則是跑在開發電腦的 JVM 上。

androidTest 下面放的是 Android Unit Test (或叫 Instrumented tests),而 JVM Test 則放在 test 資料夾下。(兩者後面都和 main 相同,用程式結構去開資料夾即可)

https://ithelp.ithome.com.tw/upload/images/20260907/20141615H7lQf39Zd3.png

接下來的測試,和之前寫過的 PhotoDiaryViewModelTest 很類似,連 Before 和 After 的 Annotation 用法都一樣。跑測試的方法也一樣,就是按下 UI 左邊的綠色「播放鍵」按下「Run 'DiaryValidatorTest'」即可。

test 對應的資料夾下新增 DiaryValidatorTest

class DiaryValidatorTest {
    // 先留空白
}

不過 JVM Test class name 上面不用加上 @RunWith(AndroidJUnit4::class)。接著就可以寫相關的測試:

  1. 兩個都沒有寫,需要有錯誤訊息
  2. 有 Title -> 通過,錯誤訊息給空值
  3. 有 Note -> 通過,錯誤訊息給空值

兩個都沒有寫,需要有錯誤訊息

https://ithelp.ithome.com.tw/upload/images/20260907/20141615qusPhEHnvI.png

只要驗證 result 是錯誤訊息即可!

有 Title -> 通過,錯誤訊息給空值

https://ithelp.ithome.com.tw/upload/images/20260907/20141615rDC2W8qOSt.png

因為 DiaryValidator() 完全不用 Android 相關的元件,可以直接這樣 New 出來,非常爽。

有 Note -> 通過,錯誤訊息給空值

https://ithelp.ithome.com.tw/upload/images/20260907/201416155JWOZ1qk58.png

沒有錯誤訊息,代表通過啦。

三個測試都加入之後,再嘗試跑跑看。就目前的程式碼來看,會通過嗎?

補充

單一職責原則(Single Responsibility Principle, SRP)

每個 class 最好只負責一件事,例如這個 validator 就只做「查證」。如果做了與其名稱無關的其他事情(像是在 validator 裡頭去改日記內容),就做太多囉!
沒有 SRP 的話,維護的人就無法直接透過 class 名稱,去推測這個東西可以拿來做什麼事情。這樣就需要每次都進去看詳細實作,否則容易出現意料之外的行為,不太好。

經典的說法是:一個 class 應該只有一個改變的理由(only one reason to change),這個 class 唯一的改變理由就是:「日記要怎樣才算合格」的規則變了——例如前面說的,以後要檢查日期合理性、檢查 PRO 付費。這些全是同一個理由的延伸,改起來還是只動 DiaryValidator

TL;DR

  • 條判抽離成 Class:把「日記能不能新增」的判斷從 ViewModel 抽出來變成 DiaryValidator,是今天的主角;重點是認得「這段四散的判斷」可以搬去哪。
  • Validator(驗證器):只做一件事——檢查輸入合不合格,回傳錯誤訊息或 null;不重組、不修改日記內容。(不違反單一職責)
  • 提早最佳化(Premature optimization is the root of all evil):這種判斷其實放 ViewModel 就可以;今天抽出來是教學用,不代表看到重複判斷就該大改。
  • JVM Unit Test:不依賴 Android 元件、只用 Kotlin 內建函式的 class,就能在電腦的 JVM 上直接跑測試,不用開模擬器,速度超快。
  • testandroidTest 的差別:JVM 測試放 test,需要在 Android 裝置上跑的 Instrumented test 放 androidTest;兩者都用跟 main 相同的套件結構開資料夾。
  • @RunWith(AndroidJUnit4::class) 不是必備:JVM 測試的 class 上面不用加這個 Annotation;只有 Android 裝置上跑的測試才需要。
  • 單一職責原則(SRP):一個 class 只負責一件事、只有一個改變的理由;看 class 名稱就該推得到它做什麼,違反了就容易出現意料之外的行為。

上一篇
[Day18] 抽換更多東西,換得更好的測試
系列文
我的第一個手作りAndroid App!快樂學習物件導向程式設計!19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言