Skip to content

Commit

Permalink
Add JUnit 5 checkstyle rule
Browse files Browse the repository at this point in the history
Fixes gh-106
  • Loading branch information
philwebb committed Jun 7, 2019
1 parent 29caadc commit 9a60006
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.spring.javaformat.checkstyle.check;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;

/**
* Checks that JUnit 5 conventions are followed and that JUnit 4 is not accidentally used.
*
* @author Phillip Webb
*/
public class SpringJUnit5Check extends AbstractSpringCheck {

private static final String JUNIT4_TEST_ANNOTATION = "org.junit.Test";

private static final String JUNIT5_TEST_ANNOTATION = "org.junit.jupiter.api.Test";

private static final List<String> TEST_ANNOTATIONS = Collections
.unmodifiableList(Arrays.asList("Test", JUNIT4_TEST_ANNOTATION, JUNIT5_TEST_ANNOTATION));

private static final List<String> BANNED_IMPORTS;
static {
List<String> bannedImports = new ArrayList<>();
bannedImports.add(JUNIT4_TEST_ANNOTATION);
bannedImports.add("org.junit.After");
bannedImports.add("org.junit.AfterClass");
bannedImports.add("org.junit.Before");
bannedImports.add("org.junit.BeforeClass");
bannedImports.add("org.junit.Rule");
bannedImports.add("org.junit.ClassRule");
BANNED_IMPORTS = Collections.unmodifiableList(bannedImports);
}

private List<String> unlessImports = new ArrayList<>();

private final List<DetailAST> testMethods = new ArrayList<>();

private final Map<String, FullIdent> imports = new LinkedHashMap<>();

@Override
public int[] getAcceptableTokens() {
return new int[] { TokenTypes.METHOD_DEF, TokenTypes.IMPORT };
}

@Override
public void beginTree(DetailAST rootAST) {
this.testMethods.clear();
}

@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.METHOD_DEF:
visitMethodDef(ast);
case TokenTypes.IMPORT:
visitImport(ast);
break;
}
}

private void visitMethodDef(DetailAST ast) {
if (AnnotationUtil.containsAnnotation(ast, TEST_ANNOTATIONS)) {
this.testMethods.add(ast);
}
}

private void visitImport(DetailAST ast) {
FullIdent ident = FullIdent.createFullIdentBelow(ast);
this.imports.put(ident.getText(), ident);
}

@Override
public void finishTree(DetailAST rootAST) {
if (shouldCheck()) {
check();
}
}

private boolean shouldCheck() {
if (this.testMethods.isEmpty()) {
return false;
}
for (String unlessImport : this.unlessImports) {
if (this.imports.containsKey(unlessImport)) {
return false;
}
}
return true;
}

private void check() {
for (String bannedImport : BANNED_IMPORTS) {
FullIdent ident = this.imports.get(bannedImport);
if (ident != null) {
log(ident.getLineNo(), ident.getColumnNo(), "junit5.bannedImport", bannedImport);
}
}
for (DetailAST testMethod : this.testMethods) {
if (AnnotationUtil.containsAnnotation(testMethod, JUNIT4_TEST_ANNOTATION)) {
log(testMethod, "junit5.bannedTestAnnotation");
}
}
for (DetailAST testMethod : this.testMethods) {
DetailAST modifiers = testMethod.findFirstToken(TokenTypes.MODIFIERS);
if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null) {
log(testMethod, "junit5.publicMethod");
}
if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null) {
log(testMethod, "junit5.privateMethod");
}
}
}

private void log(DetailAST method, String key) {
String name = method.findFirstToken(TokenTypes.IDENT).getText();
log(method.getLineNo(), method.getColumnNo(), key, name);
}

public void setUnlessImports(String unlessImports) {
this.unlessImports = Collections.unmodifiableList(
Arrays.stream(unlessImports.split(",")).map(String::trim).collect(Collectors.toList()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ javadoc.badCase=Javadoc element descriptions should not start with an uppercase
header.unexpected=Unexpected header.
nothis.unexpected=Reference to instance variable ''{0}'' should not use \"this.\".
methodorder.outOfOrder=Method ''{0}'' is out of order, expected {1}.
junit5.publicMethod="Test method ''{0}'' should not be public."
junit5.privateMethod="Test method ''{0}'' should not be private."
junit5.bannedImport="Import ''{0}'' should not be used in a JUnit 5 test."
junit5.bannedTestAnnotation="JUnit 4 @Test annotation should not be used in a JUnit 5 test."
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+JUnit 4 @Test annotation should not be used in a JUnit 5 test.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+Import 'org.junit.Before' should not be used in a JUnit 5 test.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+0 errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
+Test method 'doSomethingWorks' should not be public
+Test method 'doSomethingElseWorks' should not be private
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+0 errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
</module>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
</module>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
<property name="unlessImports" value="com.example.OptOutRunner"/>
</module>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
</module>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
</module>
</module>
</module>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* Test with bad full qualified annotation.
*
* @author Phillip Webb
*/
public class JUnit5BadAnnotation {

@org.junit.Test
void doSomethingWorks() {
// test here
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.junit.jupiter.api.Test;
import org.junit.Before;

/**
* Test with banned import.
*
* @author Phillip Webb
*/
public class JUnit5BadImport {

@Before
public void bad() {
}

@Test
void doSomethingWorks() {
// test here
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.example.OptOutRunner;
import org.junit.RunWith;
import org.junit.Test;

/**
* Test with banned import but also opt-out trigger.
*
* @author Phillip Webb
*/
@RunWith(OptOutRunner.class)
public class JUnit5BadImportWithOptOut {

@Test
void doSomethingWorks() {
// test here
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.junit.jupiter.api.Test;

/**
* Test with bad modifiers.
*
* @author Phillip Webb
*/
public class JUnit5BadModifier {

@Test
public void doSomethingWorks() {
// test here
}

@Test
private void doSomethingElseWorks() {
// test here
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import org.junit.jupiter.api.Test;

/**
* This is a valid example.
*
* @author Phillip Webb
*/
public class JUnit5Valid {

@Test
void doSomethingWorks() {
// test here
}

}

0 comments on commit 9a60006

Please sign in to comment.