如何创建不在故障跟踪

时间:2015-07-14 04:31:54

标签: java junit4 assertion java-5

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

4 个答案:

答案 0 :(得分:4)

您是否考虑过将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,仅用作示例。

答案 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中,它将如下所示:

IDE screen shot