公司最近很重視代碼質量問題,自然的對單元測試要求也是越來越高,對新代碼的Merge Request設置了Line Coverage >= 80%,Branch Coverage >= 80%的門檻。由於事出突然,身邊的小伙伴們對此也是頻頻叫苦......毒害最深的要數Branch Coverage要求了,這也讓我們深刻地體會到了UT的不簡單。
言歸正傳,公司統一采用Jacoco來統計各項的覆蓋率,包括類(Classes)、行(Lines)、方法(Methods)、指令(Instructions),分支(Branches)、圈復雜度(Cyclomatic Complexity)等維度。
Jacoco的pom.xml文件配置也很簡單,網上一搜一大把,這邊隨意貼一個:
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.2</version> <configuration> <destFile>target/coverage-reports/jacoco-unit.exec</destFile> <dataFile>target/coverage-reports/jacoco-unit.exec</dataFile> </configuration> <executions> <execution> <id>jacoco-initialize</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>jacoco-site</id> <phase>package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
與Maven集成后,可以使用mvn install指令跑UT結果,會在report文件下生成一個site文件夾,如圖:
點開其中的jacoco文件夾,打開index.html文件即可查看覆蓋率情況。
當然這都不是本文的重點,有需要了解更加細節的內容可自行查閱。今兒主要是想介紹下Branch Coverage的麻煩之處,因為在提Merge Request的時候經常碰到分支覆蓋率不達標的情況,於是本地跑了UT結果之后,查看了具體情況,如圖:
Jacoco有三種棱形圖案來表示Branch Coverage:
1、紅色棱形:沒覆蓋
2、黃色棱形:覆蓋了部分
3、綠色棱形:全覆蓋
如圖中所示,黃色棱形經常伴隨着1 of n branches missed的提示,就是告訴你還有幾個邏輯沒覆蓋到。舉個例子:
if (A && B && C) { // xxx }
上述代碼如果想要分支覆蓋率達到100%,就需要覆蓋以下情況:
1、A滿足,B滿足,C滿足
2、A不滿足
3、A滿足,B不滿足
4、A滿足,B滿足,C不滿足
對於或條件的例子:
if (A || B || C) { //..... }
1、A不滿足,B不滿足,C不滿足
2、A滿足
3、A不滿足,B滿足
4、A不滿足,B不滿足,C滿足
舉的例子都是if語句的,對於switch也是類似的情況,如果switch的條件中有多個情況,也都需要統統走一遍。總而言之,想要把分支覆蓋率達到100%,就必須把所有的邏輯場景都考慮到,所以寫UT並沒有想象中的那么簡單,業內有理論說業務開發時間與UT時間應該是1:1的關系,一點不假!
從我自身實際寫UT的感受中,簡單說下UT帶來的好處:
1、如果是認真寫UT,且覆蓋率全面的話,確實能發現業務代碼中的bug
2、如果是為他人代碼寫UT,有助於熟悉之前不了解的代碼邏輯
3、鍛煉思維邏輯,考慮會更加周到
我在寫UT的時候采用了Mockito與PowerMockito相結合的方式,對於一些static、final類,或者一些static方法,可以使用PowerMockito去mock,十分方便。
比如我們當前正在寫A類print方法的UT,其中用到了如下工具類的getImageUrl方法,我們需對其進行mock:
// 工具類 public final class ImageUtil { private ImageUtil(){} public static String getImageUrl(String image) { // .... } } // A public class A { // .... // print public String print() { // .... ImageUtil.getImageUrl(xxx); // .... } } // 測試 @RunWith(PowerMockRunner.class) @PrepareForTest({ImageUtil.class}) public class ATest { @InjectMocks private A a; // 被測試類 @Before public void setup(){ PowerMockito.mockStatic(A.class); // 准備 } @Test public void testPrint() { Mockito.when(ImageUtil.getImageUrl(Mockito.anyString())).thenReturn("Anything you want..."); } }
當然使用PowerMockito還能相對簡單的對私有方法進行測試,原理是基於反射。但即使不用PowerMockito也能自行通過reflect機制對私有方法進行測試。
就像我標題寫的“兩三事”一樣,本文只是很隨意的記錄一些實際寫UT過程中碰到的一些問題或者有趣的事情,如果想要看到很強的理論知識,那恐怕會讓你失望了。
前幾天又用到了一個新的mock手段,“Mockito.RETURNS_DEEP_STUBS”。
我們在寫UT的時候,經常會出現如下情況:
// 假設有一個企業對象 Company company = .....; // 獲取該企業內的某一個事業部下的某一個部門里某團隊的某一個人的姓名 company.getBU().getDepartment().getTeam().getSomeOne().getName()
對於一長串的調用的mock,“Mockito.RETURNS_DEEP_STUBS”能很方便的搞定。
// 深度打樁 Company company = Mockito.mock(Company.class, Mockito.RETURNS_DEEP_STUBS); // mock Mockito.when(company.getBU().getDepartment().getTeam().getSomeOne().getName()).thenReturn("小明");
而且不需要擔心會有NPE問題的拋出,十分方便!