代码之家  ›  专栏  ›  技术社区  ›  David M. Karr

编写基于Java的SonarQube规则来检查assertj错误,无法确定何时未调用链式方法

  •  0
  • David M. Karr  · 技术社区  · 4 年前

    我在一家使用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的解决方案,但我不太理解他的说明。

    0 回复  |  直到 4 年前