Ist es möglich das Logging (SLF4J + Logback) irgendwie abzufangen und an eine InputStream
(oder etwas anderes, das lesbar ist) über einen JUnit-Testfall …?
Wie kann man die SLF4J-Protokollierung (mit Logback) über einen JUnit-Test abfangen?
Carlspring
davidxxx
Die Slf4j-API bietet einen solchen Weg nicht, aber Logback bietet eine einfache Lösung.
Sie können verwenden ListAppender
: ein Whitebox-Logback-Appender, in dem Protokolleinträge in a hinzugefügt werden public List
Feld, das wir verwenden könnten, um unsere Behauptungen aufzustellen.
Hier ist ein einfaches Beispiel.
Foo-Klasse:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Foo {
static final Logger LOGGER = LoggerFactory.getLogger(Foo .class);
public void doThat() {
logger.info("start");
//...
logger.info("finish");
}
}
FootTest-Klasse:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
public class FooTest {
@Test
void doThat() throws Exception {
// get Logback Logger
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
// create and start a ListAppender
ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
listAppender.start();
// add the appender to the logger
fooLogger.addAppender(listAppender);
// call method under test
Foo foo = new Foo();
foo.doThat();
// JUnit assertions
List<ILoggingEvent> logsList = listAppender.list;
assertEquals("start", logsList.get(0)
.getMessage());
assertEquals(Level.INFO, logsList.get(0)
.getLevel());
assertEquals("finish", logsList.get(1)
.getMessage());
assertEquals(Level.INFO, logsList.get(1)
.getLevel());
}
}
Sie können auch Matcher-/Assertion-Bibliotheken wie AssertJ oder Hamcrest verwenden.
Mit AssertJ wäre es:
import org.assertj.core.api.Assertions;
Assertions.assertThat(listAppender.list)
.extracting(ILoggingEvent::getFormattedMessage, ILoggingEvent::getLevel)
.containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));
-
Ich danke dir sehr! Das ist genau das, wonach ich gesucht habe!
– Ol
16. November 2018 um 10:12 Uhr
-
Ich erhalte ClassCastException für
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
. ich benutzeLoggerFactory
vonorg.slf4j.LoggerFactory
undLogger
vonch.qos.logback.classic.Logger
– anstellen
27. Mai 2019 um 9:48 Uhr
-
@Hiren Wenn Sie JUnit 5 verwenden, stellen Sie sicher, dass Sie nicht versehentlich importieren
org.junit.platform.commons.logging.LoggerFactory
was mir passiert ist.– Denis Washington
5. Juli 2019 um 8:33 Uhr
-
Beachten Sie, dass Sie anstelle von ILoggingEvent::getMessage ILoggingEvent::getFormattedMessage verwenden sollten, wenn Ihr Protokoll einen Parameterwert enthält. Andernfalls schlägt Ihr Assertion fehl, da der Wert fehlt.
– Robert Maurer
30. Juli 2019 um 15:10 Uhr
-
wenn Sie verwenden
SLF4J
Diese Lösung wird am Ende erhöhenSLF4J: Class path contains multiple SLF4J bindings.
Warnung, da Sie sowohl SLF4J als auch logback.classic haben– Ghilteras
24. Oktober 2019 um 18:52 Uhr
Evgeniy Dorofeev
Sie können einen benutzerdefinierten Appender erstellen
public class TestAppender extends AppenderBase<LoggingEvent> {
static List<LoggingEvent> events = new ArrayList<>();
@Override
protected void append(LoggingEvent e) {
events.add(e);
}
}
und konfigurieren Sie logback-test.xml, um es zu verwenden. Jetzt können wir die Protokollierungsereignisse aus unserem Test überprüfen:
@Test
public void test() {
...
Assert.assertEquals(1, TestAppender.events.size());
...
}
HINWEIS: Verwenden ILoggingEvent
Wenn Sie keine Ausgabe erhalten, lesen Sie die Begründung im Kommentarbereich.
-
Beachten Sie, dass Sie logback classic + slf4j verwenden müssen
ILoggingEvent
Anstatt vonLoggingEvent
. Das hat bei mir funktioniert.– etech
27. Mai 2016 um 20:52 Uhr
-
@Evgeniy Dorofeev Könnten Sie bitte zeigen, wie man logback-test.xml konfiguriert?
– hipokito
16. November 2016 um 17:16 Uhr
-
Ich nehme an, Sie müssen löschen
events
nach jeder Testausführung.– Andrii Karaiwanskyi
17. Februar 2017 um 10:28 Uhr
-
@hipokito Sie können das erwähnte verwenden [here] (logback.qos.ch/manual/configuration.html) in
sample0.xml
. Vergessen Sie nicht, den Appender an Ihre Implementierung anzupassen– Codierungs_Idiot
6. Juli 2017 um 22:00 Uhr
-
@EvgeniyDorofeev kannst du mir dabei helfen? stackoverflow.com/questions/48551083/…
– Bhavya Arora
31. Januar 2018 um 21:57 Uhr
Oleg Majewski
Sie können slf4j-test von verwenden http://projects.lidalia.org.uk/slf4j-test/. Es ersetzt die gesamte logback slf4j-Implementierung durch seine eigene slf4j-API-Implementierung für Tests und stellt eine API bereit, die sich gegen Protokollierungsereignisse behaupten kann.
Beispiel:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExcludes>ch.qos.logback:logback-classic</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>
public class Slf4jUser {
private static final Logger logger = LoggerFactory.getLogger(Slf4jUser.class);
public void aMethodThatLogs() {
logger.info("Hello World!");
}
}
public class Slf4jUserTest {
Slf4jUser slf4jUser = new Slf4jUser();
TestLogger logger = TestLoggerFactory.getTestLogger(Slf4jUser.class);
@Test
public void aMethodThatLogsLogsAsExpected() {
slf4jUser.aMethodThatLogs();
assertThat(logger.getLoggingEvents(), is(asList(info("Hello World!"))));
}
@After
public void clearLoggers() {
TestLoggerFactory.clear();
}
}
-
Danke für diese alternative Antwort! Es sieht sehr nützlich aus und ich werde diesen Ansatz höchstwahrscheinlich auch in Zukunft ausprobieren! Leider habe ich bereits die andere Antwort akzeptiert, die auch richtig ist.
– Karlsfeder
2. März 2016 um 10:51 Uhr
-
Vollständiges Beispiel mit Lidalia
slf4j-test
Paket finden Sie hier: github.com/jaegertracing/jaeger-client-java/pull/378/files– Debosmit Ray
6. April 2018 um 1:58 Uhr
-
Diese Lösung funktioniert gut, wenn Sie Spring nicht verwenden. Wenn Sie Spring verwenden, wird eine Ausnahme “Klasse nicht gefunden” ausgelöst (JoranConfigurator).
– Jesu H
2. Januar 2020 um 18:54 Uhr
Epoxid
Mit JUnit5
private ListAppender<ILoggingEvent> logWatcher;
@BeforeEach
void setup() {
logWatcher = new ListAppender<>();
logWatcher.start();
((Logger) LoggerFactory.getLogger(MyClass.class)).addAppender(logWatcher);
}
Notiz: MeineKlasse.Klasse – sollte Ihre Prod-Klasse sein, von der Sie die Protokollausgabe erwarten
use: (AssertJ-Beispiel)
@Test
void myMethod_logs2Messages() {
...
int logSize = logWatcher.list.size();
assertThat(logWatcher.list.get(logSize - 2).getFormattedMessage()).contains("EXPECTED MSG 1");
assertThat(logWatcher.list.get(logSize - 1).getFormattedMessage()).contains("EXPECTED MSG 2");
}
zerstören:
Detach wird empfohlen, für eine bessere Leistung:
@AfterEach
void teardown() {
((Logger) LoggerFactory.getLogger(MyClass.class)).detachAndStopAllAppenders();
}
Importe:
import org.slf4j.LoggerFactory;
import ch.qos.logback.core.read.ListAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.Logger;
Credits an: Antwort von @ davidxxx. Sehen Sie es für import ch.qos.logback...
Details: https://stackoverflow.com/a/52229629/601844
snovelli
Eine einfache Lösung könnte sein, den Appender mit Mockito zu verspotten (zum Beispiel)
MeineKlasse.java
@Slf4j
class MyClass {
public void doSomething() {
log.info("I'm on it!");
}
}
MyClassTest.java
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {
@Mock private Appender<ILoggingEvent> mockAppender;
private MyClass sut = new MyClass();
@Before
public void setUp() {
Logger logger = (Logger) LoggerFactory.getLogger(MyClass.class.getName());
logger.addAppender(mockAppender);
}
@Test
public void shouldLogInCaseOfError() {
sut.doSomething();
verify(mockAppender).doAppend(ArgumentMatchers.argThat(argument -> {
assertThat(argument.getMessage(), containsString("I'm on it!"));
assertThat(argument.getLevel(), is(Level.INFO));
return true;
}));
}
}
HINWEIS: Ich verwende eine Behauptung, anstatt zurückzukehren false
da es den Code und (mögliche) Fehler leichter lesbar macht, aber es funktioniert nicht, wenn Sie mehrere Überprüfungen haben. In diesem Fall müssen Sie zurückkehren boolean
zeigt an, ob der Wert wie erwartet ist.
-
funktioniert das, wenn ich die lombok.extern.slf4j-Anmerkungen wie Slf4j verwende? Wie verspottet oder spioniert man den Logger aus, wenn er nicht einmal ein Objekt in meinen Klassen ist? dh log.error wird nur verwendet, indem die Anmerkung Slf4j in meiner Klasse bereitgestellt wird …
– enth
1. April 2020 um 22:24 Uhr
-
@ennth Es sollte funktionieren, weil Sie den Schein mit der statischen Methode LoggerFactory.getLogger().addAppender(mockAppender) injizieren. Das funktioniert genauso, wenn Sie den Logger mit Lombok erstellen
– Snovelli
3. April 2020 um 12:22 Uhr
-
Habe das gleiche nicht funktionierende Problem. Was sind die ‘Importe’ für die Klassen Logger und LoggerFactory? Warum werden die statischen Importe aufgelistet und die anderen nicht?
– Dirk Schumacher
28. Mai 2020 um 7:40 Uhr
Benutzer2179737
Obwohl das Erstellen eines benutzerdefinierten Logback-Appenders eine gute Lösung ist, ist dies nur der erste Schritt, Sie werden schließlich entwickeln/neu erfinden slf4j-testund wenn du etwas weiter gehst: spf4j-slf4j-test oder andere Frameworks, die ich noch nicht kenne.
Sie müssen sich schließlich Gedanken darüber machen, wie viele Ereignisse Sie im Speicher behalten, Unit-Tests fehlschlagen, wenn ein Fehler protokolliert (und nicht bestätigt) wird, Debug-Protokolle bei fehlgeschlagenen Tests verfügbar machen usw.
Haftungsausschluss: Ich bin der Autor von spf4j-slf4j-test, ich habe dieses Backend geschrieben, um besser testen zu können spf4j, das ein guter Ort ist, um nach Beispielen für die Verwendung von spf4j-slf4j-test zu suchen. Einer der Hauptvorteile, die ich erzielt habe, war die Reduzierung meiner Build-Ausgabe (die bei Travis begrenzt ist), während ich immer noch alle Details habe, die ich brauche, wenn ein Fehler auftritt.
-
funktioniert das, wenn ich die lombok.extern.slf4j-Anmerkungen wie Slf4j verwende? Wie verspottet oder spioniert man den Logger aus, wenn er nicht einmal ein Objekt in meinen Klassen ist? dh log.error wird nur verwendet, indem die Anmerkung Slf4j in meiner Klasse bereitgestellt wird …
– enth
1. April 2020 um 22:24 Uhr
-
@ennth Es sollte funktionieren, weil Sie den Schein mit der statischen Methode LoggerFactory.getLogger().addAppender(mockAppender) injizieren. Das funktioniert genauso, wenn Sie den Logger mit Lombok erstellen
– Snovelli
3. April 2020 um 12:22 Uhr
-
Habe das gleiche nicht funktionierende Problem. Was sind die ‘Importe’ für die Klassen Logger und LoggerFactory? Warum werden die statischen Importe aufgelistet und die anderen nicht?
– Dirk Schumacher
28. Mai 2020 um 7:40 Uhr
oberlies
Ich würde eine einfache, wiederverwendbare Spionageimplementierung empfehlen, die als JUnit-Regel in einen Test aufgenommen werden kann:
public final class LogSpy extends ExternalResource {
private Logger logger;
private ListAppender<ILoggingEvent> appender;
@Override
protected void before() {
appender = new ListAppender<>();
logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); // cast from facade (SLF4J) to implementation class (logback)
logger.addAppender(appender);
appender.start();
}
@Override
protected void after() {
logger.detachAppender(appender);
}
public List<ILoggingEvent> getEvents() {
if (appender == null) {
throw new UnexpectedTestError("LogSpy needs to be annotated with @Rule");
}
return appender.list;
}
}
In Ihrem Test würden Sie den Spion folgendermaßen aktivieren:
@Rule
public LogSpy log = new LogSpy();
Anruf log.getEvents()
(oder andere benutzerdefinierte Methoden), um die protokollierten Ereignisse zu überprüfen.
-
Damit dies funktioniert, müssen Sie
import ch.qos.logback.classic.Logger;
Anstatt vonimport org.slf4j.LoggerFactory;
AndernfallsaddAppender()
ist nicht verfügbar. Ich habe eine Weile gebraucht, um das herauszufinden.– Urs Beeli
1. Oktober 2019 um 14:13 Uhr
-
Funktioniert nicht für mich. Es sieht so aus, als ob die Regel nicht richtig angewendet wird: Beim Debuggen habe ich festgestellt
before()
undafter()
nie erreicht, daher wird der Appender nie erstellt/angehängt und der UnexpectedTestError wird ausgelöst. Irgendwelche Ideen, was ich falsch mache? Muss die Regel in einem bestimmten Paket abgelegt werden? Bitte fügen Sie Ihrer Antwort auch den Importabschnitt hinzu, da einige der Objekte/Schnittstellen mehrdeutige Namen haben.– Philzen
23. Februar 2020 um 2:26 Uhr