Ist es möglich, die Protokollierung (SLF4J + Logback) irgendwie abzufangen und InputStream
über einen JUnit-Testfall eine (oder etwas anderes, das lesbar ist) zu erhalten ...?
75
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
Sie diese Option, wenn Sie keine Ausgabe erhalten. Die Begründung finden Sie im Kommentarbereich.
ILoggingEvent
stattdessen verwendenLoggingEvent
. Das hat bei mir funktioniert.events
nach jeder Testausführung löschen müssen.sample0.xml
. Vergessen Sie nicht, den Appender auf Ihre Implementierung zu ändernDie Slf4j-API bietet keine solche Möglichkeit, aber Logback bietet eine einfache Lösung.
Sie können
ListAppender
Folgendes verwenden : einen Whitebox-Logback-Appender, in dem Protokolleinträge in einpublic List
Feld eingefügt werden, mit dem wir unsere Aussagen treffen können.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"); } }
FooTest-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 Matcher / Assertion-Bibliotheken auch als 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));
quelle
Logger fooLogger = (Logger) LoggerFactory.getLogger(Foo.class);
. Ich benutzeLoggerFactory
vonorg.slf4j.LoggerFactory
undLogger
vonch.qos.logback.classic.Logger
SLF4J
diese Lösung verwenden, wird am Ende eineSLF4J: Class path contains multiple SLF4J bindings.
Warnung ausgegeben, da Sie sowohl SLF4J als auch logback.classicSie können slf4j-test von http://projects.lidalia.org.uk/slf4j-test/ verwenden . Es ersetzt die gesamte logback slf4j-Implementierung durch eine eigene slf4j-API-Implementierung für Tests und bietet eine API, die gegen Protokollierungsereignisse aktiviert werden 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(); } }
quelle
slf4j-test
Paket von Lidalia finden Sie hier: github.com/jaegertracing/jaeger-client-java/pull/378/filesEine einfache Lösung könnte darin bestehen, den Appender mit Mockito zu verspotten (zum Beispiel)
MyClass.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 Assertion, anstatt zurückzukehren,
false
da dadurch Code und (mögliche) Fehler leichter zu lesen sind, aber es funktioniert nicht, wenn Sie mehrere Überprüfungen haben. In diesem Fall müssen Sie zurückgeben undboolean
angeben, ob der Wert wie erwartet ist.quelle
Obwohl das Erstellen eines benutzerdefinierten Logback-Appenders eine gute Lösung ist, ist dies nur der erste Schritt. Am Ende werden Sie slf4j-test entwickeln / neu erfinden. Wenn Sie noch ein bisschen weiter gehen: spf4j-slf4j-test oder andere Frameworks, die ich nicht verwende. Ich weiß es noch nicht.
Sie müssen sich eventuell Gedanken darüber machen, wie viele Ereignisse Sie im Speicher behalten, Unit-Tests nicht bestehen, wenn ein Fehler protokolliert (und nicht bestätigt) wird, Debug-Protokolle bei Testfehlern verfügbar machen usw.
Haftungsausschluss: Ich bin der Autor von spf4j-slf4j-test. Ich habe dieses Backend geschrieben, um spf4j besser testen zu können. Dies ist ein guter Ort, um Beispiele für die Verwendung von spf4j-slf4j-test zu finden. 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.
quelle
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();
Rufen Sie
log.getEvents()
(oder andere benutzerdefinierte Methoden) auf, um die protokollierten Ereignisse zu überprüfen.quelle
import ch.qos.logback.classic.Logger;
stattimport org.slf4j.LoggerFactory;
sonstaddAppender()
nicht verfügbar sein. Ich habe eine Weile gebraucht, um das herauszufinden.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 ein bestimmtes Paket eingefügt werden? Außerdem fügen Sie Ihrer Antwort bitte den Importabschnitt hinzu, da einige der Objekte / Schnittstellen mehrdeutige Namen haben.Ich hatte Probleme beim Testen der Protokollzeile wie: LOGGER.error (Nachricht, Ausnahme) .
Die in http://projects.lidalia.org.uk/slf4j-test/ beschriebene Lösung versucht, die Ausnahme ebenfalls geltend zu machen, und es ist nicht einfach (und meiner Meinung nach wertlos), den Stacktrace neu zu erstellen.
Ich habe auf diese Weise beschlossen:
import org.junit.Test; import org.slf4j.Logger; import uk.org.lidalia.slf4jext.LoggerFactory; import uk.org.lidalia.slf4jtest.TestLogger; import uk.org.lidalia.slf4jtest.TestLoggerFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static uk.org.lidalia.slf4jext.Level.ERROR; import static uk.org.lidalia.slf4jext.Level.INFO; public class Slf4jLoggerTest { private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jLoggerTest.class); private void methodUnderTestInSomeClassInProductionCode() { LOGGER.info("info message"); LOGGER.error("error message"); LOGGER.error("error message with exception", new RuntimeException("this part is not tested")); } private static final TestLogger TEST_LOGGER = TestLoggerFactory.getTestLogger(Slf4jLoggerTest.class); @Test public void testForMethod() throws Exception { // when methodUnderTestInSomeClassInProductionCode(); // then assertThat(TEST_LOGGER.getLoggingEvents()).extracting("level", "message").contains( tuple(INFO, "info message"), tuple(ERROR, "error message"), tuple(ERROR, "error message with exception") ); } }
Dies hat auch den Vorteil, nicht von der Hamcrest Matchers Library abhängig zu sein .
quelle