TDD自動測試
前言
以往接觸自動測試,覺得常落入為何要寫測試的迷惘,尤其是當包袱已經很沉重的系統,要寫測試真的會愈來愈讓人無力,甚至已經到了為測試而寫測試到最後放棄。
但上了91大大的課程之後,突然豁然開朗不少,為了怕忘記,當天就趕快寫一下部落格來加強自己的印象。
測試有何好處?
有寫測試的好處不勝枚舉,可以對曾經出現過的bug加上測試案例,然後大聲對此bug宣告你已經死了。又或者是在重構程式碼時,可以大刀闊斧的改革,也不怕改壞既有的功能。除此之外,測試案例現在還可以產生文件,我的天阿,這簡直是太神奇了!
但我最欣賞的效益,我覺得是寫了測試之後平行開發的能力。自己也跑過不少的專案,但最常遇到的場景,尤其是傳統公司,在做跨部門合作時,都要跟別的部門的開發者喬時間。但專案的時程如果就是那麼趕時,那要撰寫高端功能的開發者就會面臨必須先等底層的一些服務開發完成,才有辦法開始開發程式,這無形中浪費了不少時間,也是一種內耗的表現。但測試程式卻能完全解決這樣的問題,因為,需求全都在測試案例中描述了出來,只要靠著IOC(相依於介面)就能神奇地解決此問題。
IOC是啥?
在了解測試之前,我理解的IOC,就是抽介面而已,然後可以用多型的方式選擇自己想要的實作。可是這就會是剛接觸測試的人遇到的第一個迷惘,為何每個類別都要墊一個介面,這到底有啥意義?因為大部分的類別都只有一個實作而已。
但玩了測試之後,我突然領悟介面是個魔法,抽介面是很有意義的!因為他大幅降低物件的相依性,就像平行開發,如果底層的Production Code還沒開發完成,就真的沒辦法在真實環境上跑相依於此底層的程式碼,可是透過介面的方式,我只需要具體描述出我負責的物件該完成甚麼任務,然後透過mock和stub的方式,我可以透過介面假裝這個物件存在,並且依照我的Scenerio來模擬出我跟這個底層物件的互動,只要我的Scenerio正確,我就能確保我的程式碼邏輯是照我所想的在執行,等到別模組的開發人員將底層開發完成,只需再補上整合測試,系統就可以安全的上線了。
那如何模擬並驗證與其他物件互動?
這是我遇到的第二個迷惘,最簡單的驗證就是呼叫某方法後,驗證回傳值是否正確,但因為有些物件,呼叫方法可能並沒有回傳值,所以,無法直接驗證回傳值來達到我想要驗證的效果。但只要理解"因果"這個觀念,似乎一切就豁然開朗了,所有物件提供的方法,就算沒有回傳任何資訊,但它的存在一定是有因果的,只要被呼叫的"因"被觸發,你一定會得到一個"果",但"果"的來源,可能是此物件某個屬性被改變,也有可能是造成其他方法會有不同的邏輯運算,而正確的測試就是要將這個"果"給找出來,然後就可以完成一個完整的需求scenerio。但若真的找不到任何的"果"的話,那就代表此物件的設計是有問題的,因此物件的方法被呼叫,卻又沒有任何事情發生,那這些程式碼根本沒有存在的價值。
這裡順便筆記一下,一般人容易發生的第三個迷惘就是,一個scenerio到底要包含多少東西才是最小單位的單元測試?會有人認為,單元測試都說要最小粒度了,那當然一個方法就一個單元測試,不就最小了嗎?但只要看上面的"因果"案例,就可以知道,很多時候不可能只單獨呼叫一個方法就達成你的需求scenerio,可能會還需要在呼叫第二個方法來判斷"果"是否正確,所以,單元測試的粒度還是要看需求的scenerio而定,沒有一定的標準。
假如互動的類別來自第三方廠商又該如何?
第四個迷惘又來了,剛剛的案例,可能都是自家人寫的程式,只要大家互相約定好介面,就可以很簡單的用介面模擬出物件的互動並驗證,但假如互動的類別來自第三方廠商呢?寫信給對方,請對方提供介面?別做夢了!這個問題乍看之下,還真的無解,但只要把一些物件導向觀念或設計模式搬出來,似乎又沒那麼難了,在設計模式中,轉接器模式就是適用這種場景,開發一個自家類別,將與第三方廠商的互動封裝起來,在把此類別墊一個介面,後面的做法就又回到原本的方式了,簡直太神奇了!
額外筆記
沒有人程式一開始就可以寫到完美,而技術債最大的來源,就是public屬性的濫用。public開得愈多,系統改不動的風險就愈大,而且別人要使用這個物件時,也會更困惑。因為太多方法可以選擇,會不知道要用哪一個。
因此,想要寫出高維護性的物件,節制public方法的宣告可以帶來很大的好處,至少在更改物件內部方法時,不會影響到其他程式。
結論
自動測試最重要的環節在於將產品需求翻譯成scenerio的能力,基本要做到IOC才有辦法將測試的隔離性做出來。並且透過一次只關注一個焦點的觀念,當scenerio失敗時,便能很快速地朝對的方向追問題。
另外,自動測試不可能一步登天,尤其是既有系統的技術債已經非常沉重時,一刀劃出防火牆,從這刀之後的程式必須要包含測試程式一起交付才是正解。
只要跨出了測試程式的第一步,哪怕測試涵蓋率只有1%,未來的系統將會因為跨出的這一步,而發展出天堂和地獄的。