Upgrade to Pro — share decks privately, control downloads, hide ads and more …

BABBQ: The other Android getting started guide

BABBQ: The other Android getting started guide

Getting started with a project is fairly easy when it is small enough, but if you want to include continuous integration, code quality analysis, good looking documentation, unit testing on the JVM combined with test coverage and automated testing on multiple devices, you will find it not so easy.

Javier de Pedro López

October 25, 2016
Tweet

More Decks by Javier de Pedro López

Other Decks in Programming

Transcript

  1. Javier de Pedro López Strengths Experience Android Developer at Mobgen

    2 Years professionally 2 Different companies Time spent in 4 Projects Free time Design Patterns Gradle Clean architecture Code quality @droidpl [email protected] [email protected]
  2. `Why the other getting started guide? Beginner Junior Medior …

    Getting started The other getting started
  3. This talk Gradle in Android Continuous Integration Android Testing and

    Unit testing Code coverage Documentation Put it all together
  4. • Google I/O 2013 (Android Studio) • Gradle: default build

    system • Configuration in Groovy • Gradle version 2.4 (out of 2.8) • Android plugin 1.3.0 (exp 1.5.0-beta1) Gradle in Android Context The project Closures / DSL TASKS
  5. Gradle in Android Context The project Closures / DSL TASKS

    /—— (project) —build.gradle —settings.gradle —/app (module) —build.gradle Android Studio Project gradle project ($project) apply from apply plugin android { } org.gradle.api.Project root project ($rootProject) allprojects
  6. Gradle in Android The project Closures / DSL TASKS Context

    myExtension { test true inner { test false } } DSL (Domain Specific Language) class MyExtension { InnerObject obj def test(boolean value){} def inner(Closure closure){ project.configure(obj, closure) } } Closure project.task(‘copy’, type: Copy, Closure) task('copy', type: Copy) { from(file('srcDir')) into(buildDir) } Plugin extension project.extensions.create(‘android”, Ex) android { }
  7. Gradle in Android The project Closures / DSL TASKS Context

    Android DSL android { ... ... } buildTypes { debug { } release { } } Build types productFlavors { pro { dimension 'version' } x86 { dimension ‘abi' } } Flavors flavorDimensions 'abi', 'version' Dimensions Variant == Flavor(s) + BuildType
  8. Gradle in Android The project Closures / DSL TASKS Context

    org.gradle.api.Task Execute task ./gradlew app:myTask Android Studio Define tasks task mytask << { println "Ending" } mytask.doFirst { println "Beginning" } mytask.dependsOn(assembleDebug)
  9. Gradle in Android The project Closures / DSL TASKS Context

    Sample execution (mytask) . . . :app:generateDebugResValues UP-TO-DATE :app:generateDebugResources UP-TO-DATE :app:mergeDebugResources UP-TO-DATE :app:processDebugManifest UP-TO-DATE :app:processDebugResources UP-TO-DATE :app:generateDebugSources UP-TO-DATE :app:processDebugJavaRes UP-TO-DATE :app:compileDebugJavaWithJavac :app:compileDebugNdk UP-TO-DATE :app:compileDebugSources :app:preDexDebug :app:dexDebug :app:validateDebugSigning :app:packageDebug :app:zipalignDebug :app:assembleDebug :app:mytask Beginning Ending
  10. CI Tips CI version code ci Version name Why Android

    versioning problem android { ... defaultConfig { versionCode 1 versionName "1.0-SNAPSHOT" } ... } “Be a hater of hardcoded values, if you can get rid of them" Solution • Use your repository for versioning • Use gradle to be smart
  11. CI Tips CI version code ci Version name Why Count

    the commit Android Version code == commit number (why not?) Code final String COMMAND = "git rev-list HEAD --first-parent —count" public int getCommitRevision(){ def count = 0 def commitNumber = COMMAND.execute().text if(!commitNumber.isEmpty()){ count = commitNumber.toInteger() } return count }
  12. Version name 1.0.134(-SNAPSHOT) CI Tips ci Version Name Why CI

    version code public String getTag(){ def tag = "git describe".execute().text.trim() if(tag.isEmpty()){ tag = "v0" } return tag } //BAMBOO or other CI system def CI_BUILD = System.getenv(“bamboo_buildNumber") def version = "${getTag}.$CI_BUILD" gradle.taskGraph.whenReady { taskGraph -> if(taskGraph.hasTask(assembleDebug)) { version += "-SNAPSHOT" } }
  13. Android testing / Unit testing Why two types Android Testing

    Unit Testing CI tasks Unit testing Pros • Runs on the JVM • Uses JUnit • Is lightning fast • Allows TDD Cons • No real views • Have to mock android.jar Android testing Pros • You can have a coffee • Test views • Activity lifecycle • Real behaviour Cons • SLOW, seriously • Needs a device • Difficult to setup the CI • Automation The android.jar trouble
  14. Why two types Android Testing Unit Testing CI tasks •

    Goes in the src/androidTest folder • Extend from: ApplicationTestCase, ActivityInstrumentationTestCase2, ActivityUnitTestCase, ServiceTestCase, ProviderTestCase • Use libraries: AndroidJUnitRunner: JUnit compatible test runner UIAutomator: System tests Espresso: UI testing Android testing / Unit testing
  15. Why two types Android Testing Unit Testing CI tasks •

    Goes in the src/test folder • Uses jUnit 4 standard @Test. • You can use the default runner @RunWith(BlockJUnit4ClassRunner.class) • Robolectric 3 (gradle support) @RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml") Android testing / Unit testing
  16. Why two types Android Testing Unit Testing CI tasks CI

    Unit test tasks //One variant ./gradlew app:test{flavor}{buildType}UnitTest // All variants ./gradlew app:test CI Android test task //Variant ./gradlew app:connected{flavor}{buildType}AndroidTest //All flavours ./gradlew app:connectedAndroidTest //All checks ./gradlew app:connectedCheck Android testing / Unit testing
  17. Code coverage Coverage on Android tests Coverage on Unit tests

    Simply works • Enable it in the build type • Execute the task • Report located on build/reports/coverage/{flavor}/{buildtype}/ testCoverageEnabled true Report Merge reports CI tasks
  18. Code coverage Coverage on Android tests Coverage on Unit tests

    Not covered (yet) • https://code.google.com/p/android/issues/detail?id=144664 • Manual task Merge reports CI tasks Gradle saves the day
  19. Code coverage Coverage on Android tests Coverage on Unit tests

    Merge reports CI tasks sourceDirectories = files("src/main/java", "src/debug/java") executionData = files("$buildDir/jacoco/test$variantNameUnitTest.exec") reports { xml.enabled = false html.enabled = true } Jacoco report task task (name: “create${variantName}UnitTestCoverageReport”, type: JacocoReport, dependsOn: "test${variantName}UnitTest") { } def classes = "$buildDir/intermediates/classes/$flavor/$buildType" classDirectories = project.fileTree( dir: "${baseLocation}", excludes: ['**/R.class', '**/R$*.class', '**/*$ViewInjector*.*', '**/BuildConfig.*', '**/Manifest*.*', '**/*_*Factory.*'] )
  20. Code coverage Coverage on Android tests Coverage on Unit tests

    Merge reports CI tasks CI Unit test coverage tasks Task name provided: ./gradlew app:create{flavor}{buildType}UnitTestCoverageReport CI Android test coverage tasks ./gradlew app:create{flavor}{buildType}AndroidTestCoverageReport ONLY AVAILABLE FOR DEBUG
  21. Code coverage Coverage on Android tests Coverage on Unit tests

    What if… "I want one report to rule them all" Gradle again project.afterEvaluate { def dest = "$buildDir/outputs/code-coverage/connected/coverage.ec" testDebugUnitTest.jvmArgs "-javaagent:$buildDir/intermediates/jacoco/ jacocoagent.jar=append=true,destfile=$destfile" createDebugAndroidTestCoverageReport.dependsOn testDebugUnitTest } Merge reports CI tasks ./gradlew clean createDebugAndroidTestCoverageReport
  22. Code QUAlity sonarQube Configuration CI Tasks • Provides metrics •

    Supports Android • Reports issues by priority • Integrates with JIRA, Mantis… • Implements SQALE
  23. Code QUAlity sonarQube Configuration CI Tasks SonarQube task apply plugin

    : "sonar-runner" task (name:"codeQuality", dependsOn: "test${variantName}UnitTestCoverageRepor") { } sonarRunner { sonarProperties { $PROPERTIES } } project.findByName("sonarRunner").execute() doLast { }
  24. Code QUAlity sonarQube Configuration CI Tasks SonarQube task property "sonar.host.url",

    “$sonarHost" property "sonar.projectKey", "$sonarProjectKey" property "sonar.projectName", "$sonarProjectName" property "sonar.jdbc.url", "$databaseHost" property "sonar.jdbc.driverClassName", "$databaseDriver" property "sonar.jdbc.username", "$databaseUsername" property "sonar.jdbc.password", "$databasePassword" property "sonar.sources", "$SOURCES" property "sonar.binaries", "${project.buildDir}/$BINARIES/$flavorName/$buildTypeName" property "sonar.jacoco.reportPath", "${project.buildDir}$JACOCO_PATH/test${variantName}UnitTest.exec" private final String BINARIES = "/intermediates/classes" private final String JACOCO_PATH = "/jacoco" private final String SOURCES = "src/main"
  25. DOcumentation vs javadoc Configuration CI Tasks • Enhanced look and

    feel • Versioning inside documentation • Customizations using templates • Embedding in bigger pages • Extended markup (@hide, @undeprecate…)
  26. DOcumentation vs javadoc Configuration CI Tasks Doclava task project.configurations {

    docLava } project.dependencies { docLava "com.google.doclava:doclava:1.0.6" }
  27. DOcumentation vs javadoc Configuration CI Tasks Doclava task project.task("create${variantName}Documentation", type:

    Javadoc) { } options { addStringOption "templatedir", "$yourTemplateDir" doclet “com.google.doclava.Doclava" bootClasspath new File(System.getenv('JAVA_HOME') + "/jre/lib/rt.jar") docletpath = project.configurations.docLava.files.asType(List) } title = null source = variant.javaCompile.source ext.androidJar = “${project.android.sdkDirectory}/platforms/“ + “${project.android.compileSdkVersion}/android.jar" classpath = project.files(variant.javaCompile.classpath.files) + project.files(ext.androidJar) exclude '**/BuildConfig.java' exclude '**/R.java'
  28. DOcumentation vs javadoc Configuration CI Tasks CI execution tasks ./gradlew

    app:create{flavor}{buildType}Documentation Task name provided:
  29. Plugin how to create one grill (ALPHA) Create a groovy

    project • src/main/groovy • apply plugin: “groovy" • Add dependencies Declare the plugin • Create src/main/resources/META-INF/gradle-plugins • Choose a name for the plugin • Create name.properties • Add: implementation-class=class.name.of.Plugin Declare the plugin • apply plugin: “maven” • ./gradlew plugin:install • apply plugin: "myCustomPlugin"
  30. Plugin how to create one grill (alpha) BBQ Grill Android

    Plugin https://github.com/droidpl/GrillPlugin DSL for quality projects grill { CI { $PROPERTIES } testing { $PROPERTIES } codeQuality { $PROPERTIES } documentation { $PROPERTIES } } • Multiple variants • Libraries and apps • Many tools - one place
  31. Conclusions (CI) Unit testing test$variantUnitTest Android Testing conected$variant AndroidTest Unit

    Testing Code coverage create$variantUnit TestCoverageReport Android Testing Code coverage create$variantAndroid TestCoverageReport quality codeQuality documentation create$variant Documentation
  32. Conclusions Gradle IS OUR FRIEND Multiple variants PROBLEMS Configuring everything

    is hard Quality measurements matters Testing EVERYTHING IS possible Take care of your build logic