android+jenkins+gradle+lint+checkstyle+findbugs+郵件附件報(bào)告

打造最實(shí)用的Android持續(xù)集成(Continuous Integration以下簡(jiǎn)稱CI)系統(tǒng)

什么是CI

縱覽全局(打破職責(zé)界限)

軟件開(kāi)發(fā)仪媒、運(yùn)維和質(zhì)量保證三個(gè)部門之間的溝通辐马、協(xié)作和集成所采用的流程员辩、方法和體系的一個(gè)集合挥转。
打破目前的rd->qa->op流水線的流程,而是將三者緊密的結(jié)合在一起潜沦。從實(shí)踐的結(jié)果來(lái)看干花,rd每次提交代碼都會(huì)觸發(fā)一系列的自動(dòng)化步驟,包括編譯揍鸟,單元測(cè)試兄裂,代碼覆蓋率,功能測(cè)試阳藻,部署測(cè)試懦窘,性能/容量測(cè)試(注:后兩者受限與時(shí)間要求,實(shí)際實(shí)施不會(huì)每次提交代碼都觸發(fā))稚配。Rd,qa港华,op都在過(guò)程中做質(zhì)量保障道川。

代碼樹(shù)被管理起來(lái)——主干開(kāi)發(fā)

主干開(kāi)發(fā)的好處是每個(gè)rd都知曉整體的變更,所有的feature作為一個(gè)整體發(fā)布立宜,對(duì)OP的現(xiàn)實(shí)意義就是上線變得更有規(guī)律冒萄,非計(jì)劃的、臨時(shí)的上線最后消失橙数。
代碼和周邊(配置尊流,數(shù)據(jù),構(gòu)建腳本灯帮,單元測(cè)試崖技,測(cè)試用例)統(tǒng)一作為產(chǎn)品被管理起來(lái)——一鍵式產(chǎn)構(gòu)建,測(cè)試钟哥,部署迎献,完成產(chǎn)品的最終發(fā)布。
大家都在一個(gè)平臺(tái)上工作腻贰,所有的任務(wù)都在這個(gè)平臺(tái)下吁恍,各角色間對(duì)互相的工作有更深入的了解,并且,工作狀態(tài)也可以共享冀瓦。
少就是多伴奥,簡(jiǎn)潔就是美(用簡(jiǎn)單的方法解決問(wèn)題)
持續(xù)集成的解決方案是簡(jiǎn)潔的。產(chǎn)品由SVN去管理翼闽,構(gòu)建過(guò)程由CI server負(fù)責(zé)拾徙,而構(gòu)建過(guò)程包含了編譯,測(cè)試肄程,發(fā)布锣吼,部署過(guò)程

容量測(cè)量(Capacity management)

容量的變化體現(xiàn)在用戶行為(流量)系統(tǒng)變更(軟件性能)和資源(服務(wù)器數(shù)量,冗余度計(jì)劃)等幾個(gè)因素的變化上蓝厌,將容量和這些變化掛鉤玄叠,在每一個(gè)因素變化下重新得到系統(tǒng)的容量,從而在變更中控制容量不足造成的風(fēng)險(xiǎn)拓提。有一個(gè)要點(diǎn)读恃,我們需要的是系統(tǒng)的容量而不是單個(gè)模塊的性能。

質(zhì)量反饋(Quality feedback)

變更會(huì)導(dǎo)致質(zhì)量變化代态,而質(zhì)量變化體現(xiàn)在各種指標(biāo)上寺惫,而測(cè)量這些指標(biāo)(包括應(yīng)用指標(biāo):平響,處理效率等和系統(tǒng)指標(biāo):負(fù)載蹦疑,網(wǎng)絡(luò)流量)西雀,發(fā)現(xiàn)指標(biāo)之間的規(guī)律,將指標(biāo)share給整個(gè)團(tuán)隊(duì)歉摧,從而有效的達(dá)成質(zhì)量的反饋艇肴,控制變更(包括內(nèi)部變更和外部條件的變化)造成的質(zhì)量下降的風(fēng)險(xiǎn)。

Jenkins能做什么

在實(shí)施持續(xù)集成的過(guò)程中叁温,并行實(shí)施的三個(gè)項(xiàng)目:
持續(xù)部署/一鍵式部署(continuous deployment/one step deploy)再悼,
容量測(cè)試/管理(Capacity Test/Management)
質(zhì)量反饋(Quality feedback)
Jenkins將完成上面3個(gè)并行的項(xiàng)目工作

安裝與配置Jenkins

gradle代碼

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    signingConfigs {
        mySign {
            keyAlias 'xxx'
            keyPassword 'xxx'
            storeFile file('hqyx.keystore')
            storePassword 'xxx'
            v2SigningEnabled false
        }
    }
    compileSdkVersion 26

    defaultConfig {
        applicationId "com.hqyxjy.word"
        minSdkVersion 19
        targetSdkVersion 21
        versionCode 7
        versionName "1.1.0"
        multiDexEnabled = true

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        resValue('string', 'git_last_commit', 'git log --pretty=format:"%H:%ad:%an:%s" --date=short -1'.execute().text)
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.mySign
        }
        debug {
            signingConfig signingConfigs.mySign
        }
    }

    productFlavors {
        flavorDimensions 'channel', 'host', 'area'

        // 渠道
        hqyx {
            dimension 'channel'
            manifestPlaceholders.put('PACKAGE_CHANNEL_VALUE', 'hqyx')
        }

        develop {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
        }

        // host的值
        ttest {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.test'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.test'])
        }

        sim {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.sim'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.sim'])
        }
        beta {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.beta'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.beta'])
        }
        online {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.online'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.online'])
        }
        develop {
            dimension 'host'
            buildConfigField('String', 'HOST', project.properties['environment.host.develop'])
            buildConfigField('String', 'H5_HOST', project.properties['environment.h5_host.develop'])
        }

        // area
        outside {
            dimension 'area'
            buildConfigField('boolean', 'IS_INSIDE', 'false')
        }
        inside {
            dimension 'area'
            buildConfigField('boolean', 'IS_INSIDE', 'true')
        }
    }
    // 控制打包時(shí)輸出的文件名
    android.applicationVariants.all { variant ->
        variant.outputs.all {
            if (!variant.buildType.debuggable) {
                // 計(jì)算出新的文件名格式:NewName_v1.3.0_02051437_develop.apk
                def fileName = "NewName_${defaultConfig.versionName}_${new Date().format("MMddHHmm")}_${variant.flavorName}.apk"
                fileName = fileName.replace("_z_", "_")
                outputFileName = fileName
            }
        }
    }

    // 配置多渠道時(shí),去除沒(méi)用的Variants
    android.variantFilter { variant ->
        if (variant.buildType.name.contains('debug')) {
            variant.getFlavors().each() { flavor ->
                if (flavor.name.contains('z_')) {
                    variant.setIgnore(true)
                }
            }
        } else {
            variant.getFlavors().each() { flavor ->
                if (flavor.name.contains('develop')) {
                    variant.setIgnore(true)
                }
            }
        }
    }
    lintOptions {
        //發(fā)生錯(cuò)誤時(shí)停止構(gòu)建
        abortOnError true
        //警告都提升為錯(cuò)誤
        warningsAsErrors true
        //build release 版本 時(shí) 開(kāi)啟lint 檢測(cè)
        checkReleaseBuilds true
        //規(guī)則位置
        lintConfig file("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/lint.xml")
        //不生成xml報(bào)告
        xmlReport false
        //忽略
        disable 'OldTargetApi'
        //設(shè)置報(bào)告位置
        htmlOutput file("${project.rootDir}/ci/report/app/lint_report.html")
    }
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:support-v4:26.1.0'
    implementation 'com.google.android.gms:play-services-plus:11.6.2'
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testImplementation files('powermocklibs/byte-buddy-1.2.1.jar')
    testImplementation files('powermocklibs/cglib-nodep-2.2.2.jar')
    testImplementation files('powermocklibs/hamcrest-core-1.3.jar')
    testImplementation files('powermocklibs/javassist-3.21.0-GA.jar')
    testImplementation files('powermocklibs/mockito-core-2.0.42-beta.jar')
    testImplementation files('powermocklibs/objenesis-2.4.jar')
    testImplementation files('powermocklibs/powermock-api-mockito-common-1.6.6.jar')
    testImplementation files('powermocklibs/powermock-mockito2-1.6.6-full.jar')
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'

    implementation project(':AndroidCoreLibrary:core')

    implementation project(path: ':svprogresshud')
    testImplementation 'junit:junit:4.12'
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:design:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:multidex:1.0.1'
    implementation 'com.jakewharton:butterknife:8.4.0'
    implementation 'com.android.support:gridlayout-v7:26.1.0'
    implementation 'cn.yipianfengye.android:zxing-library:2.1'
    testCompile 'junit:junit:4.12'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    debugImplementation 'com.amitshekhar.android:debug-db:1.0.1'
    implementation 'org.greenrobot:eventbus:3.0.0'
    implementation 'com.github.traex.rippleeffect:library:1.3'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

    dependencies {
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
    }

    //友盟統(tǒng)計(jì)
    implementation 'com.umeng.analytics:analytics:latest.integration'
}
repositories {
    mavenCentral()
}
/*------------------------------------------------------------------------------------------------*/
/*                                         CI TASK
/*------------------------------------------------------------------------------------------------*/

apply plugin: 'pmd'
task pmd(type: Pmd) {
    ruleSetFiles = files("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_library_ruleset.xml",
            "${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_custom_ruleset.xml",
            "${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/pmd_single_ignore_ruleset_naming_VariableNamingConventions.xml")
    ignoreFailures = false
    ruleSets = []

    source 'src'
    include '**/*.java'
    exclude '**/gen/**'

    reports {
        xml.enabled = false
        html.enabled = true
        html {
            destination "${project.rootDir}/ci/report/app/pmd_report.html"
        }
    }
}

apply plugin: 'findbugs'
task findbugs(type: FindBugs) {
    ignoreFailures = false
    effort = "default"
    reportLevel = "medium"
    //過(guò)濾器
    excludeFilter = new File("${project.rootDir}/../AndroidCoreLibrary/ci/ruleset/findbug_filter.xml")
    //這里填寫(xiě)項(xiàng)目classes目錄
    classes = files("${project.buildDir}/intermediates/classes")
    source = fileTree('src/main/java')
    classpath = files()
    reports {
        //只能開(kāi)啟一個(gè)
        xml.enabled = false
        html.enabled = true
        html {
            destination "${project.rootDir}/ci/report/app/find_bugs_report.html"
        }
    }
}

apply plugin: 'checkstyle'

checkstyle {
    toolVersion '7.1.2'
    showViolations false
}

task checkstyle(type: Checkstyle) {
    ignoreFailures = true
    def config = file("${project.rootDir}/ci/ruleset/checkstyle.xml")
    if (config.exists()) {
        ignoreFailures = false
        configFile = config

        classpath = files()
        source = android.sourceSets.main.java.srcDirs
        include '**/*.java'
        include '**/*.xml'

        reports {
            xml.enabled = false
            html.enabled = true
            html {
                destination "${project.rootDir}/ci/report/app/check_style_report.html"
            }
        }
    }
}

代碼分析

運(yùn)行效果

自定義郵件報(bào)告格式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膝但,一起剝皮案震驚了整個(gè)濱河市冲九,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跟束,老刑警劉巖莺奸,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冀宴,居然都是意外死亡憾筏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門花鹅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)氧腰,“玉大人,你說(shuō)我怎么就攤上這事」潘” “怎么了箩帚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)黄痪。 經(jīng)常有香客問(wèn)我紧帕,道長(zhǎng),這世上最難降的妖魔是什么桅打? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任是嗜,我火速辦了婚禮,結(jié)果婚禮上挺尾,老公的妹妹穿的比我還像新娘鹅搪。我一直安慰自己,他們只是感情好遭铺,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布丽柿。 她就那樣靜靜地躺著,像睡著了一般魂挂。 火紅的嫁衣襯著肌膚如雪甫题。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天涂召,我揣著相機(jī)與錄音坠非,去河邊找鬼。 笑死果正,一個(gè)胖子當(dāng)著我的面吹牛炎码,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舱卡,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼队萤!你這毒婦竟也來(lái)了轮锥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤要尔,失蹤者是張志新(化名)和其女友劉穎舍杜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赵辕,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡既绩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了还惠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饲握。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出救欧,到底是詐尸還是另有隱情衰粹,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布笆怠,位于F島的核電站铝耻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蹬刷。R本人自食惡果不足惜瓢捉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望办成。 院中可真熱鬧泡态,春花似錦、人聲如沸诈火。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冷守。三九已至刀崖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拍摇,已是汗流浹背亮钦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留充活,地道東北人蜂莉。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像混卵,于是被迫代替她去往敵國(guó)和親映穗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • <<互聯(lián)網(wǎng)敏捷DevOps和自動(dòng)化之5.持續(xù)集成>>持續(xù)集成的價(jià)值是什么幕随?對(duì)于開(kāi)發(fā)和測(cè)試人員又意味著什么呢蚁滋?1.1...
    燕京博士閱讀 2,792評(píng)論 0 5
  • 質(zhì)量綜述 隨著互聯(lián)網(wǎng)快速發(fā)展,早期的傳統(tǒng)軟件公司強(qiáng)調(diào)工程的嚴(yán)謹(jǐn)性赘淮,CMMI辕录,ISO9000格局已經(jīng)發(fā)生變化,逐漸退...
    老余2017閱讀 3,930評(píng)論 6 31
  • 在前一篇文章持續(xù)集成入門篇中我大概介紹了下持續(xù)集成的概念及工具(抱歉梢卸,在前一篇文章中我查的資料不夠與時(shí)俱進(jìn)走诞,工具介...
    craneyuan閱讀 1,772評(píng)論 0 7
  • 《美麗新世界》被認(rèn)為和《1984》、《動(dòng)物莊園》齊名的反烏托邦小說(shuō)蛤高。描繪的是在未來(lái)的一個(gè)大同世界蚣旱,消滅了衰老碑幅、疾病...
    人在旅途_Amber閱讀 720評(píng)論 0 1
  • 看得太真切, 會(huì)對(duì)世界感到沮喪姻锁。 即便是真的枕赵, 又何必去捅破? 情愿是假的位隶, 依然會(huì)對(duì)世界充滿向往拷窜。 可有一處?kù)o謐...
    Electrician閱讀 217評(píng)論 0 2