2015-07-14 28 views
7

Chciałbym dodać niestandardowe asercje do naszej bazy kodu, które poprawnie ukrywają się przed śledzeniem awarii. Wiem, jak napisać publiczną statyczną metodę, którą ktoś może statycznie importować. Wiem, jak używać starych twierdzeń lub wyrzucać nowe AssertionError.Tworzenie niestandardowych asercji JUnit4, które nie są wyświetlane w ścieżce niepowodzenia

To, czego nie mogę wymyślić, to zachować nowe niestandardowe asercje z śledzenia awarii. Przyzwyczailiśmy się do pierwszego trafienia w śladzie niepowodzenia NIE będącym samym kodem asercji, ale kodem testowym, który wywołał asercję.

Wiem, że istnieje atrybut filtertrace, który kontroluje filtrowanie stosu, ale nie mogę znaleźć żadnej dobrej dokumentacji, co bym musiał zrobić, aby dodać nowe asercje do filtra.

Przykładem tego, co chcę zrobić:

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 śladu myAssertTrueFailing() pokazuje:

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

Muszę go tylko do pokazu:

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

Odpowiedz

2

Jak wspomniano w artykule another question about cleaning noise from stack traces, filtrowanie klas z poziomu IDE jest prawdopodobnie najłatwiejszym rozwiązaniem. W rzeczywistości, ślady stosu, które pokazałeś w pytaniu, są już filtrowane.

Jeśli naprawdę chciał zrobić to w kodzie, można dodać filtrowanie do niestandardowej klasy twierdzenie coś jak poniżej:

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

nazwa klasy zakrywające „newassertions.MyAssertions” (zakodowane) jest filtrowany ze śledzenia stosu w tym przykładzie. Mechanizm ten oczywiście działałby również w celu filtrowania śladu stosu z AssertionError, który sam tworzysz, a nie tylko tych wzniesionych z innych asercji.

+0

To brakuje niektórych średników na górze, ale wygląda bardzo obiecująco. – CandiedOrange

+0

Re. edytuj komentarz: Darn tych grubych palców gremliny. W każdym razie wygląda to dobrze. Kusi mnie, aby dodać kod konfiguracji, który wyłączałby filtr, który powinien być najczystszym, aby mógł zobaczyć prawdziwy ślad stosu, ale pozostawiłby domyślne rozwiązanie tego kodu. Oznacza to, że jeśli nie otrzymam uprawnień do zmiany konfiguracji, mogę filtrować jeden z naszych pakietów. Wystarczy mieć szansę, aby to zakodować w pracy, aby przetestować go w naszym środowisku. – CandiedOrange

1

Możesz użyć niestandardowej reguły metody JUnit razem z niestandardowymi szablonami. Indentyfikatory niestandardowe mogą pracować z podtypem AssertionError. Umożliwiłoby to nawet używanie asercji Junit i niestandardowych asercji.

Przykład

Poniżej przedstawiono przykład, w którym wykorzystuje własny MyAssert klasę, która generuje MyAssertionError s w przypadku, gdy failes twierdzenia. Reguła JUnit obsługuje MyAssertionError i ukrywa wszelkie szczegóły śledzenia awarii.

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

Stosując ten zwyczaj TestVerifier rządzić swój ślad awarii powiem tylko:

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

w Twoim IDE będzie wyglądać następująco:

IDE screen shot

+0

Nie rozumiem tego przykładu. Nie ma nic niestandardowego w assertTrue(). Pokaż mi myAssertTrue(). – CandiedOrange

+0

@CandiedOrange Przeformułowałem nieco moją poprzednią odpowiedź.W poprzedniej wersji brakowało mi * wymagać * niestandardowych asercji. –

+0

To jest trochę lepsze, ale nadal ma poważny problem w śledzeniu awarii. Z powodu importu, kiedy go uruchomię, "public RuleTest klasy" jest na linii 10. Kiedy sprawdzam (klikam) test junitNotOK, prawidłowo raportuje problem w linii 22, gdzie wywołano assertTrue(). Jednak test myAssertNotOK() zgłasza problem w linii 38. Nie jest to miejsce, gdzie wywołano funkcję myAssertTrue(), tylko tam, gdzie został zgłoszony wyjątek. Muszę wiedzieć, czy został wywołany i potrzebuję linii, która mówi mi, że to pierwsza linia w śledzeniu awarii. – CandiedOrange

4

Czy można uznać za pomocą org.junit.Assert.assertThat z Hamcrest matchers?

W przypadku Hamcrest nie trzeba zmieniać metod asercji, ale zamiast tego zaimplementować własne szablony.Na przykład, aby zweryfikować hasło BCrypt hashującym pasuje do zwykłego hasła, napisać dopasowywania tak:

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

Następnie dodać metodę gdzieś, że można statycznie import:

public class CustomMatchers { 

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

} 

Wreszcie napisać Twój testy tak:

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

    assertThat(hashedPassword, matchesPassword(plainPassword)); 
} 

niezgodność zostanie zalogowany do konsoli tak:

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

Uwaga: BCryptPasswordEncoder pochodzi z Spring Security i jest używany jako przykład.

+0

Nie używamy assertThat, matchers, Hamcrest ani Spring. Jesteśmy na java 5. Tak, wiem. To jest to, z czym mam do czynienia. – CandiedOrange

+0

Którą wersję JUnit używasz? AFAIK, JUnit dodał "assertThat" w wersji 4.4 (od 2007 r.), A także podstawowe korektory z Hamcrest (więc nie będziesz potrzebować dodatkowej biblioteki). Wiosna była tu tylko przykładem, nie będziesz jej potrzebować. – hzpz

+0

Czy sprawdziłeś, która wersja JUnit jest dostępna i czy możesz użyć 'assertThat'? – hzpz

2

Moim rozwiązaniem byłoby również filtr IDE, jak już sugerowali inni. Jeśli wykonasz "zakodowane" rozwiązanie, będzie to mniej możliwe do prześledzenia w procesie automatycznej kompilacji.

W Eclipse możesz otworzyć preferencje i wybrać Java -> JUnit i dodać klasy lub pakiety za pomocą przycisków po prawej stronie.

Ale tylko dla zabawy:


Jeśli naprawdę chcesz to zrobić programowo @ rozwiązanie Gar brzmi całkiem rozsądne. Jednakże, jeśli masz większą liczbę twierdzeń, może to być trochę nużące.

Co można zrobić, to podklasę AssertionError i przefiltrować ślad stosu w jego katalogu głównym.

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 dwie rzeczy tutaj: 1) nazwę klasy z StackTraceElement nigdy nie może być zerowa, więc jej dobrze napisać stała po prawej stronie 2) Jeśli umieścić wszystkie swoje twierdzenia w osobnym opakowaniu ty może również napisać element.getClassName().startsWith("newassertions")

Twoja klasa twierdzenie będzie wtedy wyglądać tak:

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


} 

W ten sposób nie można wywołać metody z Assert ale jeśli piszesz MO w przypadku złożonych stwierdzeń istnieje jednak kilka powodów, aby to zrobić. Zachowałoby to jednak nieco czystszy kod twierdzenia w porównaniu do zawijania wszystkiego w dużych blokach try-catch.