提高單元測試覆蓋率的意義與價值

1. 什麼是單元測試覆蓋率?

單元測試覆蓋率是一種軟件測試的度量指標,指在所有功能代碼中,完成了單元測試的代碼所佔的比例。有很多自動化測試框架工具可以提供這一統計數據,其中最基礎的計算方式爲:

單元測試覆蓋率 = 被測代碼行數 / 參測代碼總行數 * 100%

Note: 一般情況下, 參測代碼總行數是指排除配置文件、以及測試代碼本身的所有功能代碼的總行數。


2. 單元測試的度量方式

最常見的單元測試覆蓋率的度量方式有以下三種:

1. 行覆蓋率 / 語句覆蓋 這種覆蓋率統計方式是最爲基礎的,它可以用於體現參測代碼中已被執行和未被執行的代碼行(語句),從而可以進一步推斷代碼的邏輯覆蓋是否全面。

2. 分支覆蓋 這種覆蓋率統計方式是用於統計代碼中所有判斷分支是否都被覆蓋,如

...
if (condition)
{
    Operation_1();
} 
else 
{
    Operation_2();
}
...

語句就會根據 condition 的值產生兩個不同的分支操作,那麼在統計分支覆蓋時,就需要對兩個分支都進行校驗。值得注意的是,上例中的代碼,其分支覆蓋可以被行覆蓋所取代,也就是說若上面代碼的行覆蓋率爲100%, 則其分支覆蓋率亦爲100%。

但是如果將代碼換爲三元表達式,如

...
condition ? Operation_1() : Operation_2();
...

此時,行覆蓋率在統計時,只要這一行代碼有被執行,那麼行覆蓋率必然爲100%,並不關心是否兩種情況都被執行過。但如此一來就沒辦法通過行覆蓋率保證其分支覆蓋率爲100% 了。

3.條件覆蓋 這種覆蓋統計方式是針對代碼中產生多分支,並且每個分支的激活條件比較複雜的情況。 代碼的分支一般都是通過流程控制語句產生的,而流程控制語句再激活一個分支時是需要條件的,依然以之前的代碼爲例,若condition 是幾個布爾值的組合,

...
if (Condition())
{
    Operation_1();
} 
else 
{
    Operation_2();
}
...

private bool Condition () {
  return condition_1 || condition_2 && condition_3;
}

那麼此時,代碼依然只有兩個分支,但是由於激活條件是取決於condition_1, condition_2, condition_3的組合情況,那麼在校驗條件覆蓋時,就應該考慮所有可能的組合情況,也就是8種測試用例。這樣纔可以使條件覆蓋的覆蓋率達到100%。全面考慮條件覆蓋的做法就可以達到優化測試的效果

其實,覆蓋率的統計方式還是用很多其他種類的,這裏給出的只是幾種最基礎的。但不管怎樣,完備的單元測試行覆蓋都是其他度量方式的基礎。當行覆蓋率達標之後,開發人員纔會有精力修改、優化測試用例,從而提高分支覆蓋率、條件覆蓋率等其他更具業務價值的單元測試覆蓋率。


3. 提高單元測試覆蓋率的意義與價值

img

摘自《TestCoverage》Martin Fowler

有些時候,我們想提高項目測試覆蓋率的閾值,比如從45% 提高到80%,又或者從 95% 提高到100%。 那麼這個時候,我們就需要問一問自己,爲什麼要做這樣的事情?提高覆蓋率的意義與價值何在?

1. 是想通過單元測試來保證代碼質量? 單元測試的覆蓋率高與代碼質量高,二者並沒有直接關係,這是因爲覆蓋率的高低完全是可以**“造假”的。一些沒有實際業務價值的測試用例因運而生,甚至發生類似 AssertionFreeTesting 的情況,使覆蓋率“虛高”**。 因此,我們沒有依據說明高覆蓋率就能確保好的代碼質量。

2. 是想通過單元測試保證業務邏輯不會出錯? 用單元測試保證業務邏輯,看上去很有道理。可是仔細一想,就會發現這一點,其實有些不切實際。一個業務功能的實現並不僅僅依賴於某一個方法、某一個類,那麼通過單元測試能夠保證的業務邏輯也是十分有限的,不可能做到**“不會出錯”。 可以試想,如果僅僅依靠測試金字塔最底層的單元測試,使其覆蓋率達標就能保證業務邏輯不出錯,那麼爲什麼還需要更高層的集成測試和功能測試,甚至探索性測試呢? 另一方面,如果是想將單元測試與業務邏輯相綁定,那麼方向也不應是提高覆蓋率,而應該是提供更加符合業務場景的測試用例給已有的單元測試**,也就是提高已有的單元測試質量。

如此一來,這兩個問題都無法通過提高單元測試覆蓋率解決,那麼我們提高覆蓋率還有什麼意義與價值呢?

測試覆蓋率幫助我們找到沒有被測試的代碼提高測試覆蓋率,可以讓我們在重構、優化這些沒有被測試的代碼時更有底氣

但是,最大的前提是你的測試代碼是值得信任的

那麼現在,我們可以考慮一下這兩種情況了:

  1. 將覆蓋率從 45% 提高到 80%。
  2. 將覆蓋率從 95% 提高到100%。

對於第一種情況,不到一般的初始測試覆蓋率而言,首先會讓開發人員在進行重構時很沒有安全感,由於未被測試覆蓋的代碼量太多,從而無法快速發現改動是否會對未覆蓋的代碼帶來不良影響。 因此,在這種情況下,提高代碼的覆蓋率,從而讓更多的代碼被測試,讓開發者在重構時能更放心,這對項目更有益。

而對於第二種情況,可以看到覆蓋率已經很高了,那麼這個時候, 首先看看開發者經常改動或重構的代碼是否都被覆蓋,如果是,則重點應該放在優化測試用例上,看看是否所有的邊界條件都被測試,所有的分支都被覆蓋等等。因爲這時,提高已有的單元測試質量,讓測試用例更貼近業務場景所帶來的收益會遠遠大於進一步提高測試覆蓋率所帶來的收益。 否則,一旦重構了未被覆蓋的參測代碼,就應該爲改代碼添加單元測試,使之被覆蓋,從而確保功能的正常運作。


4. 總結

  1. 單元測試僅用來保證代碼所對應的功能正常,若想將之與業務結合,需要貼合業務場景的測試用例。
  2. 單元測試覆蓋率僅用來找出未被測試的代碼,若想通過覆蓋率保證業務邏輯,需要進一步優化已有單元測試的質量。
  3. 提高單元測試覆蓋率僅對之前沒有測試覆蓋或覆蓋極其不足的代碼有顯著增益,若想對已經被測試高度覆蓋的代碼進行優化,需要着眼於提升已有的測試用例質量。
  4. 團隊合作開發,應儘量保證自己每次提交的代碼都已經全部被測試覆蓋

5. 參考文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章