better code through lint and checkstyle

64
Better Code through Lint and Checkstyle droidcon New York City | 28.08.2015 | Marc Prengemann

Upload: marc-prengemann

Post on 22-Jan-2018

2.038 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: Better Code through Lint and Checkstyle

Better Code through Lint and Checkstyledroidcon New York City | 28.08.2015 | Marc Prengemann

Page 2: Better Code through Lint and Checkstyle

About me

Marc Prengemann Android Software Engineer

Mail: [email protected] Wire: [email protected] Github: winterDroid Google+: Marc Prengemann Twitter: @winterDroid89

Page 3: Better Code through Lint and Checkstyle

Lint? Checkstyle? What?

Page 4: Better Code through Lint and Checkstyle

What is Lint and Checkstyle?

App Source Files

Configuration

Analysis Tool Report

Page 5: Better Code through Lint and Checkstyle

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

Page 6: Better Code through Lint and Checkstyle

Getting started with own Checks

Page 7: Better Code through Lint and Checkstyle

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

Page 8: Better Code through Lint and Checkstyle

Library misuse detection with Lint

Page 9: Better Code through Lint and Checkstyle

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

Page 10: Better Code through Lint and Checkstyle

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

Page 11: Better Code through Lint and Checkstyle

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)); } }}

Page 12: Better Code through Lint and Checkstyle

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)); } }}

Page 13: Better Code through Lint and Checkstyle

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)); } }}

Page 14: Better Code through Lint and Checkstyle

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)); } }}

Page 15: Better Code through Lint and Checkstyle

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)); } }}

Page 16: Better Code through Lint and Checkstyle

Issue

• potential bug in an Android application

• is discovered by a Detector

• are exposed to the userpublic 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));

Page 17: Better Code through Lint and Checkstyle

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));

Page 18: Better Code through Lint and Checkstyle

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));

Page 19: Better Code through Lint and Checkstyle

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));

Page 20: Better Code through Lint and Checkstyle

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));

Page 21: Better Code through Lint and Checkstyle

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));

Page 22: Better Code through Lint and Checkstyle

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));

Page 23: Better Code through Lint and Checkstyle

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));

Page 24: Better Code through Lint and Checkstyle

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));

Page 25: Better Code through Lint and Checkstyle

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); } }

Page 26: Better Code through Lint and Checkstyle

Naming Convention enforcement with Checkstyle

Page 27: Better Code through Lint and Checkstyle

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;

Page 28: Better Code through Lint and Checkstyle

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;

Page 29: Better Code through Lint and Checkstyle

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;

Page 30: Better Code through Lint and Checkstyle

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;

Page 31: Better Code through Lint and Checkstyle

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;

Page 32: Better Code through Lint and Checkstyle

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(); }}

Page 33: Better Code through Lint and Checkstyle

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(); }}

Page 34: Better Code through Lint and Checkstyle

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(); } }

Page 35: Better Code through Lint and Checkstyle

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(); }}

Page 36: Better Code through Lint and Checkstyle

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(); }}

Page 37: Better Code through Lint and Checkstyle

Include Lint within your Project

Page 38: Better Code through Lint and Checkstyle

Project structure

• lintrules

• Java module

• source of all custom detectors and our IssueRegistry

• specify reference in build.gradle as attribute Lint-Registry

• will export a lint.jar

$ ./gradlew projects :projects

------------------------------------------------------------ Root project ------------------------------------------------------------

Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'

jar { manifest { attributes 'Manifest-Version': 1.0 attributes 'Lint-Registry': 'com.checks.CustomIssueRegistry' } }

Page 39: Better Code through Lint and Checkstyle

Project structure

• lintrules

• lintlib

• Android library module

• has a dependency to lintrules $ ./gradlew projects :projects

------------------------------------------------------------ Root project ------------------------------------------------------------

Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'

Page 40: Better Code through Lint and Checkstyle

Project structure

• lintrules

• lintlib

• app

• has dependency to lintlib module

• since lintlib is an Android library module, we can use the generated lint.jar

• circumvent bug introduced in build tools version 1.2.0

https://goo.gl/1uCNSR

$ ./gradlew projects :projects

------------------------------------------------------------ Root project ------------------------------------------------------------

Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'

Page 41: Better Code through Lint and Checkstyle

Project structure

• lintrules

• lintlib

• app

• check your project using with

./gradlew :app:lintDebug • configure Lint as described here:

http://goo.gl/xABHhy

$ ./gradlew projects :projects

------------------------------------------------------------ Root project ------------------------------------------------------------

Root project 'awsm_app' +--- Project ':app' +--- Project ':lintlib' \--- Project ':lintrules'

Page 42: Better Code through Lint and Checkstyle

Include Checkstyle within your Project

Page 43: Better Code through Lint and Checkstyle

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'

Page 44: Better Code through Lint and Checkstyle

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'

Page 45: Better Code through Lint and Checkstyle

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'

Page 46: Better Code through Lint and Checkstyle

Testing Lint Checks

Page 47: Better Code through Lint and Checkstyle

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)); }}

Page 48: Better Code through Lint and Checkstyle

• 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

Page 49: Better Code through Lint and Checkstyle

• 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

Page 50: Better Code through Lint and Checkstyle

• 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

Page 51: Better Code through Lint and Checkstyle

Testing Checkstyle Checks

Page 52: Better Code through Lint and Checkstyle

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); }}

Page 53: Better Code through Lint and Checkstyle

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); }}

Page 54: Better Code through Lint and Checkstyle

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); }}

Page 55: Better Code through Lint and Checkstyle

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); }}

Page 56: Better Code through Lint and Checkstyle

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); }}

Page 57: Better Code through Lint and Checkstyle

Debugging

Page 58: Better Code through Lint and Checkstyle

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=trueorg.gradle.jvmargs=-XX:MaxPermSize=4g \ -XX:+HeapDumpOnOutOfMemoryError \ -Xmx4g \ -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000

Page 59: Better Code through Lint and Checkstyle

Debugging

• same for Checkstyle and Lint

• prepare and run Gradle daemon

• create new debug configuration in IntelliJ

Page 60: Better Code through Lint and Checkstyle

Debugging

• same for Checkstyle and Lint

• prepare and run Gradle daemon

• create new debug configuration in IntelliJ

• debug newly created configuration

Page 61: Better Code through Lint and Checkstyle

Conclusion

Page 62: Better Code through Lint and Checkstyle

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

Page 63: Better Code through Lint and Checkstyle

Questions?

Page 64: Better Code through Lint and Checkstyle

Thank you!