Potrzebowałem tego także kilka razy. Poniżej zebrałem małą próbkę, którą chciałbyś dostosować do swoich potrzeb. Zasadniczo tworzysz własne Appender
i dodajesz je do rejestratora, który chcesz. Jeśli chcesz zebrać wszystko, rootger jest dobrym miejscem do rozpoczęcia, ale możesz użyć bardziej szczegółowego, jeśli chcesz. Nie zapomnij usunąć programu dołączającego, gdy skończysz, w przeciwnym razie możesz spowodować wyciek pamięci. Poniżej zrobiłem to w ramach testu, ale setUp
lub@Before
i tearDown
czy @After
może być lepsze miejsca, w zależności od potrzeb.
Ponadto implementacja poniżej gromadzi wszystko List
w pamięci. Jeśli dużo się logujesz, możesz rozważyć dodanie filtra, aby usunąć nudne wpisy lub zapisać dziennik do pliku tymczasowego na dysku (Wskazówka: LoggingEvent
jest Serializable
, więc powinieneś być w stanie po prostu serializować obiekty zdarzeń, jeśli twój komunikat dziennika jest.)
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class MyTest {
@Test
public void test() {
final TestAppender appender = new TestAppender();
final Logger logger = Logger.getRootLogger();
logger.addAppender(appender);
try {
Logger.getLogger(MyTest.class).info("Test");
}
finally {
logger.removeAppender(appender);
}
final List<LoggingEvent> log = appender.getLog();
final LoggingEvent firstLogEntry = log.get(0);
assertThat(firstLogEntry.getLevel(), is(Level.INFO));
assertThat((String) firstLogEntry.getMessage(), is("Test"));
assertThat(firstLogEntry.getLoggerName(), is("MyTest"));
}
}
class TestAppender extends AppenderSkeleton {
private final List<LoggingEvent> log = new ArrayList<LoggingEvent>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(final LoggingEvent loggingEvent) {
log.add(loggingEvent);
}
@Override
public void close() {
}
public List<LoggingEvent> getLog() {
return new ArrayList<LoggingEvent>(log);
}
}
logger.getAllAppenders()
, a następnie przejść i zadzwonićappender.setThreshold(Level.OFF)
do każdego (i zresetować je, gdy skończysz!). Dzięki temu „złe” wiadomości, które próbujesz wygenerować, nie pojawią się w dziennikach testu i nie przestraszą następnego programisty.ListAppender<ILoggingEvent>
zamiast tworzenia własnego niestandardowego programu dołączającego.Logger
sięorg.apache.logging.log4j.core.Logger
(klasy implementacji dla interfejsu) dostaniesz dostęp dosetAppender()/removeAppender()
ponownie.Oto proste i wydajne rozwiązanie Logback.
Nie wymaga dodawania / tworzenia żadnej nowej klasy.
Opiera się on na
ListAppender
: aplikatorze logback whitebox, w którym wpisy dziennika są dodawane wpublic List
polu, którego moglibyśmy użyć do sformułowania naszych twierdzeń.Oto prosty przykład.
Klasa Foo:
Klasa FooTest:
Asercje JUnit nie wydają się zbyt dobrze przystosowane do zapewnienia określonych właściwości elementów listy.
Biblioteki dopasowywania / asercji jako AssertJ lub Hamcrest wydają się do tego lepsze:
W przypadku AssertJ byłoby to:
źródło
mock
klasa, która jest testowana. Musisz utworzyć instancję znew
operatoremBardzo dziękuję za te (zaskakująco) szybkie i pomocne odpowiedzi; postawili mnie na właściwej drodze do mojego rozwiązania.
Baza kodów, w której chcę tego użyć, używa java.util.logging jako mechanizmu rejestrującego i nie czuję się wystarczająco dobrze w tych kodach, aby całkowicie zmienić to na log4j lub logger interfejsy / fasady. Ale w oparciu o te sugestie „zhakowałem” rozszerzenie julhandlera i działa to jako uczta.
Poniżej znajduje się krótkie streszczenie. Przedłuż
java.util.logging.Handler
:Oczywiście możesz przechowywać tyle, ile chcesz / chcesz / potrzebujesz z
LogRecord
, lub wepchnąć je wszystkie do stosu, aż pojawi się przepełnienie.Przygotowując się do testu junit, tworzysz
java.util.logging.Logger
i dodajeszLogHandler
do niego takie nowe :Wezwanie
setUseParentHandlers()
to ucisza normalne moduły obsługi, aby (dla tego testu testowego junit) nie było niepotrzebnego rejestrowania. Wykonaj wszystko, czego wymaga testowany kod, aby użyć tego programu rejestrującego, uruchom test i assertEquality:(Oczywiście, dużą część tej pracy przeniosłeś na
@Before
metodę i wprowadziłeś inne ulepszenia, ale to zaśmieciłoby tę prezentację.)źródło
Inną opcją jest wyśmiewanie programu Appender i sprawdzenie, czy wiadomość została zalogowana do tego programu. Przykład dla Log4j 1.2.x i mockito:
źródło
Skutecznie testujesz efekt uboczny klasy zależnej. W przypadku testów jednostkowych wystarczy to sprawdzić
został wywołany z poprawnym parametrem. Dlatego używaj frameworku do emulacji programu rejestrującego, który pozwoli ci przetestować zachowanie własnej klasy.
źródło
Wyśmiewanie jest tutaj opcją, chociaż byłoby trudne, ponieważ rejestratory są zwykle prywatnymi statycznymi końcami - więc ustawienie fałszywego rejestratora nie byłoby bułką z masłem lub wymagałoby modyfikacji testowanej klasy.
Możesz utworzyć niestandardowy program dołączający (lub jakkolwiek się nazywa) i zarejestrować go - albo za pomocą pliku konfiguracyjnego tylko do testów, albo w środowisku wykonawczym (w pewien sposób, zależnie od środowiska rejestrowania). Następnie możesz pobrać ten moduł dołączający (statycznie, jeśli zadeklarowany w pliku konfiguracyjnym lub przez jego bieżące odniesienie, jeśli podłączasz go do środowiska wykonawczego) i zweryfikować jego zawartość.
źródło
Zainspirowany rozwiązaniem @ RonaldBlaschke, wymyśliłem:
... co pozwala na:
Prawdopodobnie możesz sprawić, że użyje hamcrestu w mądrzejszy sposób, ale na tym zostawiłem.
źródło
W przypadku log4j2 rozwiązanie jest nieco inne, ponieważ AppenderSkeleton nie jest już dostępny. Ponadto użycie Mockito lub podobnej biblioteki do utworzenia Appendera z ArgumentCaptor nie będzie działać, jeśli oczekujesz wielu komunikatów rejestrowania, ponieważ MutableLogEvent jest ponownie wykorzystywany w wielu komunikatach dziennika. Najlepsze rozwiązanie, które znalazłem dla log4j2 to:
źródło
Jak wspomniano od innych, możesz użyć frameworku. Aby to zadziałało, musisz ujawnić program rejestrujący w swojej klasie (chociaż raczej wolałbym, aby pakiet był prywatny, niż tworzenie publicznego setera).
Innym rozwiązaniem jest ręczne utworzenie fałszywego rejestratora. Musisz napisać fałszywy program rejestrujący (więcej kodu urządzenia), ale w tym przypadku wolałbym lepszą czytelność testów względem zapisanego kodu z fałszywego frameworka.
Zrobiłbym coś takiego:
źródło
Łał. Nie jestem pewien, dlaczego to było takie trudne. Odkryłem, że nie mogłem użyć żadnego z powyższych przykładów kodu, ponieważ korzystałem z log4j2 nad slf4j. To jest moje rozwiązanie:
źródło
Oto, co zrobiłem dla logback.
Utworzyłem klasę TestAppender:
Następnie w rodzicu mojej klasy testów jednostek testowych stworzyłem metodę:
Mam plik logback-test.xml zdefiniowany w src / test / resources i dodałem program dołączający test:
i dodał ten program dołączający do programu głównego:
Teraz w moich klasach testowych, które rozciągają się od mojej nadrzędnej klasy testowej, mogę pobrać program dołączający i zarejestrować ostatnią wiadomość oraz zweryfikować wiadomość, poziom, możliwość rzucenia.
źródło
W przypadku Junita 5 (Jupiter) Spring OutputCaptureExtension jest całkiem użyteczny. Jest dostępny od wersji Spring Boot 2.2 i jest dostępny w artefakcie testu rozruchu wiosny .
Przykład (wzięty z javadoc):
źródło
getOut()
lubgetErr()
.Jak dla mnie można uprościć za pomocą testu
JUnit
zMockito
. Proponuję dla niego następujące rozwiązanie:Właśnie dlatego mamy niezłą elastyczność w testach z różną ilością komunikatów
źródło
when(appender.isStarted()).thenReturn(true); when(appender.getName()).thenReturn("Test Appender");
prześlij logger na „org.apache.logging.log4j.core.Logger”, dodaj i zmień LoggingEvent -> LogEventźródło
Interfejs API dla Log4J2 jest nieco inny. Być może korzystasz z jego asynchronicznego programu dołączającego. W tym celu utworzyłem zatrzaskowego programu dołączającego:
Użyj tego w ten sposób:
źródło
Zauważ, że w Log4J 2.x interfejs publiczny
org.apache.logging.log4j.Logger
nie zawierasetAppender()
iremoveAppender()
metod .Ale jeśli nie robisz nic szczególnego, powinieneś być w stanie rzucić to na klasę implementacji
org.apache.logging.log4j.core.Logger
, która ujawnia te metody.Oto przykład z Mockito i AssertJ :
źródło
Innym pomysłem, o którym warto wspomnieć, chociaż jest to starszy temat, jest stworzenie producenta CDI, który wstrzykuje twój rejestrator, aby kpiny stały się łatwe. (I ma to tę zaletę, że nie musi już deklarować „całej instrukcji rejestratora”, ale to nie na temat)
Przykład:
Tworzenie rejestratora do wstrzyknięcia:
Kwalifikator:
Za pomocą rejestratora w kodzie produkcyjnym:
Testowanie rejestratora w kodzie testowym (podając przykład easyMock):
źródło
Używając Jmockit (1.21) mogłem napisać ten prosty test. Test sprawdza, czy określony komunikat o błędzie jest wywoływany tylko raz.
źródło
Wyśmiewanie programu dołączającego może pomóc uchwycić wiersze dziennika. Znajdź próbkę na: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html
źródło
Użyj poniższego kodu. Używam tego samego kodu do wiosennego testu integracji, w którym do logowania używam logowania. Użyj metody assertJobIsScheduled, aby potwierdzić tekst wydrukowany w dzienniku.
źródło
jeśli korzystasz z
java.util.logging.Logger
tego artykułu, może być bardzo pomocny, tworzy nowy moduł obsługi i czyni asercje w dzienniku Dane wyjściowe: http://octodecillion.com/blog/jmockit-test-logging/źródło
Istnieją dwie rzeczy, które możesz próbować przetestować.
Te dwie rzeczy są właściwie różnymi rzeczami, dlatego można je przetestować osobno. Jednak testowanie drugiego (tekstu wiadomości) jest tak problematyczne, że w ogóle nie polecam tego robić. Test tekstu wiadomości będzie ostatecznie polegał na sprawdzeniu, czy jeden ciąg tekstowy (oczekiwany tekst wiadomości) jest taki sam lub może być w prosty sposób uzyskany z ciągu tekstowego użytego w kodzie logowania.
Zauważ, że kod programu (być może implementujący pewną logikę biznesową) bezpośrednio wywołujący interfejs rejestrowania tekstu jest kiepski (ale niestety bardzo powszechny). Kod odpowiedzialny za logikę biznesową decyduje również o niektórych zasadach rejestrowania i treści komunikatów dziennika. Łączy logikę biznesową z kodem interfejsu użytkownika (tak, komunikaty dziennika są częścią interfejsu użytkownika programu). Te rzeczy powinny być oddzielne.
Dlatego zalecam, aby logika biznesowa nie generowała bezpośrednio tekstu komunikatów dziennika. Zamiast tego należy go delegować do obiektu rejestrującego.
implements
interface
interface
.Następnie możesz przetestować, czy klasy logiki biznesowej poprawnie informują interfejs rejestrowania o zdarzeniach, tworząc próbny program rejestrujący, który implementuje wewnętrzny interfejs API rejestrowania, i wykorzystując wstrzykiwanie zależności w fazie konfiguracji testu.
Lubię to:
źródło
To, co zrobiłem, jeśli chcę tylko zobaczyć, że jakiś ciąg został zarejestrowany (w przeciwieństwie do weryfikacji dokładnych instrukcji dziennika, które są po prostu zbyt kruche), to przekierowanie StdOut do bufora, wykonanie zawiera, a następnie zresetowanie StdOut:
źródło
java.util.logging
(chociaż użyłemSystem.setErr(new PrintStream(buffer));
, ponieważ loguje się do stderr), ale to nie działa (bufor pozostaje pusty). jeśli używamSystem.err.println("foo")
bezpośrednio, to działa, więc zakładam, że system rejestrujący zachowuje własne odniesienie do strumienia wyjściowego, z którego pobieraSystem.err
, więc moje wywołanieSystem.setErr(..)
nie ma wpływu na dane wyjściowe dziennika, tak jak dzieje się to po inicjacji systemu dziennika.Odpowiedziałem na podobne pytanie dotyczące log4j, zobacz jak-can-i-test-with-junit-that-a-warning-was-logged-with-log4
Jest to nowszy przykład z Log4j2 (testowany z 2.11.2) i junit 5;
Korzystanie z następujących zależności maven
źródło
Jeśli używasz log4j2, rozwiązanie z https://www.dontpanicblog.co.uk/2018/04/29/test-log4j2-with-junit/ pozwoliło mi stwierdzić, że wiadomości zostały zarejestrowane.
Rozwiązanie wygląda następująco:
Zdefiniuj program dołączający log4j jako regułę ExternalResource
Zdefiniuj test, który korzysta z reguły ExternalResource
Nie zapomnij mieć log4j2.xml jako części src / test / resources
źródło