單元測試
這部分主要內容是講解什麼是測試,什麼又是單元測試,以及java中常用的測試框架junit的學習。
1.1軟體測試
軟體測試,描述一種用來促進鑑定軟體的正確性、完整性、安全性和質量的過程。換句話說,軟體測試是一種實際輸出與預期輸出之間的審核或者比較過程。
1.2 單元測試
單元測試,是針對程式模組(軟體設計的最小單位)來進行正確性檢驗的測試工作。程式單元是應用的最小可測元件,像是單個程式、函數、過程等。通常來說,工程師每修改一次就會進行最少一次的單元測試,在編寫程式的過程前後可能要進行多次的測試,以證實程式達到規格書要求的工作目標。
1.3單元測試的優點
l 提升軟體質量
在大多數互聯網企業中開發的工程師在研發過程中都會頻繁的執行測試範例,執行失敗的單元測試能幫助我們快速盤查和定位問題,使問題在線上前完成修復。
l 增加重構自信
程式重構通常是牽一髮動全身,當修改底層數據結構時,上層經常會受到影響,有時候只是簡單的修改一段名稱,就會引以一連串錯誤,但是在有單元測試的前提下,在重構程式時就會多一份信心。
1.4單元測試的基本原則
宏觀上,單元測試要符合AIR原則,微觀上單元測試的程式層面要符合BCDE原則。程式在線上運行時可能感覺不到測試範例的存在和價值,但在代碼質量的保障上卻是非常關鍵的。新增程式碼應該同步增加測試範例,修改程式邏輯時也應該保證測試用範例成功執行。AIR具體包括 : - A : Automatic - I : Independent - R: Repeatable 。
單元測試應該是全自動執行的,測試範例通常會被頻繁的觸發執行,執行過程必須完全自動化才有意義。如果單元測試的輸出結果需要人工介入檢察,那麼他一定是不合格的。單元測試中不允許使用System.out來進行人工驗證,為了保證單元測試穩定可靠且便於維護,需要保證其獨立性,範例之間不允許互相調用,也不允許執行次序先後依賴。如下範例程式所示testMethod2需要調用testMethod1。在執行時互相調用,也不允許出現執行次序的先後依賴。如下範例所示testMethod2會重複執行驗證testMethod1,導致執行效率降低。更嚴重的是,testMethod1的驗證失敗會影響testMethod2的執行。
@Test
public void testMethodl() {
...
}
@Test
public void testMethod2() {
testMethodl ();
...
}
撰寫單元測試時要保證測試粒度足夠小,這樣有助於精確定位問題,單元測試範例默認是方法級別的。單測不負責檢查跨類或只跨系統的交互邏輯,那是集成測試需要覆蓋的範圍。撰寫單元測試範例時,為了保證被測模組的交付質量,需要符合BCDE原則。 - B : Border邊界直測試。 - C : Correct 正確的輸入,並得到預期的結果。 - D : Design與設計文件檔相結合,來編寫單元測試。 - E : Error單元測試的目標是證明程式有錯,而不是程式沒錯。為了發現程式中淺在的錯誤,我們需要在編寫測試範例時有一些強制的錯誤輸入(如非法數據、異常流程、非業務允許輸入等)來得到預期的錯誤結果。
1.5單元測試的覆蓋率
單元測試是一種白盒測試,測試者依據程式的內部結構來實現測試代碼。單測覆蓋率是指業務代碼被單測測試的比例和程度,它是衡量單元測試好壞的一個很重要的指標,各類覆蓋率指標從粗到細,從強到弱如下。1.粗粒度的覆蓋率 粗粒度的覆蓋包括類覆蓋和方法覆蓋兩種。類覆蓋是指類別中只要有方法或變量被測試範例調用或執行到,那麼就說這個類別被覆蓋了。方法覆蓋同理,只要測試範例在執行過程中,某個方法被調用了,則無論執行了該方法中的多少行代碼,都可認為該方法被覆蓋了。從實際測試場景來看,無論是類覆蓋率還是方法覆蓋率來衡量測試覆蓋範圍,其粗粒都太粗了。2.細粒度的覆蓋 細粒度的覆蓋包括以下幾種。 - 行覆蓋(Line Coverage) 用來度量可執行的語句是否被執行到,行覆蓋率的計算公式的分子是執行到的語句行數,分母是全部的可執行語句行數,範例如下:
package com.daming.junit_test;
public class CoverageSampleMethods {
public Boolean testMethod(int a, int b, int c) {
boolean result = false;
if (a == 1 && b == 2 || c == 3) {
result = true;
}
return result;
}
}
以上方法有五行可執行語句和三個帶入參數,針對此方法編寫測試用範例如下:
@Test
@DisplayName("line coverage sample test")
void testCoverageSample() {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertTrue(coverageSampleMethods.testMethod(1, 2, 0));
}
以上測試範例的行覆蓋率是100%,但是在執行過程c==3的條件判斷根本沒有被執行到,a!=1並且c!=3的情形難道不該測試一下嗎?由此可見,行覆蓋的覆蓋強度並不高,但由於容易計算,因此在主流的覆蓋率工具中,它依然是十分常見的參考指標。 -分支覆蓋(Branch Coverage),用來度量程式中每一個判定分支是否都有被執行到。分支覆蓋率計算公式中的分子是程式中被執行到的分支數,分母是程式中所有分支的總數。比如前面例子中,(a==1 && b==2 || c==3)整個條件為一個判定,測試數據應至少保證此判定為真和為假的情況都被覆蓋到。分支覆蓋容易與下面要說的條件覆蓋混淆,因此先介紹判定覆蓋的定義,然後在對比介紹兩者的區別。 -條件判定覆蓋( Condition Decision Coverage )條件判定覆蓋要求設計足夠的測試範例,能夠讓判定中每個條件的所有可能情形都被執行一次,同時每個條件本身的所有可能結果也至少執行一次。例如(a== 1 && b == 2 ||c=3) 這個判定中包含了三種條件,即a== 1 、 b == 2 、c=3。為了方便了解,下面我們仍使用行覆蓋率中的testMethod方法作為被測方法,測試範例如下:
@ParameterizedTest
@DisplayName("Condition Decision coverage sample test result true")
@CsvSource({ "0,2,3", "1,0,3" })
void testConditionDecisionCoverageTrue(int a, int b, int c) {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertTrue(coverageSampleMethods.testMethod(a, b, c));
}
@Test
@DisplayName("Condition Decisior coverage sample test result false")
void testConditionDecisionCoverageFalse() {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertTrue(coverageSampleMethods.testMethod(0, 0, 0));
}
通過@ParameterizedTest我們可以定義一個參數測試,@CsvSource註解使得我們可以通過定義一個string來定義多次執行測試時的參數列表,而每個string值通過逗號分隔後的結果,就是每一次測試執行時的實際參數值。我們通過兩個測試用範例分別測試判定結果為true和false這兩種情況。第一個測試範例testConditionDecisionCoverageTrue會執行兩次,a、b、c這三個參數的值分別為0、2、3和1、0、3 ; 第二個測試用範例testConditionDecisionCoverageFalse三個測試用參數都為0。在測試方法testMethod中,有個判定(a== 1 && b == 2 || c == 3)包含了三個條件(a == 1、b == 2、c==3),判定的結果顯而易見有(true、 false),已經都覆蓋到了。另外,設計的測試範例,也使得上述三個條件真和假的結果都取到了。因此,這個測試範例滿足了條件判定覆蓋。條件覆蓋不是將判定中的每個條件表達式的結果進行排列組合,而是只要每個條件表達式的結果true和false測試到就可以了。因此,可以這樣推論 : 完全的條件覆蓋並不能保證完全的判定覆蓋。 -條件組合覆蓋(Multiple Condition Coverage)條件組合覆蓋是指判定中所有條件的各種組合情況都出現至少一次。還是以(a == 1 && b == 2 || c == 3)這個判定為例,我們在介紹條件判定覆蓋時,忽略了a==1、b ==2、c == 3等諸多情況。針對被測的方法testMethod,滿足條件組合覆蓋的一個測試範例如下:
@ParameterizedTest
@DisplayName("Mult ple Condition Coverage sample test result true")
@CsvSource({ "1,2,3", "1,2,0", "1,0,3", "0,2,3", "0,0,3" })
void testMultipleConditionCoverageSampleTrue(int a, int b, int c) {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertTrue(coverageSampleMethods.testMethod(a, b, c));
}
@ParameterizedTest
@DisplayName( "Multiple Condit on Coverage sample test result false " )
@CsvSource({
"1,0,0",
"0,0,0",
"0,2,0"
})
void testMultipleConditionCoverageSampleFalse(int a , int b , int c) {
CoverageSampleMethods coverageSampleMethods
= new CoverageSampleMethods();
Assertions.assertFalse(coverageSampleMethods. testMethod(a , b , c));
}
這組測試範例同時滿足了( a==1, b == 2, c ==3 )為( true,true, true )、( true,true, false )、( true, false, true )、( true, false, false )、( false, true, true )、( false,true, false )、( false, false, true )、( false, false , false )這些情況。對於一個包含了n個條件的判定至少需要兩個測試範例才可以。雖然這種覆蓋夠嚴謹,但無疑給撰寫測試範例的工程師增加了非常多的工作量。 -路徑覆蓋( Path Coverage)路徑覆蓋要求能夠測試到程式中所有可能的路徑,testMethod 方法中,可能的路徑有① a=1 ,b=2②a=1,b!=2,c=3③a=1,b!=2,c!=3④a!=1,c=3⑤a!=1,c=3這五種。當存在 || 時如果第一個條件已經為true,則不再計算後邊表達式的值。而當存在 && 時,如果第一個條件已經為false,則同樣不再計算後邊表達式的值。滿足路徑覆蓋的測試範例如下:
@ParameterizedTest
@DisplayName("Path coverage sample test result true ")
@CsvSource({ "1,2,0", "1,0,3", "0,0,3" })
void testPathCoverageSampleTrue(int a, int b, int c) {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertTrue(coverageSampleMethods.testMethod(a, b, c));
}
@ParameterizedTest
@DisplayName("Path coverage sample test result false ")
@CsvSource({ "1,0,0", "0,0,0" })
void testPathCoverageSampleFalse(int a, int b, int c) {
CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
Assertions.assertFalse(coverageSampleMethods.testMethod(a, b, c));
}
總結
1. 覆蓋率數據只能代表你測試過那些程式,不能代表你是否測試好這些程式。
2. 不要過於相信覆蓋率數據。
3. 不要只拿行覆蓋率來考核測試人員。
4. 路徑覆蓋率>判定覆蓋>行覆蓋。
5. 測試人員不能盲目的追求程式覆蓋率,而應該想辦法設計更多更好的案例。