都100%代碼覆蓋了,還會(huì)有什么問(wèn)題?
引言
很多人看到這個(gè)標(biāo)題時(shí),都會(huì)想“你都100%代碼覆蓋了,怎么還會(huì)有問(wèn)題呢?”
讓我們看一下代碼例子:
- public class TestCalculator {
- public Double add(Double a, Double b) {
- return a + b;}
- }
再看看用junit寫(xiě)出的測(cè)試代碼:
- @Test
- public void testAdd() {
- Double a = new Double(1);
- Double b = new Double(2);
- Double c = new Double(3);
- assertEquals(c, testCalculator.add(a, b));
- }
當(dāng)我們使用EclEmma或者Jacoco來(lái)進(jìn)行覆蓋測(cè)試時(shí),對(duì)于這個(gè)類,我們將得到100%測(cè)試覆蓋率。
一切看起來(lái)都那么的***,真是這樣的嗎?
好吧,讓我們來(lái)來(lái)看看另一個(gè)測(cè)試,當(dāng)其中一個(gè)變量為null時(shí),返回值將會(huì)是什么?
- @Test
- public void testAddNullPointerException() {
- Double a = new Double(1);
- Double b = null;
- Double c = new Double(3);
- assertEquals(c, testCalculator.add(a, b));
- }
好了,你會(huì)發(fā)現(xiàn)盡管覆蓋率為100%,但程序卻拋出了NullPointerException。
那么肯定有人會(huì)問(wèn),這樣的話單元測(cè)試覆蓋率的高低都不能作為衡量項(xiàng)目代碼質(zhì)量的指標(biāo),那么我們要單元測(cè)試還有什么用?
首先,我想我們可能搞錯(cuò)了測(cè)試覆蓋的定義。
我們先聽(tīng)聽(tīng)Martin Fowler對(duì)于測(cè)試覆蓋的定義:
Test coverage is a useful tool for finding untested parts of a codebase. Test coverage is of little use as a numeric statement of how good your tests are. |
(圖片來(lái)自:http://t.cn/R06jK5U)
他認(rèn)為:把測(cè)試覆蓋作為質(zhì)量目標(biāo)沒(méi)有任何意義,我們應(yīng)該把它作為一種發(fā)現(xiàn)未被測(cè)試覆蓋的代碼的手段。
所以100%的代碼覆蓋率還值得追求嗎?
當(dāng)然,這應(yīng)該是每個(gè)程序員畢生的追求之一,但是如果從項(xiàng)目角度考慮ROI(投入產(chǎn)出比),對(duì)于需要快速上線的短期項(xiàng)目,需要注重的是讓測(cè)試覆蓋核心功能代碼。如果你的項(xiàng)目是一個(gè)長(zhǎng)期項(xiàng)目,那么高覆蓋率是非常有必要的,它意味著高可維護(hù)性,以及更少的bug。(前提是你的測(cè)試采用TDD/BDD方式編寫(xiě),我見(jiàn)過(guò)將測(cè)試代碼寫(xiě)的一團(tuán)糟的人,看著他的代碼,我寧愿重新寫(xiě)一遍。)
那么對(duì)于一個(gè)項(xiàng)目來(lái)說(shuō),覆蓋率應(yīng)該達(dá)到多少?
其實(shí)沒(méi)有適用于所有項(xiàng)目的數(shù)值,每個(gè)項(xiàng)目都應(yīng)有自己的閾值,但共性是,測(cè)試必須覆蓋主要業(yè)務(wù)場(chǎng)景,代碼的邏輯分支也必須盡可能的覆蓋。
如何改進(jìn)你的項(xiàng)目代碼覆蓋率?
首先我們要閱讀和理解項(xiàng)目代碼,找出其中需要測(cè)試并且與業(yè)務(wù)強(qiáng)相關(guān)的代碼,結(jié)合sonar等代碼質(zhì)量管理平臺(tái),從代碼編寫(xiě)規(guī)范、復(fù)雜度、重復(fù)代碼等方面進(jìn)行代碼重構(gòu),進(jìn)一步提高項(xiàng)目的可維護(hù)性與可讀性。
這也意味著重構(gòu),重構(gòu)的同時(shí),你需要更多的測(cè)試來(lái)保證你重構(gòu)代碼的正確性。
其次要對(duì)code coverage進(jìn)行度量分析,那么我們應(yīng)該怎么度量code coverage?
一般來(lái)說(shuō)我們從以下四個(gè)維度來(lái)度量,如上圖所示:
- 行覆蓋率(line coverage):度量被測(cè)代碼中每個(gè)可執(zhí)行語(yǔ)句是否都被執(zhí)行到,但不包括java import,空行,注釋等。
- 函數(shù)覆蓋率(function coverage):度量被測(cè)代碼中每個(gè)定義的函數(shù)是否都被調(diào)用。
- 分支覆蓋率(branch coverage):度量被測(cè)代碼中每一個(gè)判定的分支是否都被測(cè)試到。
- 語(yǔ)句覆蓋率(statement coverage):度量被測(cè)代碼是否每個(gè)語(yǔ)句都被執(zhí)行。
所以行覆蓋率的高低不能說(shuō)明項(xiàng)目的好壞,我們要從多方面進(jìn)行思考,一般我們遵循的標(biāo)準(zhǔn)應(yīng)是:函數(shù)覆蓋率 > 分支覆蓋率 > 語(yǔ)句覆蓋率。
代碼覆蓋率最重要的意義在于:
- 閱讀分析之前項(xiàng)目中未覆蓋部分的代碼,進(jìn)而反推在前期QA以及相關(guān)測(cè)試人員在進(jìn)行黑盒測(cè)試設(shè)計(jì)時(shí)是否考慮充分,沒(méi)有覆蓋到的代碼是否是測(cè)試設(shè)計(jì)的盲點(diǎn),為什么沒(méi)有考慮到?是需求或者UX設(shè)計(jì)不夠清晰,還是測(cè)試設(shè)計(jì)的理解有誤。
- 檢測(cè)出程序中的廢代碼,可以逆向反推代碼設(shè)計(jì)中不合理的地方,提醒設(shè)計(jì)/開(kāi)發(fā)人員理清代碼邏輯關(guān)系,提升代碼質(zhì)量。
- 代碼覆蓋率高不能說(shuō)明代碼質(zhì)量高,但是反過(guò)來(lái)看,代碼覆蓋率低,代碼質(zhì)量絕對(duì)不會(huì)高到哪里去,可以作為測(cè)試自我審視的重要工具之一。
結(jié)束語(yǔ)
單元測(cè)試的覆蓋率并不只是為了取悅客戶或者管理層的數(shù)據(jù),它能夠?qū)崒?shí)在在反應(yīng)項(xiàng)目中代碼的健康程度,幫助我們更好的改善了代碼的質(zhì)量,增加了我們對(duì)所編寫(xiě)代碼的信心。