[英]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.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,僅用作示例。
正如另一個關於清除堆棧跟蹤噪聲的問題所述,從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中它將如下所示:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.