上一篇文章“03 微服務(wù)架構(gòu)下的測試策略”
我講到了**微服務(wù)架構(gòu)下的測試策略和質(zhì)量保障體系**,今天我來講講測試策略中的最底層測試——單元測試。
單元測試的價值
單元測試是一種白盒測試技術(shù),通常由開發(fā)人員在編碼階段完成,目的是驗(yàn)證軟件代碼中的每個單元(方法或類等)是否符合預(yù)期,即盡早在盡量小的范圍內(nèi)暴露問題。
我們都知道,問題發(fā)現(xiàn)得越早,修復(fù)的代價越小。毫無疑問,在開發(fā)階段進(jìn)行正確的單元測試可以極大地節(jié)省時間和金錢。如果跳過單元測試,會導(dǎo)致在后續(xù)更高級別的測試階段產(chǎn)生更高的缺陷修復(fù)成本。
如圖,假如有一個只包含兩個單元 A 和 B 的程序,且只執(zhí)行端到端測試,如果在測試過程中發(fā)現(xiàn)了缺陷,則可能有如下多種原因:
-
該缺陷由單元 A 中的缺陷引起;
-
該缺陷由單元 B 中的缺陷引起;
-
該缺陷由單元 A 和單元 B 中的缺陷共同引起;
-
該缺陷由單元 A 和單元 B 之間接口的缺陷引起;
-
該缺陷是測試方法或測試用例的錯誤導(dǎo)致的。
由此可見,忽略單元測試會導(dǎo)致后續(xù)發(fā)現(xiàn)缺陷時,要花費(fèi)較高的成本來確認(rèn)缺陷原因。
單元測試除了能夠在較早階段識別軟件中的錯誤,它還有如下價值。
-
反饋速度快:單元測試通常以自動化形式運(yùn)行,執(zhí)行速度非常快,可以快速反饋結(jié)果,跟持續(xù)集成結(jié)合起來,形成有效的反饋環(huán)。
-
重構(gòu)的有力保障:系統(tǒng)需要大規(guī)模重構(gòu)時,單測可以確保對已有邏輯的兼容,如果單元測試都通過,基本上可以保證重構(gòu)沒有破壞原來代碼邏輯的正確性。
-
使更熟悉代碼:寫單元測試的過程本身就是一個審視代碼的過程,可以發(fā)現(xiàn)一些設(shè)計(jì)上的問題(代碼設(shè)計(jì)的不可測試)、代碼編寫方面的問題(邊界條件的處理不當(dāng))等。
既然單元測試由開發(fā)人員來設(shè)計(jì)和執(zhí)行,那作為測試人員是不是就不需要學(xué)習(xí)這門技術(shù)了?不知道你是怎樣看待這個想法的,我的觀點(diǎn)是:
-
單元測試只是通常情況下由開發(fā)人員完成,并不是絕對的,在一些公司或項(xiàng)目里也存在測試人員完成的情況;
-
在你負(fù)責(zé)的模塊或服務(wù)里,第一級別的測試不是你來完成的,那么你更有必要去了解它的設(shè)計(jì)思路和執(zhí)行情況,這能幫助你發(fā)現(xiàn)單元測試可能存在的問題點(diǎn),也有利于你設(shè)計(jì)和執(zhí)行后續(xù)高級別的測試類型;
-
開發(fā)人員總是不太擅長做測試類的工作,當(dāng)你掌握了單元測試的技能,你便更有機(jī)會去幫助和影響到開發(fā)人員,贏得他對你的尊重,也有利于你們更好地合作;
-
這種想法是測試人員的常見想法,所以掌握單元測試技能在測試人員群體中也會是稀缺技能,因此,掌握它將會獲得額外的鍛煉機(jī)會和個人影響力,要知道,機(jī)會總是留給有準(zhǔn)備的人。
微服務(wù)下的單元測試類型
就像之前課程所說:微服務(wù)中最大的復(fù)雜性不在于服務(wù)本身,而在于微服務(wù)之間的交互方式,服務(wù)與服務(wù)之間常?;ハ嗾{(diào)用以實(shí)現(xiàn)更多更復(fù)雜的功能。
舉個例子,我們需要測試的是訂單類(Order)中的獲取總價方法(getTotalPrice()),而在該方法中除了自有的一些代碼邏輯外,通常需要去調(diào)用其他類的方法。比如這里調(diào)用的是用戶類(User)的優(yōu)惠等級方法(reductionLevel ())和商品類(Goods)中的商品價格方法(getUnitPrice())。很顯然,優(yōu)惠等級方法或商品價格方法,只要一方有錯誤,就會導(dǎo)致訂單類獲取總價方法的測試失敗?;谶@種情況,可以有兩種單元測試類型。
1. 社交型單元測試(Sociable Unit Testing)
如圖,測試訂單類的獲取總價方法(Order.getTotalPrice())時會真實(shí)調(diào)用用戶類的優(yōu)惠等級方法(User.reductionLevel())和商品類的商品單價方法(Goods.getUnitPrice())。將被測試單元視為黑盒子,直接對其進(jìn)行測試,這種單元測試稱之為社交型單元測試(Sociable Unit Testing)。
2. 孤立型單元測試(Solitary Unit Testing)
如圖,如果測試訂單類的獲取總價方法(Order.getTotalPrice())時,使用測試替身 (test doubles) 技術(shù)來替代用戶類的優(yōu)惠等級方法(User.reductionLevel())和商品類的商品單價方法(Goods.getUnitPrice())的效果。對象及其依賴項(xiàng)之間的交互和協(xié)作被測試替身代替,這種單元測試稱之為孤立型單元測試(Solitary Unit Testing)。
另外,上述提到的測試替身是一種在測試中使用對象代替實(shí)際對象的技術(shù),常用的技術(shù)如下。
-
樁代碼(Stubs):當(dāng)在對象上調(diào)用特定方法時,會對其進(jìn)行硬編碼(臨時代碼)的方式來代替真實(shí)代碼提供固定響應(yīng)。比如,某函數(shù) X 的實(shí)現(xiàn)中調(diào)用了一個函數(shù) Y,而 Y 不能調(diào)用,為了對函數(shù) X 進(jìn)行測試,就需要模擬一個函數(shù) Y,那么函數(shù) Y 的實(shí)現(xiàn)就是所謂的樁代碼。
-
模擬代碼(Mocks):模擬代碼跟樁代碼類似,它除了代替真實(shí)代碼的能力之外,更強(qiáng)調(diào)是否使用了特定的參數(shù)調(diào)用了特定方法,因此,這種對象成為我們測試結(jié)果的基礎(chǔ)。
根據(jù)被測單元是否與其交互者隔離,會產(chǎn)生以上兩種單元測試類型,這兩種類型的單元測試在微服務(wù)測試中都起著重要作用,它們用來解決不同的測試問題。
由上圖可知,在微服務(wù)架構(gòu)中,不同組成使用的單元測試類型不同:
特別注意:當(dāng)微服務(wù)的(網(wǎng)關(guān)+倉庫+資源+服務(wù)層)與(域邏輯)之比相對較大時,單元測試可能收益不大。常見的情況有小型服務(wù)或某些幾乎只包含了網(wǎng)關(guān)+倉庫+資源+服務(wù)層等內(nèi)容的服務(wù),例如適配服務(wù)等。
如何開展單元測試?
在實(shí)際項(xiàng)目過程當(dāng)中,應(yīng)該怎樣開展單元測試呢?通常來說,可以通過如下四個步驟來進(jìn)行。
1. 確定使用單元測試的代碼范圍
雖然單元測試很重要,但并不是所有代碼都需要進(jìn)行單元測試,可以重點(diǎn)關(guān)注核心模塊代碼或底層代碼,如重要的業(yè)務(wù)邏輯代碼或通用組件類等。
2. 確定技術(shù)選型(以 Java 語言為例)
單元測試中的技術(shù)框架通常包括單元測試框架、Mock 代碼框架、斷言等。
-
單元測試框架:和開發(fā)語言直接相關(guān),最常用的單元測試框架是 Junit 和 TestNG,總體來說,Junit 比較輕量級,它天生是做單測的,而 TestNG 則提供了更豐富的測試功能,測試人員對它并不陌生,這里不多做介紹。
-
Mock 代碼框架:常見的有 EasyMock、Mockito、Jmockit、Powermock 等。
-
斷言:Junit 和 TestNG 自身都支持?jǐn)嘌?,除此還有專門用于斷言的 Hamcrest 和 assertJ。
關(guān)于它們的優(yōu)劣網(wǎng)絡(luò)上已有非常多的文章,這里不再贅述。綜合來看,個人比較推薦使用Junit+Mockito+assertJ,我建議你根據(jù)自己的需求選型。
3. 引入衡量單測覆蓋情況的代碼覆蓋率工具
只單純地看單元測試的執(zhí)行通過率還比較單一,為了更全面地看到測試的覆蓋情況,可以借助代碼覆蓋率工具和技術(shù)。在 Java 語言里,常用覆蓋率工具有 Jacoco、Emma 和 Cobertura,個人推薦使用 Jacoco。
4. 接入持續(xù)集成工具
接入持續(xù)集成工具是為了形成工具鏈,將單元測試、代碼覆蓋率統(tǒng)計(jì)集成在一起,使得代碼有提交時便自動觸發(fā)單元測試用例的執(zhí)行,并伴隨有代碼覆蓋率的統(tǒng)計(jì),最后可以看到單元測試報告的數(shù)據(jù)(用例通過情況和代碼層面各個維度的覆蓋數(shù)據(jù))。接著可以判斷是否需要修改代碼,這便形成了一個代碼質(zhì)量的反饋環(huán),如下圖所示。
后續(xù)的文章還會講解到代碼覆蓋率工具和持續(xù)集成工具。
單元測試最佳實(shí)踐
了解了如何開展單元測試,那么如何做到最好呢?我們都知道,代碼產(chǎn)生錯誤無非是對一個業(yè)務(wù)邏輯或代碼邏輯沒有實(shí)現(xiàn)、實(shí)現(xiàn)不充分、實(shí)現(xiàn)錯誤或過分實(shí)現(xiàn),所以無論是拆解業(yè)務(wù)邏輯還是拆解邏輯控制時都要做到 MECE 原則(全稱 Mutually Exclusive Collectively Exhaustive,中文意思是“相互獨(dú)立,完全窮盡”,即日常溝通中常說的“不重不漏”)。
“不重不漏”說起來容易做起來難,為了努力做到它,寫出好的單元測試,可以遵循如下具體的實(shí)踐規(guī)范。
-
好的單元測試要符合 AIR 特點(diǎn):Automatic(自動化)、Independent(獨(dú)立性)、Repeatable(可重復(fù))。為了湊夠一個單詞 AIR 的效果,所以有了如上順序,但我覺得從實(shí)際的落地順序上看,應(yīng)該是 A->R->I。優(yōu)先保障單元測試能夠自動化執(zhí)行,釋放手工介入,再使單元測試可以重復(fù)執(zhí)行,這樣可以使得簡單的用例先高效地執(zhí)行起來,再逐漸追求用例的相互獨(dú)立性。
-
常見的規(guī)范或標(biāo)準(zhǔn)做法有(以 Java 為例)
-
代碼目錄規(guī)范:單元測試代碼必須放在“src/test/java”目錄下,Maven 采用“約定優(yōu)于配置”的原則,并對工程的目錄布局做了約定——測試代碼存放 src/test/java 目錄,單元測試相關(guān)的配置資源文件存放 src/test/resources 目錄。源碼構(gòu)建時會跳過此目錄,而單元測試框架默認(rèn)是掃描此目錄。
-
測試類命名規(guī)范:同一個工程里測試類只用一種命名風(fēng)格,推薦采用[類名]Test.java 或 Test[類名].java 的風(fēng)格,比如源類名為 AccountServiceImpl.java,那么測試類名為 AccountServiceImplTest.java 或者 TestAccountServiceImpl.java。
-
測試方法命名規(guī)范:同一個工程里測試方法只用一種命名風(fēng)格,推薦采用 test[源方法名]_[后綴]的風(fēng)格。比如源方法名為 login(),則測試方法可以命名為 testLogin_XxxSuccess()、testLogin_XxxNotExist()、testLogin_XxxFail()。
-
測試數(shù)據(jù)要求:盡量使用生產(chǎn)環(huán)境的測試數(shù)據(jù)以保障有效性和多樣性。
-
顆粒度要求:要保證測試粒度足夠小,有助于精確定位問題。單測粒度一般是方法級別,最好不要超過類級別。只有測試粒度小才能在出錯時盡快定位到出錯位置,一個待測試方法建議關(guān)聯(lián)一個測試方法,如果待測試方法邏輯復(fù)雜分支較多,建議拆分為多個測試方法。
-
驗(yàn)證結(jié)果必須要符合預(yù)期:簡單來說就是單元測試必須執(zhí)行通過,執(zhí)行失敗時要及時查明原因并修正問題。
-
代碼要遵守 BCDE 原則,以保證被測試模塊的交付質(zhì)量。
-
B:Border,邊界值測試,包括循環(huán)邊界、特殊取值、特殊時間點(diǎn)、數(shù)據(jù)順序等。
-
C:Correct,正確的輸入,并得到預(yù)期的結(jié)果。
-
D:Design,與設(shè)計(jì)文檔相結(jié)合,來編寫單元測試。
-
E:Error,強(qiáng)制錯誤信息輸入(如:非法數(shù)據(jù)、異常流程、非業(yè)務(wù)允許輸入等),并得到預(yù)期的結(jié)果。
-
-
實(shí)踐風(fēng)格:需加注釋、遵守命名規(guī)范、公共方法抽象等保證可讀性。編寫測試代碼時,有兩種實(shí)踐風(fēng)格(至少要有相應(yīng)的注釋來區(qū)分)。
-
準(zhǔn)備-執(zhí)行-斷言(Arrange-Act-Assert):先準(zhǔn)備用于測試的對象,然后觸發(fā)執(zhí)行,最后對輸出和行為進(jìn)行斷言。
-
給定-當(dāng)-那么(Given-When-Then):給定某個上下文,當(dāng)發(fā)生某些事情,那么期望某些結(jié)果。
-
-
執(zhí)行速度要盡量快:單個 CASE 的運(yùn)行時間推薦不超過 5 秒?,這樣才能在持續(xù)集成中盡快暴露問題。
-
必須能自動驗(yàn)證:單測要能報錯,不能只有調(diào)用,不準(zhǔn)使用 System.out 等來進(jìn)行人工驗(yàn)證,必須使用 Assert 來驗(yàn)證。
-
必須要有邏輯驗(yàn)證能力和強(qiáng)度:不允許使用恒真斷言(如:Assert. assertTrue (true) ; )不允許使用弱測試斷言(如測試方法返回數(shù)據(jù),只驗(yàn)證其中某個單字段值就當(dāng)作通過)。
-
必須有很強(qiáng)的針對性:可以有多個 Assert 斷言,但每個測試方法只測試一種情況(如一個方法涉及 3 種異常需要去覆蓋測試,就寫三個不同的測試方法)。
-
必須獨(dú)立穩(wěn)定,可重復(fù)執(zhí)行:單元測試通常會被放到持續(xù)集成中,如果單測對外部環(huán)境(發(fā)布環(huán)境、網(wǎng)絡(luò)、服務(wù)、中間件等)有依賴,容易導(dǎo)致持續(xù)集成機(jī)制的不可用。對于測試需要的任何條件,都應(yīng)該讓它們成為測試自身的一個自動化組成部分。
-
同一個工程里只用一種代碼框架;同一個工程里在能夠滿足需求的情況下只用一種單測框架、只用一種 Mock 框架、只用一種內(nèi)存數(shù)據(jù)庫等。
-
單元測試遵守基本質(zhì)量卡點(diǎn)要求:增量及全量卡點(diǎn)必須有,但覆蓋率具體卡點(diǎn)要求可以根據(jù)業(yè)務(wù)差異化、分階段地要求,如起步推廣階段,提升覆蓋率階段,最終覆蓋率目標(biāo)。但一般來說,行覆蓋率大于等于 60%(經(jīng)驗(yàn)值),分支覆蓋率大于等于 80%(經(jīng)驗(yàn)值),所有單測通過率 100%。核心業(yè)務(wù)、核心應(yīng)用、核心模塊的增量代碼確保單元測試增量覆蓋率達(dá)到要求,并且全量單測 CASE 通過。
上述規(guī)范和實(shí)踐經(jīng)驗(yàn)比較多,可能會因?yàn)槁涞仉y度和成本而使開發(fā)人員望而卻步,事實(shí)上可以采取“小步快跑”的方式,逐次提升不同方面的要求,拉長落地的戰(zhàn)線。
總結(jié)
本篇內(nèi)容講解了單元測試的定義:它是一種軟件測試方法,目的是驗(yàn)證軟件代碼中的每個單元(方法或類等)是否符合預(yù)期,即盡早在盡量小的范圍內(nèi)暴露錯誤。
接著講解了微服務(wù)架構(gòu)下常見的交互場景,測試方式和對象的不同會出現(xiàn)社交型單元測試和孤立型單元測試兩種單元測試類型。
然后講解了實(shí)際如何開展單元測試,先確定要測試的代碼范圍,再引入單測框架、mock 框架、斷言類型、代碼覆蓋率工具和持續(xù)集成工具,使代碼提交過程形成一個有效的單元測試質(zhì)量反饋環(huán)。緊接著我又給出了一系列的最佳實(shí)踐或規(guī)范,包括類和方法的命名規(guī)范、目錄規(guī)范、數(shù)據(jù)要求、驗(yàn)證結(jié)果要求、運(yùn)行速度、質(zhì)量卡點(diǎn)等,相信這些內(nèi)容可以幫助你更好地設(shè)計(jì)和實(shí)現(xiàn)單元測試。文章來源:http://www.zghlxwxcb.cn/news/detail-796874.html
相關(guān)鏈接
https://www.martinfowler.com/articles/microservice-testing/#testing-unit-introduction
單元測試框架
TestNG官網(wǎng): https://testng.org/doc/
TestNG教程: https://www.yiibai.com/testng/
Junit官網(wǎng): https://junit.org/junit5/
Mock代碼框架
Mockito: https://site.mockito.org/
jMock: http://jmock.org/
Easymock: http://www.easymock.org/
Powermock: https://github.com/powermock/powermock
Mock框架對比: https://stackoverflow.com/questions/22697/whats-the-best-mock-framework-for-java
斷言
Hamcrest: http://hamcrest.org/JavaHamcrest/
assertJ: https://joel-costigliola.github.io/assertj/assertj-core.html
覆蓋率工具
Jacoco: https://www.jacoco.org/jacoco/trunk/index.html
Emma: http://emma.sourceforge.net/
Cobertura: https://cobertura.github.io/cobertura/文章來源地址http://www.zghlxwxcb.cn/news/detail-796874.html
到了這里,關(guān)于04 單元測試:怎樣提升最小可測試單元的質(zhì)量?的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!