Since Android Studio became available there was some amount of confusion on how configure unit tests to use Robolectric, how to get code coverage working. There are different solutions, but I’ll explain mine.
So, I’ll be using:
- Android Studio v1.0.2
- Gradle 1.0.0
- Robolectric 2.4
- JUnit 4.12
- Jacoco gradle plugin
Part 1 – Project setup
Here I’ll create android library as my main project and java project for my unit tests. Steps are the same for application project (with some small differences).
Let’s start with the library project. Right now it’s now very straightforward how to create a library only project (I’m sure it will get better over time, as Android Studio matures). So, here is one way:
- launch Android Studio, create a new project:
- select API level, form factors, etc.
- don’t select any activities (unless you need to), press “Finish”
- rename “app” module and folder into “lib” (or “library”).
it should look something like this:
But it’s still an application, even though it’s called “lib”. There are few more steps:
- go to “lib” module’s build.gradle file and change
apply plugin: 'com.android.application'
toapply plugin: 'com.android.library'
- rename module in settings.gradle (right now renaming module doesn’t change the name in settings.gradle, and it does feel like a bug in Android Studio, so it may or may not be necessary).
- remove ‘applicationId’ from ‘defaultConfig’
- remove ‘appcompat’ from dependecies
- cleanup AndroidManifest.xml
- etc.
Now we got the library.
Part 2 – Unit test project
As I said before, I prefer to keep unit test in the separate project. To do this, we’ll need to add a separate Java project where all our tests will go. So, add a new module to the project, select “Java Library” from the “More Modules”. Give it a name, like “tests”, and click “Finish”. Once the project is created, add a folder for actual tests. Full path should be: $MODULE_DIR$/src/test/java/$PACKAGE, for instance “tests/src/test/java/com.example.demolib”. I just change the name of the “main” folder in tests project to “test”.
Part 3 – Configuration
Let’s start with configuring dependencies and Robolectric. There are few steps that need to be taken here:
- declare a reference to “lib” module
- include it into ‘compile’ phase (so it wouldn’t fail if you clean/rebuild a project)
- include only needed stuff in ‘testCompile’ – that includes classes from ‘lib’ and Android-related references (like android.jar, etc).
- Define which tests to include. I usually append “Tests” to all the *.java files that contain tests, so that include filter is minimal.
- Declare Robolectic, JUnit and other dependencies.
build.gradle at this point should look something like this:
evaluationDependsOn(':lib')
apply plugin: 'java'
def testIncludes = [
'**/*Tests.class'
]
def libraryModule = project(':lib')
def firstVariant = libraryModule.android.libraryVariants.toList().first()
dependencies {
compile libraryModule;
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:2.4'
testCompile firstVariant.javaCompile.classpath
testCompile firstVariant.javaCompile.outputs.files
testCompile files(libraryModule.plugins.findPlugin("com.android.library").getBootClasspath())
}
tasks.withType(Test) {
scanForTestClasses = false
includes = testIncludes
}
Go ahead a add your tests now.
We got the Robolectric tests running, we can now add Jacoco code coverage report. To do so requires just a ‘simple’ change in build.gradle (and I put ‘’ around simple, as it took me some time to find a solution, and it’s not mine: http://minimalbible.github.io/android-and-jacoco/. Read the article, it’s good, and I’m not going to repeat it here).
An updated version of the build.gradle should look as following:
evaluationDependsOn(':lib')
apply plugin: 'java'
apply plugin: 'jacoco'
def testIncludes = [
'**/*Tests.class'
]
def jacocoExcludes = [
'android/**',
'**/*$$*'
]
def libraryModule = project(':lib')
def firstVariant = libraryModule.android.libraryVariants.toList().first()
dependencies {
compile libraryModule;
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:2.4'
testCompile firstVariant.javaCompile.classpath
testCompile firstVariant.javaCompile.outputs.files
testCompile files(libraryModule.plugins.findPlugin("com.android.library").getBootClasspath())
}
def buildExcludeTree(path, excludes) {
def tree = fileTree(path).exclude(excludes)
tree
}
jacocoTestReport {
doFirst {
// First we build a list of our base directories
def fileList = new ArrayList()
def outputsList = firstVariant.javaCompile.outputs.files
outputsList.each { fileList.add(it.absolutePath.toString()) }
// And build a fileTree from those
def outputTree = fileList.inject { tree1, tree2 ->
buildExcludeTree(tree1, jacocoExcludes) +
buildExcludeTree(tree2, jacocoExcludes)
}
// And finally tell Jacoco to only include said files in the report
// For whatever reason, firstVariant.javaCompile.exclude(jacocoExcludes) doesn't work...
classDirectories = outputTree
sourceDirectories = files(libraryModule.android.sourceSets.main.java.srcDirs)
}
reports {
xml.enabled true
}
}
tasks.withType(Test) {
scanForTestClasses = false
includes = testIncludes
}
To run the test and code coverage report you can run the following Gradle command from Terminal window:
gradlew test jacocoTestReport
Once it’s done, you should have a report in ‘build’ folder of your ‘tests’ project (under “tests\build\reports\jacoco\test”).
Part 4 – Run tests from Android Studio
Of course you can run tests from command line (from Terminal for instance) with ‘gradlew test’ command. But if you want to run them from Android Studio UI, you can’t just select ‘Run All Tests’ in context menu and run them. There is some configuration that needs to be done before that. Open up “Run/Debug Configuration” window and add the following:
- Gradle build task (to rebuild test project every time we run the test) – name it “Build tests”.
- JUnit run config - (to run all tests in the “tests” project):
After all the changed are saved, you can the configuration:
Sample project can be found on the GitHub: https://github.com/iuriioapps/AndroidStudio_Robolectric_Jacoco.
No comments:
Post a Comment