我想在我们的代码库中添加一些自定义断言,以便正确隐藏故障跟踪。我知道如何编写一个可以静态导入的公共静态方法。我知道如何重用旧的断言或抛出一个新的AssertionError
。
我能弄清楚如何做的是将新的自定义断言保留在故障跟踪之外。我们习惯于故障跟踪中的第一个命中不是断言代码本身,而是调用断言的测试代码。
我知道有一个filtertrace
属性可以控制对堆栈进行过滤,但我找不到任何有关将新断言添加到过滤器所需要做的好文档。< / p>
我想做的一个例子:
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)
答案 0 :(得分:4)
您是否考虑过将org.junit.Assert.assertThat
与Hamcrest匹配使用?
使用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,仅用作示例。
答案 1 :(得分:4)
如another question about cleaning noise from stack traces中所述,从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中过滤堆栈跟踪,而不仅仅是从其他断言引出的那些。
答案 2 :(得分:2)
我的合作解决方案也将是其他人已经建议的IDE过滤器。如果你做了一个&#34;硬编码&#34;解决方案在自动构建过程中不太可追溯。
在Eclipse中,您可以打开首选项并选择Java - &gt; 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
的类名永远不能为空,所以在右侧写常量是很好的
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块中包装所有内容相比,它会使您的断言代码更清晰。
答案 3 :(得分:1)
您可以将自定义JUnit方法规则与自定义断言一起使用。自定义断言可以使用AssertionError
的子类型。这甚至允许您一起使用Junit断言和自定义断言。
示例强>
这是一个使用自定义MyAssert
类的示例,如果断言失败,则抛出MyAssertionError
。 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中,它将如下所示: