1. 程式人生 > >單元測試匯總

單元測試匯總

lee 一段 必備 dac jacoco try list 遇到 反選

轉自:https://blog.csdn.net/u012933335/rss/list

[原]Android單測調研篇

1. 為什麽做單測

單測的好處

減少bug
快速定位bug
提高代碼質量
減少調試時間
放心重構

不得不寫單測的原因

在成為大牛的路上,單測是必備技能
單測可以給你信心
保住面子

難處

難於堅持,在快速叠代開發過程中,可供寫單測的時間過少

擴展

TDD(Test Drive Develop):測試驅動開發,是一種非常高效的開發方式

2. 測試框架

2.1 概述

 junit4.12 (單測框架)
 mockito1.9.5(mock工具)
 robolectric3.1.2 (模擬Android虛擬機)

2.2 junit4.12

Junit測試是程序員測試,即所謂白盒測試,因為程序員知道被測試的軟件如何(How)完成功能和完成什麽樣(What)的功能。Junit是一套框架,繼承TestCase類,就可以用Junit進行自動測試了。

示例代碼:

@Before: 執行單測之前的初始化操作。

@After:單測完成後收尾工作。

      @Before
      public void setUp() throws Exception {
      }
      @After
      public void tearDown() throws Exception {
      }

assume: 用於判斷測試用例的入參是否有業務含義的工具,如果入參不符合預期時會拋出

assumptionViolatedException、assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoException

@RunWith(Theories.class)
public class AssumeTest
{
    @DataPoints
    public static String[] names = {"LiLei", "HanMeiMei"};

    @DataPoints
    public static int[] ages = {10, -2, 12};

    @Theory
    public void printAge(String name, int age)
    {
        Assume.assumeTrue(age > 0);
        System.out.println(String.format("%s‘s Name is %s.", name, age));
    }
}

assert :用於常用的測試結果驗證

AssertTrue、AssertFalse:結果的true、false。

AssertThat:使用Matcher做自定義的校驗。

AssertEquals、AssertNotEquals:判斷兩個對象是否相等。

AssertNull、AssertNotNull:判斷對象是否為空。

AssertSame:判斷兩個對象是否為同一個,不同於equals這裏是使用“==”判斷。

AssertArrayEquals:判斷兩個數組是否相等。

    @Test
    public void sum() throws Exception {
        assertEquals(mCalculator.sum(3, 4), 7);
    }

verify : 主要用於驗證方法是否執行

    @Test
    public void testVerify() {
        List mockedList = mock(List.class);
        mockedList.add("one");
        mockedList.clear();
        mockedList.add("3");
        // verification
        verify(mockedList).add("one");
        verify(mockedList).clear();
    }

其他高級用法:

@Test(timeout = 1000): 限時操作,若超過制定時間,強制停止

@Test(expected = ArithmeticException.class): 預測拋出指定異常

2.3 mockito1.9.5

創建mock對象不能對final,Anonymous ,primitive類進行mock。
用法:

when… thenRetrun; when… thenThrow

doNothing().doRetrun();

doNothing.doThrow()

anyInt、anyString、anyMap…..(參數匹配器)

    @Test
    public void argumentMatcherTest2(){
        Map

2.4 robolectric3.1.2

  實現一套JVM能運行的Android代碼,從而做到脫離Android環境進行測試

1.測試跳轉

    /**
     * Activity跳轉測試
     */
    @Test
    public void testStartActivity() {
        //按鈕點擊後跳轉到下一個Activity
        forwardBtn.performClick();
        Intent expectedIntent = new Intent(sampleActivity, LoginActivity.class);
        Intent actualIntent = ShadowApplication.getInstance().getNextStartedActivity();
        assertEquals(expectedIntent.getComponent(), actualIntent.getComponent());
    }

2.模擬activity

        sampleActivity = Robolectric.setupActivity(SampleActivity.class);
  1. UI測試:
    /**
     * Toast的測試
     */
    @Test
    public void testToast() {
        //點擊按鈕,出現吐司
        toastBtn.performClick();
        assertEquals(ShadowToast.getTextOfLatestToast(), "we love UT");
    }
    /**
     * Dialog的測試
     */
    @Test
    public void testDialog() {
        //點擊按鈕,出現對話框
        dialogBtn.performClick();
        AlertDialog latestAlertDialog = ShadowAlertDialog.getLatestAlertDialog();
        assertNotNull(latestAlertDialog);
    }
    /**
     * 測試控件狀態
     */
    @Test
    public void testViewState() {
        CheckBox checkBox = (CheckBox) sampleActivity.findViewById(R.id.checkbox);
        Button inverseBtn = (Button) sampleActivity.findViewById(R.id.btn_inverse);
        assertTrue(inverseBtn.isEnabled());
        checkBox.setChecked(true);
        //點擊按鈕,CheckBox反選
        inverseBtn.performClick();
        assertTrue(!checkBox.isChecked());
        inverseBtn.performClick();
        assertTrue(checkBox.isChecked());
    }
    /**
     * 資源文件訪問測試
     */
    @Test
    public void testResources() {
        Application application = RuntimeEnvironment.application;
        String appName = application.getString(R.string.app_name);
        String activityTitle = application.getString(R.string.title_activity_simple);
        assertEquals("LoveUT", appName);
        assertEquals("SimpleActivity", activityTitle);
    }
    /**
     * 測試廣播
     */
    @Test
    public void testBoradcast() {
        ShadowApplication shadowApplication = ShadowApplication.getInstance();
        String action = "com.geniusmart.loveut.login";
        Intent intent = new Intent(action);
        intent.putExtra("EXTRA_USERNAME", "geniusmart");
        //測試是否註冊廣播接收者
        assertTrue(shadowApplication.hasReceiverForIntent(intent));
        //以下測試廣播接受者的處理邏輯是否正確
        MyReceiver myReceiver = new MyReceiver();
        myReceiver.onReceive(RuntimeEnvironment.application, intent);
        SharedPreferences preferences = RuntimeEnvironment.application.getSharedPreferences("account", Context.MODE_PRIVATE);
        assertEquals("geniusmart", preferences.getString("USERNAME", ""));
    }
    /**
     * 測試Fragment
     */
    @Test
    public void testFragment() {
        SampleFragment sampleFragment = new SampleFragment();
        //此api可以主動添加Fragment到Activity中,因此會觸發Fragment的onCreateView()
        SupportFragmentTestUtil.startFragment(sampleFragment);
        assertNotNull(sampleFragment.getView());
    }
  4.登錄場景測試
    @Test
    public void loginSuccess() {
        emailView.setText("[email protected]");
        passwordView.setText("123");
        button.performClick();
        ShadowApplication application = ShadowApplication.getInstance();
        assertThat("Next activity has started", application.getNextStartedActivity(), is(notNullValue()));
    }
    @Test
    public void loginWithEmptyUsernameAndPassword() {
        button.performClick();
        ShadowApplication application = ShadowApplication.getInstance();
        assertThat("Next activity should not started", application.getNextStartedActivity(), is(nullValue()));
        assertThat("Show error for Email field ", emailView.getError(), is(notNullValue()));
        assertThat("Show error for Password field ", passwordView.getError(), is(notNullValue()));
        assertEquals(emailView.getError().toString(), RuntimeEnvironment.application.getString(R.string.error_field_required));
    }
    @Test
    public void loginFailure() {
        emailView.setText("invalid@email");
        passwordView.setText("invalidpassword");
        button.performClick();
        ShadowApplication application = ShadowApplication.getInstance();
        assertThat("Next activity should not started", application.getNextStartedActivity(), is(nullValue()));
        assertThat("Show error for Email field ", emailView.getError(), is(notNullValue()));
        assertThat("Show error for Password field ", passwordView.getError(), is(notNullValue()));
    }

更多場景還需探索。。。

與espresso的對比

Google 官方提供的一個易於測試 Android UI 的開源框架 , 於2013年10月推出它的 released 版本 , 目前最新版本已更新到2.x . 並且在AndroidStudio 2.2 預覽版中已經默認集成該測試庫 。

ViewMatchers - 在當前View層級去匹配指定的View .

ViewActions - 執行Views的某些行為,如點擊事件 .

ViewAssertions - 檢查Views的某些狀態,如是否顯示 .

@RunWith(AndroidJUnit4.class)
public class LoginUITest {
    @Rule
    public ActivityTestRule rule=new ActivityTestRule(LogingActivity.class,true);
    @Test
    public void login(){
        //login
        onView(withId(R.id.userName)).perform(typeText("Jack"),closeSoftKeyboard());
        onView(withId(R.id.password)).perform(typeText("1234"),closeSoftKeyboard());
        onView(withText("登錄")).perform(click());
        //verify
        onView(withId(R.id.content)).check(matches(isDisplayed()));
    }
}

espresso更偏向於自動化測試,集成後執行單元測試需要跑在Android手機上,其有個高級功能,根據你的點擊軌跡,自動生成自動測試代碼。

3. 覆蓋率

 jacoco:Android Studio自帶的生成單元測試覆蓋率報告的工具。

行覆蓋率:度量被測程序的每行代碼是否被執行,判斷標準行中是否至少有一個指令被執行。

類覆蓋率:度量計算class類文件是否被執行。

分支覆蓋率:度量if和switch語句的分支覆蓋情況,計算一個方法裏面的

總分支數,確定執行和不執行的 分支數量。

方法覆蓋率:度量被測程序的方法執行情況,是否執行取決於方法中是否有至少一個指令被執行。

指令覆蓋:計數單元是單個java二進制代碼指令,指令覆蓋率提供了代碼是否被執行的信息,度量完全 獨立源碼格式。

圈復雜度:在(線性)組合中,計算在一個方法裏面所有可能路徑的最小數目,缺失的復雜度同樣表示測試案例沒有完全覆蓋到這個模塊。

 參考自:http://blog.csdn.net/tmq1225/article/details/52221187

集成配置:

apply plugin: ‘jacoco‘
android {

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
        }
        debug{
            testCoverageEnabled true
        }
    }
}
jacoco {
    toolVersion = "0.7.5.201505241946"
}

jacoco覆蓋率報告分為兩種:

   1. 只生成java層代碼覆蓋率報告

   2. 在運行app期間執行的覆蓋率報告
task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") {
    println("=========jacocoTestReport start");
    group = "Reporting"
    description = "Generate Jacoco coverage reports"
    classDirectories = fileTree(
            dir: "${project.buildDir}/intermediates/classes/debug",
            excludes: [‘**/R.class‘,
                       ‘**/R$*.class‘,
                       ‘**/*$ViewInjector*.*‘,
                       ‘**/BuildConfig.*‘,
                       ‘**/Manifest*.*‘]
    )
    println("path==========>>" + "${project.buildDir}/intermediates/classes/debug")
    def coverageSourceDirs = "${project.projectDir}/src/main/java"
    println("coverageSourceDirs==========>>" + coverageSourceDirs)
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    executionData = fileTree(dir: project.projectDir, includes:[‘**/*.exec‘, ‘**/*.ec‘])
    reports {
        xml.enabled = true
        html.enabled = true
    }
}

task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports{
        xml.enabled = true
        html.enabled = true
        csv.enabled = false
    }
    classDirectories = fileTree(
            dir : "$buildDir/intermediates/classes/debug",
            excludes : [
                    ‘**/*Test.class‘,
                    ‘**/R.class‘,
                    ‘**/R$*.class‘,
                    ‘**/BuildConfig.*‘,
                    ‘**/Manifest*.*‘
            ]
    )
    def coverageSourceDirs = [‘src/main/java‘]
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    additionalClassDirs = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
}

結果展示

技術分享圖片

技術分享圖片

總結

單元測試的一些原則

  1. 在最低的功能/參數上驗證程序的正確性
  2. 單元測試過後,機器狀態保持不變。(數據庫,sp等)
  3. 單元測試應該產生可重復、一致的結果。(盡量減少一切隨機)
  4. 獨立性,單元測試的運行/通過/失敗不依賴於別的測試,可以人為構造數據,以保持單元測試的獨立性
  5. 單元測試應該覆蓋所有代碼路徑,包括錯誤處理路徑,為了保證單元測試的代碼覆蓋率,單元測試必須測試公開的和私有的函數/方法
  6. 單元測試必須和產品代碼一起保存和維護。
  7. 單元測試要快(一個測試運行時間是幾秒鐘,而不是幾分鐘)
作者:u012933335 發表於 2018/02/24 09:51:48 原文鏈接 https://blog.csdn.net/u012933335/article/details/79358869 閱讀:15
[原]Android單測踩坑篇

在開始寫單測之前,已經調研了很久Android單測的框架以及demo,正好換了一個新的項目組,就從頭開始將單測框架應用到開發過程中,後續也方便可以使用TDD。

調研的框架:junit,mockito, roboletric,espresso,jacoco(覆蓋率報告)

具體場景:網絡請求,todomvp的單測方式,UI測試等

理想永遠是美好的,擼起袖子開始幹的時候,就會發現還有很多崎嶇需要去踏平。

首先,我放棄了espresso(雖然是google官方出的測試框架),主要原因是espresso依賴真機或模擬器。我相信大部分應用應該都有模擬器判斷,在模擬器環境下禁止啟動app,為了寫單測,還得改代碼,很難受;另外多數開發團隊應該都是持續集成,跑espresso,還需要有一臺手機常年插在構建機上,很麻煩。這並不是說espresso毫無用處,espresso對於Android框架支持非常完善,基本上所有UI的測試都可以實現。另外,espresso還有一個非常強大的功能,適合測試人員手工測試:錄制測試過程,自動生成測試代碼。比方說:你進入app後,按照正確測試流程點擊一遍後,保存下來,下一次開發人員有了些許改動,你只需要運行一次之前自動生成的測試代碼,應用便會按照之前的流程進行測試,如出現異常,則測試不通過!非常強大(據團隊的IOS工程師說,ios也有一樣的測試框架,個人覺得測試人員可以考慮一下這個)。

所以最初我采用的Android單測框架就是:junit + mockito + roboletric + jacoco

junit和mockito就不要多講了,都是java單元測試最基本的框架。在說roboletric之前,得先說一下powerMock,mockito無法mock static方法,而powermock則解決了這個問題。所以powermock和mockito配合使用,基本就可以覆蓋絕大部分情況。不過由於powermock和mockito是兩個團隊實現的,經常出現版本不兼容的情況,建議直接使用powermock內部引用的mockito,這樣就不會沖突了。
roboletric簡單來說就是實現了一套JVM能運行Android代碼的框架,從而做到脫離Android環境進行測試。powermock和roboletric在版本上有些不太兼容,roboletric的github的wiki上有官方出的powermock和roboletric的集成方式:

貼上地址:

 https://github.com/robolectric/robolectric/wiki/Using-PowerMock

在使用框架時,註意對應版本。頁面中的第一句話:

NOTE: PowerMock integration is broken in Robolectric 3.1 and 3.2, but fixed in 3.3.

我使用的完整配置如下:

apply plugin: ‘com.android.library‘
apply plugin: ‘android-apt‘
apply plugin: ‘com.jakewharton.butterknife‘
apply plugin: ‘jacoco‘

android {
    buildTypes {
        debug {
            testCoverageEnabled true
        }
    }
}

/**
 * 生成jacoco測試報告
 */
task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") {
    println("=========jacocoTestReport start");
    group = "Reporting"
    description = "Generate Jacoco coverage reports"
    classDirectories = fileTree(
            dir: "${project.buildDir}/intermediates/classes/debug",
            excludes: [‘**/R.class‘,
                       ‘**/R$*.class‘,
                       ‘**/*$ViewInjector*.*‘,
                       ‘**/BuildConfig.*‘,
                       ‘**/Manifest*.*‘]
    )
    println("path==========>>" + "${project.buildDir}/intermediates/classes/debug")

    def coverageSourceDirs = "${project.projectDir}/src/main/java"
    println("coverageSourceDirs==========>>" + coverageSourceDirs)

    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    println("executionData==========>>" + "$buildDir/jacoco/testDebugUnitTest.exec")
    executionData = files("$buildDir/jacoco/testDebugUnitTest.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }

}


jacoco {
    toolVersion = "0.7.1.201405082137"
}

dependencies {
    compile files(‘libs/fastjson-1.1.51.android.jar‘)
    compile ‘com.android.support:appcompat-v7:24.2.1‘
    compile ‘com.cmbchina.ccd.pluto:CMBCore:1.0.0-SNAPSHOT@aar‘
    compile ‘com.jakewharton:butterknife:8.4.0‘
    apt ‘com.jakewharton:butterknife-compiler:8.4.0‘
    annotationProcessor ‘com.jakewharton:butterknife-compiler:8.4.0‘

    testCompile ‘junit:junit:4.12‘
    testCompile ‘org.robolectric:robolectric:3.6.1‘
    testCompile ‘org.powermock:powermock-api-mockito:1.6.6‘
    testCompile ‘org.powermock:powermock-module-junit4:1.6.6‘
    testCompile ‘org.powermock:powermock-module-junit4-rule:1.6.6‘
    testCompile ‘org.powermock:powermock-classloading-xstream:1.6.6‘
}

原本使用powermock的版本是1.7.X,發現使用的過程中各種報錯,還是使用了官方的1.6.6版本,不知道這兩個團隊什麽時候兼容能做的很完善。

附上使用BaseTest:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class , sdk = 21)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*", "sun.security.*", "javax.net.*"})
@PrepareForTest({CPSNetUtils.class, Common.class})
public abstract class CPSBaseTest {

    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Before
    public void setUp() {
        // 將log日誌打印到控制臺
        ShadowLog.stream = System.out;
        initLog();
        //mockito 初始化
        MockitoAnnotations.initMocks(this);
        //mock靜態方法所在類
        PowerMockito.mockStatic(CPSNetUtils.class, Common.class);
        Common.application = getApplication();
        new FoundationBuildConfig().init();
        initNetMock();
        mockCommon();
    }
  }

@Config(constants = BuildConfig.class , sdk = 21):原本想使用23的sdk版本,會有不兼容問題。

@PowerMockIgnore({“org.mockito.“, “org.robolectric.“, “android.“, “org.json.“, “sun.security.“, “javax.net.“}):根據我的理解:powermock類加載器忽略以上類的加載。

@PrepareForTest({CPSNetUtils.class, Common.class}):想mock static的方法,必須加上此註解。

基本配置如上,若上述配置都沒問題了,就可以真正開始寫單測了。目前Android端的框架使用的是google推薦的mvp框架,優缺點,網上有很多文章,就不在贅述。github地址如下:

   https://github.com/googlesamples/android-architecture/tree/todo-mvp/

不可否認的一點,使用mvp框架後單測實現會簡單很多,極大程度減少了對view的測試。貼上一段業務測試代碼:

public class FeedbackPresenterTest extends CPSBaseTest {

    @InjectMocks
    private FeedbackPresenter mPresenter;

    @Mock
    private FeedbackContract.View mView;


    @Before
    public void setUp() {
        super.setUp();
        mPresenter = new FeedbackPresenter();
        mPresenter.attachView(mView);
    }

    @Test
    public void feedbackFailTest() {
        when(mView.getFeedback()).thenReturn("");
        when(mView.getContact()).thenReturn("15012341234");
        mPresenter.uploadFeedback();
        verify(mView).showToastInBottom("反饋信息不能為空!");
    }

    @Test
    @Config(shadows = {ShadowCommon.class})
    public void feedbackSuccessTest() {
        when(mView.getFeedback()).thenReturn("閃退!");
        when(mView.getContact()).thenReturn("15012341234");
        mPresenter.uploadFeedback();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        verify(mView).showToastInBottom("操作成功");
    }
}

細心的讀者會發現在feedbackSuccessTest測試方法中,我開了個線程睡了半秒鐘,這時候就要說到網絡請求的單元測試了。網絡交互是客戶端最頻繁的場景,而網絡的不穩定,會導致客戶端出現很多難以預知的情況,崩潰,閃退都有可能發生。所以對於網絡請求的單測是重中之重。我使用的網絡庫是okhttp,而okhttp有一個很強大的功能:Interceptor。interceptor可以攔截網絡請求,可以處理完後繼續發送,也可以直接直接返回。廢話不多說,上代碼:

public class MockInterceptor implements CMBHttpInterceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response;
        String json = "";


        if (chain.request().url().toString().equals(FoundationHostConst.LOGIN)) {
            // login
            json = "{\"respMsg\": \"用戶名或密碼錯誤[CD1105]\",\"respCode\": \"1001\"}";
        } else if (chain.request().url().toString().equals(FoundationHostConst.REGISTER)) {
            //register
            json = "{\n" +
                    "    \"data\": {\n" +
                    "       \"sessionId\": \"c742f1a3915a445d997735413ca12a78\",\n" +
                    "       \"userId\": \"7ac3960080e94be38c79ac83808b579a\",\n" +
                    "       \"channel\": \"MOB\"\n" +
                    "    },\n" +
                    "    \"respMsg\": \"操作成功\",\n" +
                    "    \"respCode\": \"1000\"\n" +
                    " }";
        } else if (chain.request().url().toString().equals(FoundationHostConst.FEEDBACK)) {
            //feedback
            json = "{\n" +
                    "    \"respMsg\": \"操作成功\",\n" +
                    "    \"respCode\": \"1000\"\n" +
                    " }";
        }

        response = setResponse(chain, json);

        return response;
    }

    /**
     * 設置指定返回報文
     *
     * @param chain
     * @param response
     * @return
     */
    private Response setResponse(Chain chain, String response) {
        return new Response.Builder()
                .code(200)
                .addHeader("Content-Type", "multipart/form-data")
                .body(ResponseBody.create(MediaType.parse("multipart/form-data"), response))
                .message(response)
                .request(chain.request())
                .protocol(Protocol.HTTP_2)
                .build();
    }
}

當然也可以模擬異常返回,404什麽的都可以。另外okhttp使用的是建造者模式,客戶端網絡請求OkHttpClient都是一致的,故可以使用類似代碼直接mock返回:

CMBHttpClient.Builder builder CMBHttpUtils.getDefaultClientBuilder().addInterceptor(new CPSInterceptor())
                .addInterceptor(new CMBLogInterceptor())
                .addInterceptor(new MockInterceptor());
        PowerMockito.when(CPSNetUtils.getCPSDefaultBuilder()).thenReturn(builder);

單測寫完,總得看到點數據報告吧。這時候就需要覆蓋率報告了。Android studio自帶了jacoco 插件生成覆蓋率報告。
jacoco數據含義:

行覆蓋率:度量被測程序的每行代碼是否被執行,判斷標準行中是否至少有一個指令被執行。

類覆蓋率:度量計算class類文件是否被執行。

分支覆蓋率:度量if和switch語句的分支覆蓋情況,計算一個方法裏面的總分支數,確定執行和不執行的分支數量。

方法覆蓋率:度量被測程序的方法執行情況,是否執行取決於方法中是否有至少一個指令被執行。

指令覆蓋:計數單元是單個java二進制代碼指令,指令覆蓋率提供了代碼是否被執行的信息,度量完全 獨立源碼格式。

圈復雜度:在(線性)組合中,計算在一個方法裏面所有可能路徑的最小數目,缺失的復雜度同樣表示測試案例沒有完全覆蓋到這個模塊。

 參考自:http://blog.csdn.net/tmq1225/article/details/52221187

jacoco的覆蓋率報告分為兩種:

  1. 只生成java層代碼覆蓋率報告
  2. 在運行app期間執行的覆蓋率報告

方法也不盡相同。生成java層代碼的任務在前面代碼中已經貼出。

生成運行app期間執行的覆蓋率報告代碼如下:

//task jacocoAndroidTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports{
        xml.enabled = true
        html.enabled = true
        csv.enabled = false
    }
    classDirectories = fileTree(
            dir : "$buildDir/intermediates/classes/debug",
            excludes : [
                    ‘**/*Test.class‘,
                    ‘**/R.class‘,
                    ‘**/R$*.class‘,
                    ‘**/BuildConfig.*‘,
                    ‘**/Manifest*.*‘
            ]
    )
    def coverageSourceDirs = [‘src/main/java‘]
    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    additionalClassDirs = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
}

以上代碼並未測試過,點擊執行測試後,會啟動app,並在會生成.ec文件,通過解析.ec文件, 可以生成覆蓋率報告。因為前面放棄esspreso時就說過放棄真機測試,所以此方法也未使用。
註意事項:testCoverageEnabled 得設置成true,否則無法生成覆蓋率報告。

buildTypes {
    debug {
        testCoverageEnabled true
    }
}

另外,中間遇到一個大坑,被坑了很久。就是jacoco和roboletric也有版本不兼容的問題。。。使用最新的jacoco的版本,發現生成的覆蓋率報告始終為0

在stackoverflow中找了查了很久,最後在一個帖子裏看到,在jacoco 0.7.3以上的版本使用roboletric就始終為0,嘗試了多個版本,發現 0.7.1.201405082137 是OK的。千萬不要隨便升級版本,否則會出現異想不到的問題。。

好了,下面就看一下覆蓋率報告:

技術分享圖片

後續會接入jenkins,等踩完坑,再補一篇文章。

作者:u012933335 發表於 2018/02/07 18:32:37 原文鏈接 https://blog.csdn.net/u012933335/article/details/79283201 閱讀:86

單元測試匯總