繁体   English   中英

如何创建未在故障跟踪中显示的自定义JUnit4断言

[英]How to create custom JUnit4 assertions that don't show in the Failure Trace

我想在我们的代码库中添加一些自定义断言,以正确隐藏故障跟踪。 我知道如何编写一个可以静态导入的公共静态方法。 我知道如何重用旧的断言或抛出一个新的AssertionError

我无法弄清楚如何做的是将新的自定义断言保留在Failure Trace之外。 我们已经习惯了故障跟踪中的第一个命中,而不是断言代码本身,而是调用断言的测试代码。

我知道有一个filtertrace属性控制过滤堆栈,但我找不到任何关于将新断言添加到过滤器我必须做什么的好文档。

我想做的一个例子:

package testassertions;

import static newassertions.MyAssertions.myAssertTrue;

import org.junit.Test;

public class ExampleTest {
    @Test
    public void myAssertTruePassing() { myAssertTrue(true); }

    @Test
    public void myAssertTrueFailing() { myAssertTrue(false); }
}

package newassertions;

import static org.junit.Assert.assertTrue;

public class MyAssertions {

    public static void myAssertTrue(boolean b) {
        assertTrue(b);
    }
}

myAssertTrueFailing()的失败跟踪显示:

java.lang.AssertionError
    at newassertions.MyAssertions.myAssertTrue(MyAssertions.java:8)
    at testassertions.ExampleTest.myAssertTrueFailing(ExampleTest.java:12)

我需要它只显示:

java.lang.AssertionError
    at testassertions.ExampleTest.myAssertTrueFailing(ExampleTest.java:12)

您是否考虑过使用org.junit.Assert.assertThatHamcrest匹配器一起使用?

使用Hamcrest,您不需要更改断言方法,而是实现自己的匹配器。 例如,要验证BCrypt-hashed密码是否与普通密码匹配,请编写如下匹配器:

public class MatchesPassword extends TypeSafeMatcher<String> {

    private static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

    private final String password;

    public MatchesPassword(String password) {
        this.password = password;
    }

    @Override
    protected boolean matchesSafely(String encodedPassword) {
        return PASSWORD_ENCODER.matches(password, encodedPassword);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("matches password ");
        description.appendValue(password);
    }
}

接下来,添加一个可以静态导入的方法:

public class CustomMatchers {

    public static Matcher<String> matchesPassword(String password) {
        return new MatchesPassword(password);
    }

}

最后,像这样编写测试:

@Test
public void passwordShouldMatch() {
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder()
    String plainPassword = "secret";
    String hashedPassword = passwordEncoder.encode(plainPassword);

    assertThat(hashedPassword, matchesPassword(plainPassword));
}

不匹配将记录到控制台,如下所示:

java.lang.AssertionError: 
Expected: matches password "wrong"
     but: was "$2a$10$5lOyLzUeKMAYPJ5A3y5KfOi747DocksLPHgR7GG3XD8pjp8mhaf0m"
    at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:18)
    at org.junit.Assert.assertThat(Assert.java:956)
    at org.junit.Assert.assertThat(Assert.java:923)
    ...

注意:BCryptPasswordEncoder来自Spring Security,仅用作示例。

正如另一个关于清除堆栈跟踪噪声的问题所述,从IDE中过滤类可能是最简单的解决方案。 实际上,您在问题中显示的堆栈跟踪已经过滤。

如果您真的想在代码中执行此操作,则可以向自定义断言类添加过滤,如下所示:

package newassertions;

import static org.junit.Assert.assertTrue;
import java.util.ArrayList;

public class MyAssertions {

    public static void myAssertTrue(boolean b) {
        try {
            assertTrue(b);
        } catch (AssertionError e) {
            filterStackTrace(e);
            throw e;
        }
    }

    private static void filterStackTrace(AssertionError error) {
        StackTraceElement[] stackTrace = error.getStackTrace();
        if (null != stackTrace) {
            ArrayList<StackTraceElement> filteredStackTrace = new ArrayList<StackTraceElement>();
            for (StackTraceElement e : stackTrace) {
                if (!"newassertions.MyAssertions".equals(e.getClassName())) {
                    filteredStackTrace.add(e);
                }
            }
            error.setStackTrace(filteredStackTrace.toArray(new StackTraceElement[0]));
        }
    }
}

在此示例中,从堆栈跟踪中过滤了封闭类“newassertions.MyAssertions”(硬编码)的名称。 显然,这种机制也可以用于从您自己创建的AssertionError中过滤堆栈跟踪,而不仅仅是从其他断言中提取的那些。

我的合作解决方案也将是其他人已经建议的IDE过滤器。 如果您执行“硬编码”解决方案,则在自动构建过程中将无法追踪。

在Eclipse中,您可以打开首选项并选择Java - > JUnit,并使用右侧的按钮添加类或包。

但只是为了它的乐趣:


如果你真的想以编程方式做到这一点@ gar的解决方案听起来很合理。 但是,如果你有更多的断言,这可能有点乏味。

您还可以做的是继承AssertionError并在其根处过滤堆栈跟踪。

public class MyAssertionError extends AssertionError {

    public MyAssertionError(String message) {
        super(message);
    }

    @Override
    public synchronized Throwable fillInStackTrace() {
        super.fillInStackTrace();
        filterStackTrace();
        return this;
    }

    protected void filterStackTrace() {
        StackTraceElement[] trace = getStackTrace();
        ArrayList<StackTraceElement> list = new ArrayList<StackTraceElement>(trace.length);
        for (StackTraceElement element : trace) {
            if (!element.getClassName().equals("newassertions.MyAssertions")) {
                list.add(element);
            }
        }
        this.setStackTrace(list.toArray(new StackTraceElement[0]));
    }

}

请注意以下两点:1) StackTraceElement的类名永远不能为null,因此可以在右侧编写常量2)如果将所有断言放在单独的包中,也可以编写element.getClassName().startsWith("newassertions")

你的断言类看起来像这样:

package newassertions;

public class MyAssertions {

    public static void myAssertTrue(boolean b) {
        if (!b) {
            fail(null);
        }
    }

    public static void fail(String message) {
        if (message == null) {
            throw new MyAssertionError(message);
        }
        throw new MyAssertionError(message);
    }


}

这样你就无法从Assert调用方法,但是如果你编写更复杂的断言,那么无论如何都没有理由这样做。 但是,与在大型try-catch块中包装所有内容相比,它会使您的断言代码更清晰。

您可以将自定义JUnit方法规则与自定义断言一起使用。 自定义断言可以使用AssertionError的子类型。 这甚至允许您一起使用Junit断言和自定义断言。

下面是使用自定义的例子MyAssert抛出类MyAssertionError S IN的情况下断言failes。 JUnit规则处理MyAssertionError并隐藏故障跟踪的任何详细信息。

public class RuleTest {

  @Rule
  public TestVerifier testVerifier = new TestVerifier();

  @Test
  public void myAssertOk() { MyAssert.assertCondition("ok", true); }

  @Test
  public void myAssertNotOk() { MyAssert.assertCondition("nok", false); }

  @Test
  public void junitAssertNotOk() { assertTrue(false); }

  @Test
  public void junitAssertOk() { assertTrue(true); }

  static class TestVerifier implements TestRule {

    @Override
    public Statement apply(Statement base, Description description) {
      return new Statement() {

        @Override
        public void evaluate() throws Throwable {
          try {
            base.evaluate();
          } catch (MyAssertionError t) {
            throw new AssertionError("Test failed: " + description.getMethodName());
          }
        }
      };
    }

  }

  static class MyAssertionError extends AssertionError {
    public MyAssertionError(Object detailMessage) { super(detailMessage); }
  }

  static final class MyAssert {
    public static void assertCondition(String message, boolean condition) {
      if (!condition) { throw new MyAssertionError(message); }
    }
  }
}

使用此自定义TestVerifier规则,您的故障跟踪仅会说:

java.lang.AssertionError: Test failed: verifierTest
    at RuleTest$TestVerifier.apply(RuleTest.java:26)
    at org.junit.rules.RunRules.applyAll(RunRules.java:26)
    ...

在您的IDE中它将如下所示:

IDE屏幕截图

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM