簡體   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