Bundling and Testing Lint Checks

Intelia
5 min readDec 14, 2018

--

Last Week, on Understanding and Building Custom Android Lints, we dive deeper into the major component for building custom lint checks. With this post, we will be throwing light on testing link checks, use cases of lint checks and finally bundling our custom lint checks.

Unlike testing on Android, testing Lint checks is surprisingly easy. The first thing we need to do is add a few dependencies to our build.gradle:

dependencies {
testCompile 'junit:junit:4.11'
testCompile 'org.assertj:assertj-core:3.0.0'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'com.android.tools.lint:lint:24.3.1'
testCompile 'com.android.tools.lint:lint-tests:24.3.1'
testCompile 'com.android.tools:testutils:24.3.1'
}

The next step is to specify our source sets explicitly. We need to do this so our tests will know where to find the test-resources directory and don’t confuse other files as our test files.

sourceSets {
main {
java {
srcDirs = ["lint/src/main/java"]
}
}
test {
java {
srcDirs = ["lint/src/test/java"]
}
}
}

Registry Testing

public class CustomIssueRegistryTest {private CustomIssueRegistry mCustomIssueRegistry;/**
* Setup for the other test methods
*/

@Before
public void setUp() throws Exception {
mCustomIssueRegistry = new CustomIssueRegistry();
}
/**
* Test that the Issue Registry contains the correct number of Issues
*/

@Test
public void testNumberOfIssues() throws Exception {
int size = mCustomIssueRegistry.getIssues().size();
assertThat(size).isEqualTo(1);
}
/**
* Test that the Issue Registry contains the correct Issues
*/

@Test
public void testGetIssues() throws Exception {
List<Issue> actual = mCustomIssueRegistry.getIssues();
assertThat(actual).contains(AsyncTaskDetector.ISSUE);
}
}

We’re just instantiating our custom Registry and checking its size and list of Issues for correctness. There’s not much else to test here.

Testing the Detector

The useful tests are for the custom Detector. These tests will pull from external sample files in your test-resources directory and run a Lint check on each of them.Here’s a condensed version:

public class AsyncTaskDetectorTest extends AbstractDetectorTest {    @Override
protected Detector getDetector() {
return new AsyncTaskDetector();
}
@Override
protected List<Issue> getIssues() {
return Arrays.asList(AsyncTaskDetector.ISSUE);
}
@Override
protected String getTestResourceDirectory() {
return "enum";
}
/**
* Test that an empty java file has no warnings.
*/
public void testEmptyCase() throws Exception {
String file = "EmptyTestCase.java";
assertEquals(
NO_WARNINGS,
lintFiles(file)
);
}
/**
* Test that a java file with an async has a warning.
*/
public void testAsynTaskCase() throws Exception {
String file = "AsyncTaskFile.java";
String warningMessage = file
+ ": Warning: "
+ AsyncTaskDetector.ISSUE.getBriefDescription(TextFormat.TEXT)
+ " ["
+ AsyncTaskDetector.ISSUE.getId()
+ "]\n"
+ "0 errors, 1 warnings\n";
assertEquals(
warningMessage,
lintFiles(file)
);
}
}

The getDetector() method provides the Detector that we want to test, while getIssues() provides the Issues. For these, return AsyncTaskDetector and AsyncTaskIssue, respectively.

Here’s the content for AbstractDetectorTest.java

We have only two test cases, the empty example and a small AsyncTask example. For each case, we point to the file that we want to test, and then we call lintFiles(String path) to load the test file into memory as an on-the-fly Android project and then run a Lint check on it, returning its output as a String value.Once we have the String of the finished Lint check, we compare against what we expected.

For the empty test case, we expect a value equal to the NO_WARNINGS constant. For the AsyncTask case, we expect a warning message that is composed of a bunch of different variables we’ve defined throughout this process. Figuring out what the actual warning message will be takes some trial and error, but the simplest of Lint checks (like what we’ve done here) will follow the String concatenation pattern used in the testAsynaskCase() method.

You can write test files to help with your test process. Also note that test was written for Detectors and Registry Only. Both implementations and Issues are what enables the detector work effectively. So, writing test for Detectors also covers testing for implementation and Issue.

Running the Tests

My favorite way to do anything is via terminal. Navigate to the root of our custom Lint check project and run the following command:

./gradlew clean build test

Your output should look something like this:

:clean
:compileJava
:processResources NO-SOURCE
:classes
:jar
:assemble
:compileTestJava
:processTestResources NO-SOURCE
:testClasses
:test
:check
:build
BUILD SUCCESSFULTotal time: 2.044 secs

A TDD (test driven development) approach is recommended when building custom Lint checks. It’s a perfect use-case — provide a sample file and then write a Detector or Issue to fit the case.

Bundling Linty

If you recall, the final output of our custom Lint checker is a JAR file. Anytime we want to add Lint checks to our system, we simply add a JAR file to our ~/.android/lint/ directory. The Lint tool will always check there for anything new on each run. By running the assemble Gradle task, we will generate and assemble the JAR during each build. Now, we could move the output JAR from our build directory to the Lint directory manually, but we ca also incorporate it into the build process by adding this handy Gradle task to our build.gradle file:

defaultTasks 'assemble'task install(type: Copy) {
from configurations.lintChecks
into System.getProperty('user.home') + '/.android/lint/'
}

Let’s build and install our new Lint check by running the following from a terminal at the root of our custom Lint check project:

./gradlew clean build test install

Assuming everything builds and passes, let’s check to see if our new Lint rule is now available:

lint --show Enum

If everything went smoothly, you should see the following:

Enum
----
Summary: Avoid Using Enums
Priority: 5 / 10
Severity: ERROR
Category: Performance
The team has decided to always use rxJava. No Asynctask Please.

While having the Lint check available on a system level is great, the final trial is actually running our shiny new Lint check on a real project. Your Custom can now be used as describe in Getting the Most Out of Android Lint.

You can use Lint locally on android studio and also add it on your CI/CD environment to perform checks on your application before distribution.

Yay! Finally, our custom Lint check is registered in the system. Now we can use it on any project. It was quite a journey: we started with making sure everyone understand how powerful lint can be in your android development arsenal, showing how to build custom checks, testing , bundling and even giving insight on various use-cases of lint checks. Lint is an under-utilized tool by many Android developers, but I hope now you can start pushing the boundaries and making the most of static code analysis. Stay with us.

--

--