單元測試最佳實踐:如何最大程度地利用測試自動化
單元測試是一種眾所周知的做法,但是還有很多改進的空間!在這篇文章中,最有效的單元測試最佳實踐,包括一路最大化自動化工具的方法。我們還將討論代碼覆蓋率、模擬依賴關(guān)系和整體測試策略。
什么是單元測試?
單元測試是測試應(yīng)用程序的單個單元或組件的一種做法,目的是驗證每個單元或組件是否正常工作。通常,一個單元應(yīng)該只占應(yīng)用程序的一小部分——在Java中,它通常是單個類。請注意,我并不是在這里嚴(yán)格定義“單元”,而是由開發(fā)人員來決定每個測試的測試代碼范圍。
人們有時將“單元測試”與“集成測試”或“端到端測試”相對比。區(qū)別在于,通常通過進行單元測試來驗證單個可測試單元的行為,而集成測試則是在一起驗證多個組件或整個應(yīng)用程序的行為。就像我說過的那樣,對“單元”的定義并沒有嚴(yán)格定義,具體取決于每個測試的范圍。
為什么要進行單元測試?
單元測試是一種行之有效的技術(shù),可確保軟件質(zhì)量,并帶來很多好處。這是(多個)進行單元測試的重要原因:
- 單元測試可以驗證您的每款軟件不僅可以在今天正常運行,而且可以在將來繼續(xù)運行,為將來的開發(fā)奠定了堅實的基礎(chǔ)。
- 單元測試可以在生產(chǎn)過程的早期階段識別出缺陷,從而降低了在開發(fā)周期的后期階段修復(fù)缺陷的成本。
- 單元測試的代碼通常更安全地重構(gòu),因為可以快速重新運行測試以驗證行為沒有改變。
- 編寫單元測試迫使開發(fā)人員考慮設(shè)計生產(chǎn)代碼以使其適合于單元測試的程度,并使開發(fā)人員從不同的角度看待他們的代碼,鼓勵他們在實現(xiàn)過程中考慮極端情況和錯誤情況。
- 在代碼審查過程中包含單元測試可以揭示修改后的代碼或新代碼應(yīng)如何工作。另外,審閱者可以確認(rèn)測試是否良好。
不幸的是,過于頻繁的開發(fā)人員要么根本不編寫單元測試,要么沒有編寫足夠的測試,要么不維護它們。我了解——單元測試有時編寫起來很棘手,或者維護起來很耗時。有時會有一個截止日期,感覺就像編寫測試會讓我們錯過那個截止日期。但是沒有編寫足夠的單元測試或沒有編寫好的單元測試是一個容易陷入的陷阱。
因此,請考慮以下有關(guān)如何編寫干凈、可維護的自動化測試的最佳實踐建議,這些建議可以用最少的時間和精力為您提供單元測試的所有好處。
單元測試最佳實踐
讓我們看一些構(gòu)建,運行和維護單元測試以達到最佳結(jié)果的最佳實踐。
單元測試應(yīng)該值得信賴
如果代碼損壞并且只有代碼損壞,則測試必須失敗。否則,我們將無法相信測試結(jié)果在告訴我們什么。
單元測試應(yīng)可維護且可讀
當(dāng)生產(chǎn)代碼更改時,通常需要更新測試,也可能需要調(diào)試。因此,不僅對于編寫它的人,而且對于其他開發(fā)人員,都必須易于閱讀和理解該測試。為了清楚和易讀,請始終組織和命名測試。
單元測試應(yīng)驗證單個用例
好的測試只能驗證一件事,而只能驗證一件事,這意味著通常情況下,它們只能驗證一個用例。遵循此最佳實踐的測試更簡單,更易理解,這對于可維護性和調(diào)試很有好處。驗證不止一件事的測試很容易變得復(fù)雜且維護耗時。不要讓這種情況發(fā)生。
另一個最佳實踐是使用最少數(shù)量的斷言。有人建議每個測試只聲明一個(可能有點太嚴(yán)格了)。這個想法是集中于僅驗證所測試用例所需的內(nèi)容。
單元測試應(yīng)隔離
測試應(yīng)該可以在任何機器上以任何順序運行,而不會互相影響。如果可能,測試應(yīng)不依賴于環(huán)境因素或全局/外部狀態(tài)。具有這些依賴項的測試較難運行,并且通常不穩(wěn)定,從而使其更難以調(diào)試和修復(fù),最終花費的時間超過了所節(jié)省的時間(請參見上面的可信賴信息)。
幾年前,馬丁·福勒(Martin Fowler)撰寫了有關(guān)“單獨的”與“可社交的”代碼的文章,以描述應(yīng)用程序代碼中的依賴關(guān)系用法,以及如何相應(yīng)地設(shè)計測試。在他的文章中,“單獨的”代碼不依賴于其他單元(它更加獨立),而“可聯(lián)系的”代碼確實與其他組件交互。如果應(yīng)用程序代碼是單獨的,則測試很簡單...但是對于正在測試的社交代碼,您可以構(gòu)建“單獨”或“社交”測試。“社交測試”將依賴于真實的依賴關(guān)系以驗證行為,而“單獨測試”則將受測代碼與依賴關(guān)系隔離開。您可以使用模擬來隔離被測代碼,并為“可社交”代碼構(gòu)建“單獨”測試。我們將在下面查看如何執(zhí)行此操作。
圖1:社交測試與孤立測試。資料來源:馬丁·福勒(Martin Fowler),2014年,“UnitTest”
通常,使用模擬作為依賴項會使我們的測試人員生活更加輕松,因為我們可以為社交代碼生成“單獨的測試”。復(fù)雜代碼的社交測試可能需要大量設(shè)置,并且可能違反隔離和可重復(fù)的原則。但是,由于模擬是在測試中創(chuàng)建和配置的,因此它是獨立的,我們可以更好地控制依賴項的行為。另外,我們可以測試更多的代碼路徑。例如,我可以返回自定義值或從模擬中引發(fā)異常,以涵蓋邊界或錯誤情況。
單元測試應(yīng)自動化
確保在自動化過程中運行測試。這可以是每天、每小時或在持續(xù)集成或交付過程中。團隊中的每個人都需要訪問并查看報告。作為一個團隊,討論您關(guān)心的指標(biāo):代碼覆蓋率、修改后的代碼覆蓋率、正在運行的測試數(shù)量、性能等。
通過查看這些數(shù)字可以學(xué)到很多東西,這些數(shù)字的巨大變化通常表明可以立即解決回歸問題。
結(jié)合使用單元測試和集成測試
邁克爾·科恩(Michael Cohn)的書《成功實現(xiàn)敏捷:使用Scrum進行軟件開發(fā)》使用測試金字塔模型解決了這一問題(請參見下圖中的插圖)。這是描述測試資源理想分配的常用模型。這個想法是,隨著您進入金字塔,測試通常會更復(fù)雜、更脆弱、運行更慢、調(diào)試更慢。較低的級別更加隔離和集成、更快、更易于構(gòu)建和調(diào)試。因此,自動化的單元測試應(yīng)占您測試的大部分。
單元測試應(yīng)驗證所有細(xì)節(jié)、極端情況和邊界條件等。應(yīng)更謹(jǐn)慎地使用組件、集成、UI和功能測試,以驗證API或應(yīng)用程序的整體行為。手動測試應(yīng)該在整個金字塔結(jié)構(gòu)中所占的比例最小,但對于發(fā)布驗收和探索性測試仍然有用。該模型為組織提供了高度的自動化和測試覆蓋范圍,因此他們可以擴大測試工作量,并將與構(gòu)建、運行和維護測試相關(guān)的成本降至最低。
單元測試應(yīng)在有組織的測試實踐中執(zhí)行
為了在各個級別上推動測試的成功,并使單元測試過程具有可擴展性和可持續(xù)性,您將需要一些其他實踐。首先,這意味著在編寫應(yīng)用程序代碼時編寫單元測試。一些組織在應(yīng)用程序代碼之前編寫測試(測試驅(qū)動或行為驅(qū)動的編程)。重要的是測試與應(yīng)用程序代碼緊密結(jié)合。測試和應(yīng)用程序代碼甚至應(yīng)該在代碼審查過程中一起審查。評論有助于您理解所編寫的代碼(因為他們可以看到預(yù)期的行為)并可以改善測試!
與代碼一起編寫測試不僅是針對新行為或計劃中的更改,對于修復(fù)錯誤也至關(guān)重要。您修復(fù)的每個錯誤均應(yīng)進行測試,以驗證該錯誤是否已修復(fù)。這樣可以確保該錯誤在將來保持不變。
對測試失敗采取零容忍策略。如果您的團隊忽略測試結(jié)果,那為什么還要進行測試呢?測試失敗應(yīng)該表明是真正的問題......因此,在浪費質(zhì)量檢查人員的時間之前或更早就解決這些問題,或更糟糕的是,它們會進入發(fā)布的產(chǎn)品。
解決故障所需的時間越長,這些故障最終將花費您的組織更多的時間和金錢。因此,在重構(gòu)期間運行測試,請在提交代碼之前立即運行測試,并且在測試通過之前也不要將任務(wù)視為“完成”。
最后,維護那些測試。正如我之前說過的,如果您在應(yīng)用程序更改時沒有使這些測試保持最新狀態(tài),則它們會失去價值。尤其是如果它們失敗了,則失敗的測試會浪費時間和金錢進行每次失敗的調(diào)查。當(dāng)代碼更改時,根據(jù)需要重構(gòu)測試。
如您所見,要使單元測試中的金錢和時間回報最大化,就需要在應(yīng)用最佳實踐方面進行一些投資。但最終,這些回報值得進行初始投資。
那代碼覆蓋率呢?
通常,代碼覆蓋率是對自動化測試運行期間執(zhí)行了多少生產(chǎn)代碼的度量。通過運行一組測試并查看代碼覆蓋率數(shù)據(jù),您可以大致了解正在測試的應(yīng)用程序數(shù)量。
代碼覆蓋范圍很多,最常見的是行覆蓋范圍和分支覆蓋范圍。大多數(shù)工具專注于行覆蓋率,它僅告訴您是否覆蓋特定行。分支更加精細(xì),因為它告訴您是否覆蓋了代碼的每個路徑。
代碼覆蓋率是一項重要指標(biāo),但是請記住,增加覆蓋率是達到目的的一種手段。這對于發(fā)現(xiàn)測試中的差距非常有用,但這并不是唯一要關(guān)注的事情。注意不要花費太多的精力來嘗試達到100%的覆蓋率——這甚至可能是不可能或不可行的,實際上,測試的質(zhì)量是很重要的。話雖如此,為您的項目至少達到60%的覆蓋率是一個不錯的起點,而設(shè)定80%或更高的覆蓋率是一個好的目標(biāo)。顯然,由您決定目標(biāo)是什么。
如果您擁有自動化的工具,這不僅很有價值,它不僅可以測量代碼覆蓋率,還可以跟蹤測試覆蓋了多少修改后的代碼,因為這可以使您了解是否編寫了足夠的測試以及生產(chǎn)代碼的更改。
在此處查看來自Parasoft的報告和分析中心的示例代碼覆蓋率報告,如果您正在使用Parasoft Jtest進行單元測試,則可以瀏覽該示例:
要記住的另一件事是,在編寫新測試時,請注意不要只關(guān)注行覆蓋范圍,因為單行代碼可能會導(dǎo)致多個代碼路徑,因此請確保您的測試驗證這些代碼路徑。線覆蓋率是一個有用的快速指示器,但這并不是唯一要尋找的東西。
增加覆蓋率的最明顯方法就是簡單地為更多的代碼路徑添加更多的測試,以及被測方法的更多用例。增加覆蓋范圍的有效方法是使用參數(shù)化測試。對于Junit4,有內(nèi)置的Junit4參數(shù)化功能和諸如JunitParams之類的第三方庫。Junit5具有內(nèi)置的參數(shù)化功能。
最后,如果您尚未跟蹤測試范圍,強烈建議您開始。有很多工具可以提供幫助,例如Parasoft Jtest。首先測量您當(dāng)前的覆蓋范圍數(shù)字,然后為應(yīng)該覆蓋的范圍設(shè)定目標(biāo),首先解決重要的差距,然后再從那里開始工作。
總結(jié)
盡管單元測試是確保軟件質(zhì)量的可靠技術(shù),但仍被認(rèn)為是開發(fā)人員的負(fù)擔(dān),許多團隊仍在為此而苦苦掙扎。為了充分利用測試和自動化測試工具,測試必須是可信賴的、可維護的、可讀的、自包含的,并且必須用于驗證單個用例。自動化是使單元測試可行和可擴展的關(guān)鍵。
此外,軟件團隊需要練習(xí)良好的測試技術(shù),例如與應(yīng)用程序代碼一起編寫和審查測試,維護測試以及確保立即跟蹤和糾正失敗的測試。采用這些單元測試最佳實踐可以快速改善您的單元測試結(jié)果。