#babbqamsterdam the other android getting started guide: gradle power
TRANSCRIPT
THE OTHER ANDROID GETTING STARTED GUIDE: GRADLE POWER
Javier de Pedro López
StrengthsExperience
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]
`Why the other getting started guide?
Beginner Junior
Medior
…
Getting started
The other getting started
This talk
Gradle in Android
Continuous Integration
Android Testing and
Unit testing
Code coverage DocumentationPut it all together
GRADLE IN ANDROID: THE PLUGIN UNIVERSE Round 1
• 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
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
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 {
}
Gradle in Android
The project
Closures / DSL
TASKS
Context
Android DSLandroid { ...
... }
buildTypes { debug { } release { }
}
Build types
productFlavors { pro {
dimension 'version' } x86 {
dimension ‘abi' }
}
Flavors
flavorDimensions 'abi', 'version' Dimensions
Variant == Flavor(s) + BuildType
Gradle in Android
The project
Closures / DSL
TASKS
Context
org.gradle.api.Task
Execute task
./gradlew app:myTask
Android StudioDefine tasks
task mytask << { println "Ending" }
mytask.doFirst { println "Beginning" }
mytask.dependsOn(assembleDebug)
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
CONTINUOUS INTEGRATION TIPSRound 2
You shall not pass
CI Tips
CI version code
ci Version name
WhyAndroid versioning problemandroid { ... 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
CI Tips
CI version code
ci Version name
WhyCount 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 }
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" } }
UNIT TESTING AND ANDROID TESTINGRound 3
Android testing (espresso,...) Unit testing (JUnit)
Android testing / Unit testingWhy 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
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
Why two types
Android Testing
Unit TestingCI 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
Why two types
Android Testing
Unit Testing
CI tasksCI 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
CODE COVERAGE WITH JACOCO: NOW IN THE JVMRound 4
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
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
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 tasktask (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.*'] )
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
Code coverage
Coverage on Android tests
Coverage on Unit tests
What if…
"I want one report to rule them all"
Gradle againproject.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
CODE QUALITYRound 5
Code QUAlity
sonarQube
Configuration
CI Tasks
• Provides metrics
• Supports Android
• Reports issues by priority
• Integrates with JIRA, Mantis…
• Implements SQALE
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 {
}
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"
Code QUAlity
Configuration
CI Tasks
sonarQube
CI execution tasks
./gradlew app:codeQuality
Task name provided:
DOCUMENTATION: WHY NOT DOCLAVA?Round 6
DOcumentation
vs javadoc
Configuration
CI Tasks
• Enhanced look and feel
• Versioning inside documentation
• Customizations using templates
• Embedding in bigger pages
• Extended markup (@hide, @undeprecate…)
DOcumentation
vs javadoc
Configuration
CI Tasks
Doclava task
project.configurations { docLava }
project.dependencies { docLava "com.google.doclava:doclava:1.0.6" }
DOcumentation
vs javadoc
Configuration
CI Tasks
Doclava taskproject.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'
DOcumentation
vs javadoc
Configuration
CI Tasks
CI execution tasks
./gradlew app:create{flavor}{buildType}Documentation
Task name provided:
PLUGIN: ALL TOGETHERRound 7
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"
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
Conclusions (CI)
Unit testing
test$variantUnitTest
Android Testingconected$variant
AndroidTest
Unit Testing Code coverage
create$variantUnit TestCoverageReport
Android Testing Code coverage
create$variantAndroid TestCoverageReport
quality
codeQuality
documentationcreate$variant Documentation
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
……………………Q&A
……………………Thank you!