Android单元测试——辅助工具介绍

目录


一.Code Coverage Tool : jacoco、IntelliJ IDEA

二.静态代码检测工具:FindBugs

三.Annotation、@Rule介绍


最近在学习单元测试的相关知识,在这里我将分享一下我在学习过程中,使用到的一些辅助工具或框架。我也是一个初学小白,不足之处,还望大家予以指正。

文中使用的IDE是Android Studio,下面涉及到的插件、工具或导包,都是以Android Studio为例。

鸣谢:1.本文采用的代码来自Robolectric3.0的介绍和实战,该文附带的两篇博客也在单元测试方面给了我很大帮助,强烈推荐。
2.一些点子来自Android单元测试在蘑菇街支付金融部门的实践,该作者小创 有很多关于单元测试的知识值得学习。

一.Code Coverage Tool (代码覆盖率工具):

众所周知,在编写单元测试案例的过程中,有一个重要的指标就是代码覆盖率。编写单元测试并不是为了追求100%覆盖率,但覆盖率在单元测试中仍占据着不可或缺的地位。

经典的Java Coverage Tool : Emma、Eclemma(Eclipse推荐使用)、Cobertura,感兴趣的学者可以自行查阅资料。

接下来,我们来看Android Studio支持的Coverage Tool : jacoco、IntelliJ IDEA。

在Android Stuido新建过工程的同学,应该有注意到,该工程默认会新建androidTest及test的测试包。在Android Stuido中,在androidTest编写的单元测试,默认使用jacoco插件生成包含代码覆盖率的测试报告;而test包下的单元测试代码,则直接使用Android Studio已有工具IntelliJ IDEA生成覆盖率,也可以通过自定义gradle task使用jacoco插件生成与androidTest相同格式的测试报告。


androidTest 与 test:

区别:androidTest是存放一些与View(UI界面层)相关的单元测试案例的测试代码集,需要在真机或虚拟机上运行。一般使用的框架有:Instrumentation、Espresso,也可以编写自动化测试案例(框架:Uiautomator2.0);
而test包则一般只存放与Model(数据层)相关的单元测试案例,但Android几乎无法实现MV完全解耦,所以目前在test包下可能也会涉及到View的测试。直接在JVM虚拟机上运行即可,速度快。框架:Robolectric+Mockito+其他。

运行方式:Android Studio 2.0版本开始,已经能够智能检测当前的测试是androidTest还是test了,低版本的可以在Build Variants中设置Test Artifact或者Add New Configuration 时选择Unit Tests(对应test),Android Instrumentation Tests(对应androidTest)。


jacoco:

  • 先讲androidTest的使用方法

在Module gradle文件中添加以下代码:

apply plugin : 'com.android.application'
apply plugin: 'jacoco'
buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile ('proguard-android.txt' ), 'proguard-rules.pro'
    }
    debug {
        testCoverageEnabled true
    }
}

添加完代码之后,打开右侧的Gradle面板运行connectedAndroidTest任务,
或在Terminal控制台输入 gradlew connectedAndroidTest,回车

Gradle面板方式运行
Terminal控制台方式运行

在\app\build\reports\androidTests\connected\index.html:


androidTest测试报告

在\app\build\reports\coverage\debug \index.html:


androidTest覆盖率报告

androidTest包下未添加测试案例,所以Test Summary测试条数为0,覆盖率也都为0。可自行添加案例测试

  • 对于test包下,如果使用jacoco生成覆盖率将会麻烦一点点

需要在gradle中自定义task,代码如下:

task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {    group = "Reporting"    description = "Generate Jacoco coverage reports"    // exclude auto-generated classes and tests    def fileFilter = ['**/R.class', '**/R$*.class',                      '**/BuildConfig.*', '**/Manifest*.*',                      'android/**/*.*']    def debugTree = fileTree(dir:            "${project.buildDir}/intermediates/classes/debug",            excludes: fileFilter)    def mainSrc = "${project.projectDir}/src/main/java"    sourceDirectories = files([mainSrc])    classDirectories = files([debugTree])    additionalSourceDirs = files([            "${buildDir}/generated/source/buildConfig/debug",            "${buildDir}/generated/source/r/debug"    ])    executionData = fileTree(dir: project.projectDir, includes:            ['**/*.exec', '**/*.ec'])    reports {        xml.enabled = true        xml.destination = "${buildDir}/jacocoTestReport.xml"        csv.enabled = false        html.enabled = true        html.destination = "${buildDir}/reports/jacoco"    }}

添加task之后在右侧gradle面板中会发现,多了jacocoTestReport任务:


jacocoTask

在\app\build\reports\tests\debug\index.html:


test报告

在\app\build\reports\jacoco\index.html:


覆盖率报告

那么问题来了:如果同时编写了androidTest与test单元测试代码,能否使用jacoco生成一份综合的代码覆盖率测试报告呢?此处应有思考。

IntelliJ IDEA:
目前,只有test才支持此方式,操作步骤请看下图:

控制台运行方式

Tracing模式会增加消耗,但测量会更精确,但一般使用Sampling就足够。

生成报告:


test控制台运行结果

jacoco 与 IntelliJ IDEA:

两者各有优缺点,最明显的不同是,jacoco生成的是html报告,如果使用Jenkins自动构建测试,应该使用jacoco;而 IntelliJ IDEA,其实也是采用jacoco检测覆盖率的,但其在面板中显示,较为直观,推荐在编写调试案例时使用。分不同情况使用不同的覆盖率工具,两者可以相互取长补短。


二.静态代码检测工具:FindBugs

what:静态代码分析是指无需运行被测代码,仅通过分析或检查源程序的语法、结构、过程、接口等来检查程序的正确性,找出代码隐藏的错误和缺陷,如参数不匹配,有歧义的嵌套语句,错误的递归,非法计算,可能出现的空指针引用等等。

安装:AndroidStudio->Settigns->Plugins->Browse repositories->search "findBUgs-IDEA" 安装重启AndroidStudio即可。

findBugs安装

安装完之后,工程右键选择>FindBugs>Analyze Module Files,即可开始检测代码。


findBugs代码检测

若部分错误不想扫描,可右键工程->Open Module Settings里设置:


findBugs筛选

扫描结果分析:
该工程扫描出两种类型的错误Bad practice(坏的实践:不建议的写法,建议修改为约定俗成的写法)和Dodgy Code(危险代码: 具有潜在危险的代码,可能运行期产生错误),并给出了详细的说明。此工程的代码量较少,且编码风格也较好,所以查出来的bug较少,FindBugs关于FindBugs更多信息,可查看一些你不知道的事,findbugs的配置跟使用

需要注意的是,虽然FindBugs能够对代码进行静态检测,但并不是FindBugs查找出来的bug就一定存在问题,也并不是所有潜在的bug,FidBugs都能查找出来,还需要开发者结合具体代码进行判断。

为啥不用静态代码检查工具呢? 静态代码检查从入门到放弃。

三.Annotation、@Rule介绍

Annotation 与 JUnit Rule 的详细使用,需要自行学习,这里只介绍Android单元测试在蘑菇街支付金融部门的实践中提到的结合两者来编写测试方法的注释,来规避测试方法名冗长、难以定义、格式不美观的问题。当测试失败时,会在控制台上添加自定义的错误信息。这的确是一个不错的方法。

1).新建Annotation类,需要注意的是,单元测试以JUnit形式运行,需要添加@Retention,否则运行时无法检测到该类

@Retention (RetentionPolicy. RUNTIME)
public @interface Purpose {
    String desc () default "" ;
}

2).新建实现MethodRule接口的自定义Rule类,在apply方法中处理案例执行前、执行成功或失败所做的操作:

public class ErrorRule implements MethodRule {
    @Override
    public Statement apply( final Statement base, final FrameworkMethod method , Object target) {
        return new Statement() {
            @Override
            public void evaluate () throws Throwable {
                /***starting(method);***/
                ShadowLog. stream = System. out ;//此处是使用Robolectric框架的方法,将该测试案例的log输出到控制台,方便查看log
                try {
                    base.evaluate() ;
                    /***succeeded(method);***/
                } catch (Throwable t) {
                    /***failed(t, method);***/
                    try {
                        System. err .print("@" + method.getName() + "--->" + method .getAnnotation(Purpose . class).desc()) ;
                    } catch (NullPointerException e) {

                    } finally {

                        throw t;
                    }

                } finally {
                    /***finished(method);***/
                }
            }
        };
    }
}

3).在测试类中调用Purpose、ErrorRule

@RunWith (RobolectricGradleTestRunner. class)
@Config (constants = BuildConfig. class)
public class MainActivityTest {

    @Rule
    public ErrorRule rule = new ErrorRule();

    @Test
    @Purpose( desc = "when onCreate, the menu shoule be 'First menu item' and 'Second menu item'. ")
    public void onCreateShouldInflateTheMenu () {
        Activity activity = Robolectric. setupActivity(MainActivity. class) ;

        final Menu menu = shadowOf(activity).getOptionsMenu() ;
        assertEquals(menu.findItem(R.id. item1).getTitle() , "first menu item") ;
        assertEquals(menu.findItem(R.id. item2).getTitle() , "Second menu item") ;

    }
}

4).运行结果:


运行结果

阅读原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容