简体   繁体   English

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

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

I'd like to add some custom assertions to our code base that properly hide from the failure trace. 我想在我们的代码库中添加一些自定义断言,以正确隐藏故障跟踪。 I know how to write a public static method that someone can statically import. 我知道如何编写一个可以静态导入的公共静态方法。 I know how to reuse old assertions or throw a new AssertionError . 我知道如何重用旧的断言或抛出一个新的AssertionError

What I can't figure out how to do is keep the new custom assertions out of the Failure Trace. 我无法弄清楚如何做的是将新的自定义断言保留在Failure Trace之外。 We're used to the first hit in the failure trace NOT being the assertion code itself but the test code that called the assertion. 我们已经习惯了故障跟踪中的第一个命中,而不是断言代码本身,而是调用断言的测试代码。

I know there is a filtertrace attribute that controls filtering the stack but I can't find any good documentation of what I'd have to do to add the new assertions to the filter. 我知道有一个filtertrace属性控制过滤堆栈,但我找不到任何关于将新断言添加到过滤器我必须做什么的好文档。

An example of what I want to do: 我想做的一个例子:

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);
    }
}

Failure Trace of myAssertTrueFailing() shows: myAssertTrueFailing()的失败跟踪显示:

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

I need it to only show: 我需要它只显示:

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

Have you considered using org.junit.Assert.assertThat with Hamcrest matchers? 您是否考虑过使用org.junit.Assert.assertThatHamcrest匹配器一起使用?

With Hamcrest, you wouldn't need to change the assertion methods, but instead implement your own matchers. 使用Hamcrest,您不需要更改断言方法,而是实现自己的匹配器。 For example, to verify a BCrypt-hashed password matches the plain password, write a matcher like this: 例如,要验证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);
    }
}

Next, add a method somewhere that you can statically import: 接下来,添加一个可以静态导入的方法:

public class CustomMatchers {

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

}

Finally, write your test like this: 最后,像这样编写测试:

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

    assertThat(hashedPassword, matchesPassword(plainPassword));
}

A mismatch will be logged to the console like this: 不匹配将记录到控制台,如下所示:

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)
    ...

Note: BCryptPasswordEncoder is from Spring Security and just used as an example. 注意:BCryptPasswordEncoder来自Spring Security,仅用作示例。

As mentioned in another question about cleaning noise from stack traces , filtering classes from within your IDE is probably the easiest solution. 正如另一个关于清除堆栈跟踪噪声的问题所述,从IDE中过滤类可能是最简单的解决方案。 In fact, the stack traces you've shown in your question are already filtered. 实际上,您在问题中显示的堆栈跟踪已经过滤。

If you really wanted to do this in code, you could add filtering to your custom assertion class something like below: 如果您真的想在代码中执行此操作,则可以向自定义断言类添加过滤,如下所示:

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]));
        }
    }
}

The name of the enclosing class 'newassertions.MyAssertions' (hard-coded) is filtered from the stack trace in this example. 在此示例中,从堆栈跟踪中过滤了封闭类“newassertions.MyAssertions”(硬编码)的名称。 This mechanism would obviously also work to filter the stack trace from an AssertionError that you create yourself and not just those raised from other assertions. 显然,这种机制也可以用于从您自己创建的AssertionError中过滤堆栈跟踪,而不仅仅是从其他断言中提取的那些。

My go-with solution would also be an IDE filter as others already suggested. 我的合作解决方案也将是其他人已经建议的IDE过滤器。 If you do a "hard-coded" solution this will be less traceable in an automated build process. 如果您执行“硬编码”解决方案,则在自动构建过程中将无法追踪。

In Eclipse you can open the preferences and select Java -> JUnit and add classes or packages using the buttons on the right. 在Eclipse中,您可以打开首选项并选择Java - > JUnit,并使用右侧的按钮添加类或包。

But just for the fun of it: 但只是为了它的乐趣:


If you really want to do it programmatically @gar's solution sounds quite reasonable. 如果你真的想以编程方式做到这一点@ gar的解决方案听起来很合理。 However, if you have a bigger amount of assertions this might be a bit tedious. 但是,如果你有更多的断言,这可能有点乏味。

What you could also do is to subclass AssertionError and filter the stacktrace at its root. 您还可以做的是继承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]));
    }

}

Note two things here: 1) the class name of a StackTraceElement can never be null so its fine to write the constant on the right side 2) if you put all your assertions in a separate package you could also write element.getClassName().startsWith("newassertions") 请注意以下两点:1) StackTraceElement的类名永远不能为null,因此可以在右侧编写常量2)如果将所有断言放在单独的包中,也可以编写element.getClassName().startsWith("newassertions")

Your assertion class would then look like this: 你的断言类看起来像这样:

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);
    }


}

This way you could not call the methods from Assert but if you write more complex assertions there are few reasons to do this anyway. 这样你就无法从Assert调用方法,但是如果你编写更复杂的断言,那么无论如何都没有理由这样做。 However, it would keep your assertion code a bit cleaner compared to wrapping everything in big try-catch blocks. 但是,与在大型try-catch块中包装所有内容相比,它会使您的断言代码更清晰。

You can use a custom JUnit method rule together with custom asserts. 您可以将自定义JUnit方法规则与自定义断言一起使用。 The custom asserts can work with a subtype of AssertionError . 自定义断言可以使用AssertionError的子类型。 This would even allow you to use Junit assertions and the custom assertions together. 这甚至允许您一起使用Junit断言和自定义断言。

Example

Here is an example that uses a custom MyAssert class that throws MyAssertionError s in case an assertion failes. 下面是使用自定义的例子MyAssert抛出类MyAssertionError S IN的情况下断言failes。 The JUnit rule handles MyAssertionError and hides any details of the failure trace. 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); }
    }
  }
}

Using this custom TestVerifier rule your failure trace will only say: 使用此自定义TestVerifier规则,您的故障跟踪仅会说:

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

In your IDE it will look like this: 在您的IDE中它将如下所示:

IDE屏幕截图

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

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