Presented at the droidcon NYC 2015:
http://droidcon.nyc/2015/dcnyc/25/
Nearly every Android developer has heard of the Lint and Checkstyle tools - however few use either to its full power, if at all. In addition to maintaining a consistent code style, we will see how to enforce architecture conventions and even prevent wrong usage of both internal and your own APIs.
For example, you have a fancy BaseFragment which should be extended by all your Fragments, or you have a custom logger which should be used instead of android.util.Log. Both of these are perfect use cases for custom Lint checks. This session will show you how to configure Checkstyle and Lint to your liking, and how to use their APIs to create custom checks, as well as how to include both in your Gradle-based project.
1. Better Code through Lint and Checkstyle
droidcon New York City | 28.08.2015 | Marc Prengemann
2. About me
Marc Prengemann
Android Software Engineer
Mail: marc@wire.com
Wire: marcprengemann@gmail.com
Github: winterDroid
Google+: Marc Prengemann
Twitter: @winterDroid89
4. What is Lint and Checkstyle?
App Source
Files
Configuration
Analysis Tool Report
5. When should I use it?
• to ensure code quality
• focus in reviews on real code
• prevent people from misusing internal
libraries
… but what are the challenges?
• Lint API is @Beta
• getting familiar with the API
• integrating within your Gradle Build
• debugging / testing
7. Test ideas
• Fragments and Activities should extend your
BaseClass
• use ViewUtils instead of finding and casting a
View
• don’t check floats for equality - use
Float.equals instead
• find leaking resources
• enforce Naming conventions
• find hardcoded values in XMLs
9. A real life example
• Timber
• logger by Jake Wharton
• https://github.com/JakeWharton/
timber
• want to create a detector that detects
misuse of android.util.Log instead of
Timber
10. public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
Detector
• responsible for scanning through code
and to find issues and report them
11. Detector
• responsible for scanning through code
and to find issues and report them
• most detectors implement one or more
scanner interfaces that depend on the
specified scope
• Detector.XmlScanner
• Detector.JavaScanner
• Detector.ClassScanner
public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
12. Detector
• responsible for scanning through code
and finding issue instances and reporting
them
• most detectors implement one or more
scanner interfaces
• can detect multiple different issues
• allows you to have different severities for
different types of issues
public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
13. Detector
• responsible for scanning through code
and finding issue instances and reporting
them
• most detectors implement one or more
scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• overwritten method depends on
implemented scanner interface
• depends on the goal of the detector
public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
14. Detector
• responsible for scanning through code
and finding issue instances and reporting
them
• most detectors implement one or more
scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• analyze the found calls
• overwritten method depends on
implemented scanner interface
• depends on the goal of the detector
public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
15. Detector
• responsible for scanning through code
and finding issue instances and reporting
them
• most detectors implement one or more
scanner interfaces
• can detect multiple different issues
• define the calls that should be analyzed
• analyze the found calls
• report the found issue
• specify the location
• report() will handle to suppress warnings
• add a message for the warning
public class WrongTimberUsageDetector extends Detector
implements Detector.JavaScanner {
public static final Issue ISSUE = ...;
@Override
public List<String> getApplicableMethodNames() {
return Arrays.asList("v", "d", "i", "w", "e", "wtf");
}
@Override
public void visitMethod(@NonNull JavaContext context,
AstVisitor visitor,
@NonNull MethodInvocation node) {
if (!(node.astOperand() instanceof VariableReference)) {
return;
}
VariableReference ref = (VariableReference) node.astOperand();
String refName = ref.astIdentifier().astValue();
if (!"Timber".equals(refName)) {
context.report(ISSUE,
node,
context.getLocation(node),
String.format("Use 'Timber' instead of '%s'",
refName));
}
}
}
16. Issue
• potential bug in an Android application
• is discovered by a Detector
• are exposed to the user
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
17. Issue
• the id of the issue
• should be unique
• recommended to add the package name as
a prefix like com.wire.LogNotTimber
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
18. Issue
• the id of the issue
• short summary
• typically 5-6 words or less
• describe the problem rather than the fix public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
19. Issue
• the id of the issue
• short summary
• full explanation of the issue
• should include a suggestion how to fix it public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
20. Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• Lint
• Correctness (incl. Messages)
• Security
• Performance
• Usability (incl. Icons, Typography)
• Accessibility
• Internationalization
• Bi-directional text
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
21. Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• a number from 1 to 10
• 10 being most important/severe
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
22. Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• Fatal
• Error
• Warning
• Informational
• Ignore
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
23. Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• the default implementation for this issue
• maps to the Detector class
• specifies the scope of the implementation
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
24. Issue
• the id of the issue
• short summary
• full explanation of the issue
• the associated category, if any
• the priority
• the default severity of the issue
• the default implementation for this issue
• the scope of the implementation
• describes set of files a detector must
consider when performing its analysis
• include:
• Resource files / folder
• Java files
• Class files
public static final Issue ISSUE =
Issue.create("LogNotTimber",
"Used Log instead of Timber",
"Since Timber is included in the project, "
+ "it is likely that calls to Log should "
+ "instead be going to Timber.",
Category.MESSAGES,
5,
Severity.WARNING,
new Implementation(WrongTimberUsageDetector.class,
Scope.JAVA_FILE_SCOPE));
25. Issue Registry
• provide list of checks to be performed
• return a list of Issues in getIssues()
public class CustomIssueRegistry extends IssueRegistry {
@Override
public List<Issue> getIssues() {
return Arrays.asList(WrongTimberUsageDetector.ISSUE);
}
}
27. A real life example
• Naming Conventions for variables
• improve Readability
public final static int FirstNumber = 0;
public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;
private static int sSecondNumber = 0;
private int firstNumber = 0;
private int mSecondNumber = 0;
int FirstNumber = 0;
int secondNumber = 0;
28. A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables public final static int FirstNumber = 0;
public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;
private static int sSecondNumber = 0;
private int firstNumber = 0;
private int mSecondNumber = 0;
int FirstNumber = 0;
int secondNumber = 0;
29. A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
public final static int FirstNumber = 0;
public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;
private static int sSecondNumber = 0;
private int firstNumber = 0;
private int mSecondNumber = 0;
int FirstNumber = 0;
int secondNumber = 0;
30. A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
• member variables
public final static int FirstNumber = 0;
public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;
private static int sSecondNumber = 0;
private int firstNumber = 0;
private int mSecondNumber = 0;
int FirstNumber = 0;
int secondNumber = 0;
31. A real life example
• Naming Conventions for variables
• improve Readability
• different styles for
• constant variables
• static variables
• member variables
• local variables
public final static int FirstNumber = 0;
public final static int SECOND_NUMBER = 0;
private static int FirstNumber = 0;
private static int sSecondNumber = 0;
private int firstNumber = 0;
private int mSecondNumber = 0;
int FirstNumber = 0;
int secondNumber = 0;
32. Check
• Visitor Pattern on the traversed Abstract
Syntax Tree (AST)
• Base class for checks
• beginTree()
• visitToken()
• leaveToken()
• finishTree()
public class NamingConventionCheck extends Check {
private static final String MSG_CONSTANT = "Constant '%s' " +
"should be named in all uppercase with underscores.";
@Override
public int[] getDefaultTokens() {
return new int[] {VARIABLE_DEF};
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST modifier = ast.findFirstToken(MODIFIERS);
final String name = ast.findFirstToken(IDENT)
.getText();
if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) ||
"serialVersionUID".equals(name)) {
return;
}
if (modifier.branchContains(LITERAL_STATIC) &&
modifier.branchContains(FINAL)) {
if (NamingConventions.isWrongConstantNaming(name)) {
log(ast.getLineNo(),
ast.getColumnNo(),
String.format(MSG_CONSTANT, name));
}
}
}
}
public class NamingConventions {
private static final Pattern WRONG_CONSTANT_NAME_PATTERN =
Pattern.compile(".*[a-z].*");
public static boolean isWrongConstantNaming(String variableName) {
return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName)
.matches();
}
}
33. Check
• Visitor Pattern on the traversed Abstract
Syntax Tree (AST)
• Base class for checks
• register for token types
• see TokenTypes class
• popular examples
• CLASS_DEF
• VARIABLE_DEF
• METHOD_DEF
public class NamingConventionCheck extends Check {
private static final String MSG_CONSTANT = "Constant '%s' " +
"should be named in all uppercase with underscores.";
@Override
public int[] getDefaultTokens() {
return new int[] {VARIABLE_DEF};
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST modifier = ast.findFirstToken(MODIFIERS);
final String name = ast.findFirstToken(IDENT)
.getText();
if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) ||
"serialVersionUID".equals(name)) {
return;
}
if (modifier.branchContains(LITERAL_STATIC) &&
modifier.branchContains(FINAL)) {
if (NamingConventions.isWrongConstantNaming(name)) {
log(ast.getLineNo(),
ast.getColumnNo(),
String.format(MSG_CONSTANT, name));
}
}
}
}
public class NamingConventions {
private static final Pattern WRONG_CONSTANT_NAME_PATTERN =
Pattern.compile(".*[a-z].*");
public static boolean isWrongConstantNaming(String variableName) {
return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName)
.matches();
}
}
34. Check
• Visitor Pattern on the traversed Abstract
Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• DetailAST provides utility methods
• possible to navigate around
• don’t abuse that feature! Just look at the
neighbours if necessary
public class NamingConventionCheck extends Check {
private static final String MSG_CONSTANT = "Constant '%s' " +
"should be named in all uppercase with underscores.";
@Override
public int[] getDefaultTokens() {
return new int[] {VARIABLE_DEF};
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST modifier = ast.findFirstToken(MODIFIERS);
final String name = ast.findFirstToken(IDENT)
.getText();
if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) ||
"serialVersionUID".equals(name)) {
return;
}
if (modifier.branchContains(LITERAL_STATIC) &&
modifier.branchContains(FINAL)) {
if (NamingConventions.isWrongConstantNaming(name)) {
log(ast.getLineNo(),
ast.getColumnNo(),
String.format(MSG_CONSTANT, name));
}
}
}
}
public class NamingConventions {
private static final Pattern WRONG_CONSTANT_NAME_PATTERN =
Pattern.compile(".*[a-z].*");
public static boolean isWrongConstantNaming(String variableName) {
return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName)
.matches();
}
}
35. Check
• Visitor Pattern on the traversed Abstract
Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• report the found issue
• DetailAST provides location
• supports internationalized error messages
public class NamingConventionCheck extends Check {
private static final String MSG_CONSTANT = "Constant '%s' " +
"should be named in all uppercase with underscores.";
@Override
public int[] getDefaultTokens() {
return new int[] {VARIABLE_DEF};
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST modifier = ast.findFirstToken(MODIFIERS);
final String name = ast.findFirstToken(IDENT)
.getText();
if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) ||
"serialVersionUID".equals(name)) {
return;
}
if (modifier.branchContains(LITERAL_STATIC) &&
modifier.branchContains(FINAL)) {
if (NamingConventions.isWrongConstantNaming(name)) {
log(ast.getLineNo(),
ast.getColumnNo(),
String.format(MSG_CONSTANT, name));
}
}
}
}
public class NamingConventions {
private static final Pattern WRONG_CONSTANT_NAME_PATTERN =
Pattern.compile(".*[a-z].*");
public static boolean isWrongConstantNaming(String variableName) {
return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName)
.matches();
}
}
36. Check
• Visitor Pattern on the traversed Abstract
Syntax Tree (AST)
• Base class for checks
• register for token types
• analyze the found tokens
• report the found issue
• (optional) define check properties
• uses JavaBean reflection
• works for all primitive types
• just use a setter
public class NamingConventionCheck extends Check {
private static final String MSG_CONSTANT = "Constant '%s' " +
"should be named in all uppercase with underscores.";
@Override
public int[] getDefaultTokens() {
return new int[] {VARIABLE_DEF};
}
@Override
public void visitToken(DetailAST ast) {
final DetailAST modifier = ast.findFirstToken(MODIFIERS);
final String name = ast.findFirstToken(IDENT)
.getText();
if (ScopeUtils.inInterfaceOrAnnotationBlock(ast) ||
"serialVersionUID".equals(name)) {
return;
}
if (modifier.branchContains(LITERAL_STATIC) &&
modifier.branchContains(FINAL)) {
if (NamingConventions.isWrongConstantNaming(name)) {
log(ast.getLineNo(),
ast.getColumnNo(),
String.format(MSG_CONSTANT, name));
}
}
}
}
public class NamingConventions {
private static final Pattern WRONG_CONSTANT_NAME_PATTERN =
Pattern.compile(".*[a-z].*");
public static boolean isWrongConstantNaming(String variableName) {
return WRONG_CONSTANT_NAME_PATTERN.matcher(variableName)
.matches();
}
}
43. Project structure
• custom-checkstyle
• Java module
• source of all custom checks
• dependency to selected Checkstyle version $ ./gradlew projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'awsm_app'
+--- Project ':app'
--- Project ':custom-checkstyle'
44. Project structure
• custom-checkstyle
• app
• uses Checkstyle plugin
• holds configuration
• module must be added with its fully
qualified classname
com.wire.NamingConventionCheck
for example to the TreeWalker
• has dependency to custom-checkstyle
module in the Checkstyle configuration
$ ./gradlew projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'awsm_app'
+--- Project ':app'
--- Project ':custom-checkstyle'
45. Project structure
• custom-checkstyle
• app
• check your project using with
./gradlew :app:checkstyle
• configure Checkstyle as described here:
http://goo.gl/aDBeU9
$ ./gradlew projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'awsm_app'
+--- Project ':app'
--- Project ':custom-checkstyle'
47. Testing Lint Checks
• part of lintrules module
• register and execute tests as usual in
build.gradle
public class WrongTimberUsageTest extends LintDetectorTest {
@Override
protected Detector getDetector() {
return new WrongTimberUsageDetector();
}
@Override
protected List<Issue> getIssues() {
return Arrays.asList(WrongTimberUsageDetector.ISSUE);
}
public void testLogTest() throws Exception {
final String expected = ...;
final TestFile testFile =
java("src/test/pkg/WrongTimberTest.java",
...);
assertEquals(expected, lintProject(testFile));
}
}
48. • part of lintrules module
• register and execute tests as usual in
build.gradle
• every test should extend
LintDetectorTest
• part of lint-tests dependency by
com.android.tools.lint
• tested with version 24.3.1
public class WrongTimberUsageTest extends LintDetectorTest {
@Override
protected Detector getDetector() {
return new WrongTimberUsageDetector();
}
@Override
protected List<Issue> getIssues() {
return Arrays.asList(WrongTimberUsageDetector.ISSUE);
}
public void testLogTest() throws Exception {
final String expected = ...;
final TestFile testFile =
java("src/test/pkg/WrongTimberTest.java",
...);
assertEquals(expected, lintProject(testFile));
}
}
Testing Lint Checks
49. • part of lintrules module
• register and execute tests as usual in
build.gradle
• every test should extend
LintDetectorTest
• need to define the to be used detector
and the selected issue(s)
public class WrongTimberUsageTest extends LintDetectorTest {
@Override
protected Detector getDetector() {
return new WrongTimberUsageDetector();
}
@Override
protected List<Issue> getIssues() {
return Arrays.asList(WrongTimberUsageDetector.ISSUE);
}
public void testLogTest() throws Exception {
final String expected = ...;
final TestFile testFile =
java("src/test/pkg/WrongTimberTest.java",
...);
assertEquals(expected, lintProject(testFile));
}
}
Testing Lint Checks
50. • part of lintrules module
• register and execute tests as usual in
build.gradle
• every test should extend
LintDetectorTest
• need to define the to be used detector
and the selected issue(s)
• perform usual unit tests with the helper
methods including:
• java - Create TestFile from source string
• lintProject - Lint on given files when
constructed as separate project
public class WrongTimberUsageTest extends LintDetectorTest {
@Override
protected Detector getDetector() {
return new WrongTimberUsageDetector();
}
@Override
protected List<Issue> getIssues() {
return Arrays.asList(WrongTimberUsageDetector.ISSUE);
}
public void testLogTest() throws Exception {
final String expected = ...;
final TestFile testFile =
java("src/test/pkg/WrongTimberTest.java",
...);
assertEquals(expected, lintProject(testFile));
}
}
Testing Lint Checks
52. Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in
build.gradle
public class NamingConventionTest extends BaseCheckTestSupport {
@Test
public void testConstantNaming() throws Exception {
final DefaultConfiguration checkConfig =
createCheckConfig(NamingConventionCheck.class);
final String[] expected = {
"7:5: Constant test4 should be " +
"named in all uppercase with underscores."
};
verify(checkConfig, getPath("ConstantName.java"), expected);
}
}
53. Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in
build.gradle
• every test should extend
BaseCheckTestSupport
• part of sevntu checks
• contains some nice helper methods
• createCheckConfig
• getPath
• getMessage
public class NamingConventionTest extends BaseCheckTestSupport {
@Test
public void testConstantNaming() throws Exception {
final DefaultConfiguration checkConfig =
createCheckConfig(NamingConventionCheck.class);
final String[] expected = {
"7:5: Constant test4 should be " +
"named in all uppercase with underscores."
};
verify(checkConfig, getPath("ConstantName.java"), expected);
}
}
54. Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in
build.gradle
• every test should extend
BaseCheckTestSupport
• need to create configuration for check
public class NamingConventionTest extends BaseCheckTestSupport {
@Test
public void testConstantNaming() throws Exception {
final DefaultConfiguration checkConfig =
createCheckConfig(NamingConventionCheck.class);
final String[] expected = {
"7:5: Constant test4 should be " +
"named in all uppercase with underscores."
};
verify(checkConfig, getPath("ConstantName.java"), expected);
}
}
55. Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in
build.gradle
• every test should extend
BaseCheckTestSupport
• need to create configuration for check
• tests are performed on real Java files
• part of test resources
• should be in same package as test class
public class NamingConventionTest extends BaseCheckTestSupport {
@Test
public void testConstantNaming() throws Exception {
final DefaultConfiguration checkConfig =
createCheckConfig(NamingConventionCheck.class);
final String[] expected = {
"7:5: Constant test4 should be " +
"named in all uppercase with underscores."
};
verify(checkConfig, getPath("ConstantName.java"), expected);
}
}
56. Testing Lint Checks
• part of custom-checkstyle module
• register and execute tests as usual in
build.gradle
• every test should extend
BaseCheckTestSupport
• need to create configuration for check
• tests are performed on real Java files
• verify for final test
public class NamingConventionTest extends BaseCheckTestSupport {
@Test
public void testConstantNaming() throws Exception {
final DefaultConfiguration checkConfig =
createCheckConfig(NamingConventionCheck.class);
final String[] expected = {
"7:5: Constant test4 should be " +
"named in all uppercase with underscores."
};
verify(checkConfig, getPath("ConstantName.java"), expected);
}
}
58. Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
./gradlew --daemon
$ ./gradle.properties
## Project-wide Gradle settings.
#
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:
+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details,
visit
# http://www.gradle.org/docs/current/userguide/
multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
org.gradle.jvmargs=-XX:MaxPermSize=4g
-XX:+HeapDumpOnOutOfMemoryError
-Xmx4g
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
59. Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
• create new debug configuration in IntelliJ
60. Debugging
• same for Checkstyle and Lint
• prepare and run Gradle daemon
• create new debug configuration in IntelliJ
• debug newly created configuration
62. Conclusion
• Lint and Checkstyle do not have a well
documented API
• sample project:
https://goo.gl/QBfyf8
• Checkstyle is easier to use and to integrate
• Lint seems to be more flexible
➡ to improve code quality
➡ help new team members
• integration of Lint Checks may become
easier with
https://goo.gl/d7U1qS