我在一家使用SonarQube 7.9.2的大型企业工作。我看到很多测试代码错误地使用了AssertJ框架。我看到SonarSource确实有一套关于AssertJ的小规则,但它们都没有涵盖我正在研究的特定问题。
以下是一个示例(我的测试输入之一),显示了我正在寻找的模式:
import static org.assertj.core.api.Assertions.*;
public class AssertThatWithNoPredicate {
public Foo foo = new Foo();
@Test
public void assertSomething() throws Exception {
assertThat(foo.getStuff().equals("abc"));
}
public static class Foo {
private String stuff;
public String getStuff() { return stuff; }
public void setStuff(String aStuff) { this.stuff = stuff; }
}
}
上面的断言是有缺陷的,因为它需要这样做:
assertThat(foo.getStuff()).isEqualTo("abc");
因此,我开始尝试编写一个基于SonarQube java的规则来检查这一点。有相关文档,但没有我想要的那么深入。它有一些例子,但并没有详细解释api。我从
https://github.com/SonarSource/sonar-java/blob/master/docs/CUSTOM_RULES_101.md
作为我的向导。
以下是我目前对自定义规则的看法。它不能正常工作。我将进一步探讨失败的问题。
package org.sonar.samples.java.checks;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.JavaFileScanner;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ImportTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
@Rule(key = "AssertThatLackingPredicateCheck",
name = "Call to assertThat method has to have at least one chained predicate method call",
description = "The AssertJ assertThat method has to have at least one chained predicate method called on the return value, or it does nothing",
priority = org.sonar.check.Priority.MAJOR,
tags = {"bug"})
public class AssertThatLackingPredicateCheck extends BaseTreeVisitor implements JavaFileScanner {
private JavaFileScannerContext context;
@Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
scan(context.getTree());
}
@Override
public void visitImport(ImportTree tree) {
//scan(tree.qualifiedIdentifier());
System.out.println("ident[" + tree.qualifiedIdentifier() + "]");
super.visitImport(tree);
}
@Override
public void visitMemberSelectExpression(MemberSelectExpressionTree tree) {
System.out.println("In visitMemberSelectExpression.");
scan(tree.annotations());
System.out.println("tree.expression.firstToken[" + tree.expression().firstToken().text() + "]");
scan(tree.expression());
scan(tree.identifier());
System.out.println("Exiting visitMemberSelectExpression.");
}
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
System.out.println("In visitMethodInvocation.");
System.out.println("methodSelect[" + tree.methodSelect().firstToken().text() +
"] kind[" + tree.methodSelect().kind() +
"] parent.kind[" + tree.parent().kind() +
"]");
scan(tree.methodSelect());
scan(tree.typeArguments());
scan(tree.arguments());
if (tree.methodSelect().firstToken().text().equals("assertThat") &&
tree.methodSelect().kind() == Kind.IDENTIFIER &&
tree.parent().kind() == Kind.EXPRESSION_STATEMENT) {
System.out.println("Reporting issue.");
context.reportIssue(this, tree,
"Calls to assertThat have to chain predicate method calls, or the assertion does nothing.");
}
System.out.println("Exiting visitMethodInvocation.");
}
}
我的测试课是这样的:
package org.sonar.samples.java.checks;
import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.JavaCheckVerifier;
public class AssertThatLackingPredicateCheckTest {
@Test
public void assertThatWithNoPredicate() throws Exception {
JavaCheckVerifier.newVerifier()
.onFile("src/test/files/AssertThatWithNoPredicate.java")
.withCheck(new AssertThatLackingPredicateCheck())
.verifyIssues();
}
@Test
public void assertThatWithValidPredicate() throws Exception {
JavaCheckVerifier.newVerifier()
.onFile("src/test/files/AssertThatWithValidPredicate.java")
.withCheck(new AssertThatLackingPredicateCheck())
.verifyNoIssues();
}
}
第一个测试文件包含在上面。第二个是:
import static org.assertj.core.api.Assertions.*;
public class AssertThatWithValidPredicate {
public Foo foo = new Foo();
@Test
public void assertSomething() throws Exception {
assertThat(foo.getStuff()).isEqualTo("abc");
}
public static class Foo {
private String stuff;
public String getStuff() { return stuff; }
public void setStuff(String aStuff) { this.stuff = stuff; }
}
}
当我运行这个时,“有效”测试实际上通过了,但考虑到我并不真正理解我使用的api,这很可能是巧合。
“无效”测试失败,堆栈跟踪异常:
java.lang.AssertionError: Unexpected at [10]
at org.sonar.java.testing.InternalCheckVerifier.assertMultipleIssues(InternalCheckVerifier.java:308)
at org.sonar.java.testing.InternalCheckVerifier.checkIssues(InternalCheckVerifier.java:232)
at org.sonar.java.testing.InternalCheckVerifier.verifyAll(InternalCheckVerifier.java:223)
at org.sonar.java.testing.InternalCheckVerifier.verifyIssues(InternalCheckVerifier.java:168)
at org.sonar.samples.java.checks.AssertThatLackingPredicateCheckTest.assertThatWithNoPredicate(AssertThatLackingPredicateCheckTest.java:12)
我还要指出,这不是我第一次尝试解决这个问题,但那是几年前的事了,当时我使用AssertJ所基于的旧框架进行测试。当时,我为此开发了一个XPath规则,它似乎真的有效。不幸的是,我没有能力将XPath规则集成到我们的SonarQube实例中,只能使用基于Java的规则。
老线索在这里:
http://sonarqube-archive.15.x6.nabble.com/Write-a-custom-XPath-task-that-looks-for-a-method-that-is-NOT-followed-by-a-chained-method-call-td5024017.html
。回复确实试图介绍可能的基于Java的解决方案,但我不太理解他的说明。