單元測試的效果問題
很多項目經理不願意執行單元測試,因為這個東西的收益,很難用鞭子抽出來。
因為單元測試很容易進入特定的陷阱。
考慮如下代碼(隨便從Linux Kernel中拷貝出來的):
static int hclge_get_vector(struct hnae3_handle *handle, u16 vector_num, struct hnae3_vector_info *vector_info) { struct hclge_vport *vport = hclge_get_vport(handle); struct hnae3_vector_info *vector = vector_info; struct hclge_dev *hdev = vport->back; int alloc = 0; int i, j; vector_num = min(hdev->num_msi_left, vector_num); for (j = 0; j < vector_num; j++) { for (i = 1; i < hdev->num_msi; i++) { if (hdev->vector_status[i] == HCLGE_INVALID_VPORT) { vector->vector = pci_irq_vector(hdev->pdev, i); vector->io_addr = hdev->hw.io_base + HCLGE_VECTOR_REG_BASE + (i - 1) * HCLGE_VECTOR_REG_OFFSET + vport->vport_id * HCLGE_VECTOR_VF_OFFSET; hdev->vector_status[i] = vport->vport_id; hdev->vector_irq[i] = vector->vector; vector++; alloc++; break; } } } hdev->num_msi_left -= alloc; hdev->num_msi_used += alloc; return alloc; }
錯誤的測試考量是,這個函數基於i, j做了兩重循環,所以準備一個hdev的一個vector_status數組,,然後讓它進行循環,跑起來,看看覆蓋率達到沒有,有沒有出現死機,如果有修正測試樁,測試完成,覆蓋率,用例數,完全滿足要求,工作完美完成,收工領飯盒。
正確的考量是,先看懂這個代碼是幹什麼的:它的原理是在hdev中有個數組,裡面有一個域表示這個數組成員是否有效,如果無效,就分配出去(並置為有效),相同的方法分配多次,直到完成整個分配過程。
好了,知道這個要點,就要忘掉這個代碼了。單元測試的目的是發現「肉眼review看不見的錯誤」,這種錯誤主要是那些循環,跳出,判斷的時候有沒有想錯了的東西,那種東西要靠各種邊界用例來發現。比如這樣:
1) 在vector_status中預設10個成員,4個有效:然後分配0個,1個,3個,4個,5個,1000個,看分配結果對不對。
2) 在vector_status中預設10個成員,全部無效:然後分配5個,看結果是否正確
3)在vector_status中預設10個成員,全部有效,然後分配1個,5個,9個,10個,11個, 1000個,看結果是否正確
這樣就能發現那些判斷是否正確,有沒有考慮不周的情況。
兩個測試行為,從項目經理,乃至同伴看來,行為完全一樣,只是一個過腦子,一個不過腦子,測試效果完全不同。聚焦成功的團隊才能實施單元測試,聚焦「我沒錯」的團隊,做這種事情基本上是浪費時間。中間的情況是,測試執行工程師可能還沒有掌握這種基本的技能,這就需要花時間來輔導了。
而代碼Review應該解決什麼問題呢。還是拿這個例子來說:從演算法上來看一看,解決這樣一個分配問題,尼瑪需要兩重循環嗎?腦子生鏽了吧?尼瑪這也敢開始UT?信不信捏死你?——你看,這是代碼Review乾的活:)
從效果上來說,我們的經驗是:ST主要發現組合功能邏輯,發現的是端到端的錯誤,比如正在收發包的時候,修改網卡的MTU;對VF reset以後重新ifconfig。這種組合的功能,ST和IT的性價比是最高的。UT主要發現的是一般ST進入不了的流程的錯誤,比如大流量的時候鏈路突然閃斷重連,這個過程一般ST根本模擬不出來,不靠UT就很難發現。而RV一般發現的是覆蓋性測試無法進行全覆蓋的問題。比如系統狀態機的所有可能情況,每種狀態下,所有功能是否繼續生效?這種通過測試是做不到覆蓋的,但Review的時候審視整個狀態機的行為,是很容易發現錯誤的。
補充說明:有人可能不知道我說的單元測試是什麼,那麼請參考:從單元測試理解軟體。(所以,請注意,這裡的測試的「單元」,是C文件,不是函數,你看到我的例子是一個函數,但如果這個函數調用了本C文件的另一個函數,那個函數是不打樁的)
推薦閱讀: