Cómo hacer una afirmación JUnit en un mensaje en un registrador


Tengo un código bajo prueba que llama a un registrador de Java para informar de su estado. En el código de prueba de JUnit, me gustaría verificar que la entrada de registro correcta se hizo en este registrador. Algo en las siguientes líneas:

methodUnderTest(bool x){
    if(x)
        logger.info("x happened")
}

@Test tester(){
    // perhaps setup a logger first.
    methodUnderTest(true);
    assertXXXXXX(loggedLevel(),Level.INFO);
}

Supongo que esto podría hacerse con un registrador especialmente adaptado (o manejador, o formateador), pero preferiría reutilizar una solución que ya existe. (Y, para ser honesto, no está claro para mí cómo llegar al LogRecord de un registrador, pero supongamos que eso es posible.)

Author: approxiblue, 2009-12-01

20 answers

He necesitado esto varias veces. He reunido una pequeña muestra a continuación, que le gustaría ajustar a sus necesidades. Básicamente, creas tu propio Appender y lo agregas al registrador que deseas. Si desea recopilar todo, el registrador raíz es un buen lugar para comenzar, pero puede usar uno más específico si lo desea. No se olvide de quitar el Appender cuando haya terminado, de lo contrario podría crear una fuga de memoria. A continuación lo he hecho dentro de la prueba, pero setUp o @Before y tearDown o @After podrían ser mejores lugares, dependiendo de sus necesidades.

Además, la implementación a continuación recopila todo en un List en memoria. Si está registrando mucho, podría considerar agregar un filtro para soltar entradas aburridas, o para escribir el registro en un archivo temporal en el disco (Sugerencia: LoggingEvent es Serializable, por lo que debería poder serializar los objetos de evento, si su mensaje de registro lo es.)

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);
    }
}
 117
Author: Ronald Blaschke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-01 19:20:40

Muchas gracias por estas (sorprendentemente) respuestas rápidas y útiles; me pusieron en el camino correcto para mi solución.

El código base donde quiero usar esto, usa java.útil.el registro como su mecanismo de registro, y no me siento lo suficientemente en casa en esos códigos para cambiarlo completamente a log4j o a las interfaces/fachadas del registrador. Pero basado en estas sugerencias, 'hackeado' una extensión j. u. l. handler y que funciona como un regalo.

A continuación se presenta un breve resumen. Extender java.util.logging.Handler:

class LogHandler extends Handler
{
    Level lastLevel = Level.FINEST;

    public Level  checkLevel() {
        return lastLevel;
    }    

    public void publish(LogRecord record) {
        lastLevel = record.getLevel();
    }

    public void close(){}
    public void flush(){}
}

Obviamente, puedes almacenar todo lo que quieras/quieras/necesites de LogRecord, o meterlos todos en una pila hasta que obtengas un desbordamiento.

En la preparación para el junit-test, se crea un java.util.logging.Logger y se añade un nuevo LogHandler:

@Test tester() {
    Logger logger = Logger.getLogger("my junit-test logger");
    LogHandler handler = new LogHandler();
    handler.setLevel(Level.ALL);
    logger.setUseParentHandlers(false);
    logger.addHandler(handler);
    logger.setLevel(Level.ALL);

La llamada a setUseParentHandlers() es silenciar a los controladores normales, de modo que (para esta ejecución de junit-test) no ocurra ningún registro innecesario. Haga lo que su código bajo prueba necesita para usar este registrador, ejecute la prueba y Asertequality:

    libraryUnderTest.setLogger(logger);
    methodUnderTest(true);  // see original question.
    assertEquals("Log level as expected?", Level.INFO, handler.checkLevel() );
}

(Por supuesto, movería gran parte de este trabajo a un método @Before y haría otras mejoras variadas, pero eso desordenaría esta presentación.)

 30
Author: Jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-02 18:14:51

Efectivamente está probando un efecto secundario de una clase dependiente. Para las pruebas unitarias solo necesita verificar que

logger.info()

Fue llamado con el parámetro correcto. Por lo tanto, use un marco de burla para emular a logger y eso le permitirá probar el comportamiento de su propia clase.

 16
Author: djna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-18 18:53:02

Burlarse es una opción aquí, aunque sería difícil, porque los registradores generalmente son estáticos finales privados, por lo que configurar un registrador simulado no sería pan comido, o requeriría una modificación de la clase bajo prueba.

Puede crear un Appender personalizado (o como se llame) y registrarlo, ya sea a través de un archivo de configuración de solo prueba o en tiempo de ejecución (de alguna manera, dependiendo del marco de registro). Y luego puede obtener ese appender (ya sea estáticamente, si se declara en configuración archivo, o por su referencia actual, si lo está conectando en tiempo de ejecución), y verifique su contenido.

 10
Author: Bozho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-01 17:58:07

Otra opción es simular Appender y verificar si el mensaje se registró en este appender. Ejemplo para Log4j 1.2.x y mockito:

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

public class MyTest {

    private final Appender appender = mock(Appender.class);
    private final Logger logger = Logger.getRootLogger();

    @Before
    public void setup() {
        logger.addAppender(appender);
    }

    @Test
    public void test() {
        // when
        Logger.getLogger(MyTest.class).info("Test");

        // then
        ArgumentCaptor<LoggingEvent> argument = ArgumentCaptor.forClass(LoggingEvent.class);
        verify(appender).doAppend(argument.capture());
        assertEquals(Level.INFO, argument.getValue().getLevel());
        assertEquals("Test", argument.getValue().getMessage());
        assertEquals("MyTest", argument.getValue().getLoggerName());
    }

    @After
    public void cleanup() {
        logger.removeAppender(appender);
    }
}
 10
Author: Marcin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-23 20:14:49

Esto es lo que hice para logback.

He creado una clase TestAppender:

public class TestAppender extends AppenderBase<ILoggingEvent> {

    private Stack<ILoggingEvent> events = new Stack<ILoggingEvent>();

    @Override
    protected void append(ILoggingEvent event) {
        events.add(event);
    }

    public void clear() {
        events.clear();
    }

    public ILoggingEvent getLastEvent() {
        return events.pop();
    }
}

Luego en el padre de mi clase testng unit creé un método:

protected TestAppender testAppender;

@BeforeClass
public void setupLogsForTesting() {
    Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    testAppender = (TestAppender)root.getAppender("TEST");
    if (testAppender != null) {
        testAppender.clear();
    }
}

Tengo una prueba de logback.archivo xml definido en src/test / resources y he añadido un appender de prueba:

<appender name="TEST" class="com.intuit.icn.TestAppender">
    <encoder>
        <pattern>%m%n</pattern>
    </encoder>
</appender>

Y agregó este anexo al anexo raíz:

<root>
    <level value="error" />
    <appender-ref ref="STDOUT" />
    <appender-ref ref="TEST" />
</root>

Ahora en mis clases de prueba que se extienden desde mi clase de prueba padre puedo obtener el appender y obtener el último mensaje registrado y verificar el mensaje, el nivel, el lanzable.

ILoggingEvent lastEvent = testAppender.getLastEvent();
assertEquals(lastEvent.getMessage(), "...");
assertEquals(lastEvent.getLevel(), Level.WARN);
assertEquals(lastEvent.getThrowableProxy().getMessage(), "...");
 4
Author: kfox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-03 05:39:15

Inspirado por la solución de @RonaldBlaschke, se me ocurrió esto:

public class Log4JTester extends ExternalResource {
    TestAppender appender;

    @Override
    protected void before() {
        appender = new TestAppender();
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.addAppender(appender);
    }

    @Override
    protected void after() {
        final Logger rootLogger = Logger.getRootLogger();
        rootLogger.removeAppender(appender);
    }

    public void assertLogged(Matcher<String> matcher) {
        for(LoggingEvent event : appender.events) {
            if(matcher.matches(event.getMessage())) {
                return;
            }
        }
        fail("No event matches " + matcher);
    }

    private static class TestAppender extends AppenderSkeleton {

        List<LoggingEvent> events = new ArrayList<LoggingEvent>();

        @Override
        protected void append(LoggingEvent event) {
            events.add(event);
        }

        @Override
        public void close() {

        }

        @Override
        public boolean requiresLayout() {
            return false;
        }
    }

}

... lo que le permite hacer:

@Rule public Log4JTester logTest = new Log4JTester();

@Test
public void testFoo() {
     user.setStatus(Status.PREMIUM);
     logTest.assertLogged(
        stringContains("Note added to account: premium customer"));
}

Probablemente podrías hacer que use hamcrest de una manera más inteligente, pero lo he dejado en esto.

 4
Author: slim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-27 15:47:16

Como se mencionó en los otros, podría usar un framework de burla. Para que esto funcione tienes que exponer el logger en tu clase (aunque probablemente preferiría hacerlo un paquete privado en lugar de crear un setter público).

La otra solución es crear un logger falso a mano. Tienes que escribir el logger falso (más código de accesorio), pero en este caso preferiría la legibilidad mejorada de las pruebas contra el código guardado del marco de burla.

Me gustaría haga algo como esto:

class FakeLogger implements ILogger {
    public List<String> infos = new ArrayList<String>();
    public List<String> errors = new ArrayList<String>();

    public void info(String message) {
        infos.add(message);
    }

    public void error(String message) {
        errors.add(message);
    }
}

class TestMyClass {
    private MyClass myClass;        
    private FakeLogger logger;        

    @Before
    public void setUp() throws Exception {
        myClass = new MyClass();
        logger = new FakeLogger();
        myClass.logger = logger;
    }

    @Test
    public void testMyMethod() {
        myClass.myMethod(true);

        assertEquals(1, logger.infos.size());
    }
}
 4
Author: Arne Deutsch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-08 15:11:47

En cuanto a mí, puedes simplificar tu prueba usando JUnit con Mockito. Propongo la siguiente solución para ello:

import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.times;

@RunWith(MockitoJUnitRunner.class)
public class MyLogTest {
    private static final String FIRST_MESSAGE = "First message";
    private static final String SECOND_MESSAGE = "Second message";
    @Mock private Appender appender;
    @Captor private ArgumentCaptor<LoggingEvent> captor;
    @InjectMocks private MyLog;

    @Before
    public void setUp() {
        LogManager.getRootLogger().addAppender(appender);
    }

    @After
    public void tearDown() {
        LogManager.getRootLogger().removeAppender(appender);
    }

    @Test
    public void shouldLogExactlyTwoMessages() {
        testedClass.foo();

        then(appender).should(times(2)).doAppend(captor.capture());
        List<LoggingEvent> loggingEvents = captor.getAllValues();
        assertThat(loggingEvents).extracting("level", "renderedMessage").containsExactly(
                tuple(Level.INFO, FIRST_MESSAGE)
                tuple(Level.INFO, SECOND_MESSAGE)
        );
    }
}

Es por eso que tenemos buena flexibilidad {[7] } para pruebas con diferente cantidad de mensajes

 2
Author: Dmytro Melnychuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-19 18:05:17

Wow. No estoy seguro de por qué esto fue tan difícil. Descubrí que no podía usar ninguno de los ejemplos de código anteriores porque estaba usando log4j2 sobre slf4j. Esta es mi solución:

public class SpecialLogServiceTest {

  @Mock
  private Appender appender;

  @Captor
  private ArgumentCaptor<LogEvent> captor;

  @InjectMocks
  private SpecialLogService specialLogService;

  private LoggerConfig loggerConfig;

  @Before
  public void setUp() {
    // prepare the appender so Log4j likes it
    when(appender.getName()).thenReturn("MockAppender");
    when(appender.isStarted()).thenReturn(true);
    when(appender.isStopped()).thenReturn(false);

    final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    final Configuration config = ctx.getConfiguration();
    loggerConfig = config.getLoggerConfig("org.example.SpecialLogService");
    loggerConfig.addAppender(appender, AuditLogCRUDService.LEVEL_AUDIT, null);
  }

  @After
  public void tearDown() {
    loggerConfig.removeAppender("MockAppender");
  }

  @Test
  public void writeLog_shouldCreateCorrectLogMessage() throws Exception {
    SpecialLog specialLog = new SpecialLogBuilder().build();
    String expectedLog = "this is my log message";

    specialLogService.writeLog(specialLog);

    verify(appender).append(captor.capture());
    assertThat(captor.getAllValues().size(), is(1));
    assertThat(captor.getAllValues().get(0).getMessage().toString(), is(expectedLog));
  }
}
 2
Author: Dagmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-10 08:24:20

Para log4j2 la solución es ligeramente diferente porque AppenderSkeleton ya no está disponible. Además, usar Mockito o una biblioteca similar para crear un Appender con un ArgumentCaptor no funcionará si espera varios mensajes de registro porque el MutableLogEvent se reutiliza en varios mensajes de registro. La mejor solución que encontré para log4j2 es:

private static MockedAppender mockedAppender;
private static Logger logger;

@Before
public void setup() {
    mockedAppender.message.clear();
}

/**
 * For some reason mvn test will not work if this is @Before, but in eclipse it works! As a
 * result, we use @BeforeClass.
 */
@BeforeClass
public static void setupClass() {
    mockedAppender = new MockedAppender();
    logger = (Logger)LogManager.getLogger(MatchingMetricsLogger.class);
    logger.addAppender(mockedAppender);
    logger.setLevel(Level.INFO);
}

@AfterClass
public static void teardown() {
    logger.removeAppender(mockedAppender);
}

@Test
public void test() {
    // do something that causes logs
    for (String e : mockedAppender.message) {
        // add asserts for the log messages
    }
}

private static class MockedAppender extends AbstractAppender {

    List<String> message = new ArrayList<>();

    protected MockedAppender() {
        super("MockedAppender", null, null);
    }

    @Override
    public void append(LogEvent event) {
        message.add(event.getMessage().getFormattedMessage());
    }
}
 1
Author: joseph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-27 18:58:33

Otra idea que vale la pena mencionar, aunque es un tema más antiguo, es crear un productor de CDI para inyectar a su registrador para que la burla se vuelva fácil. (Y también da la ventaja de no tener que declarar la "declaración completa del registrador" más, pero eso está fuera de tema)

Ejemplo:

Creando el logger para inyectar:

public class CdiResources {
  @Produces @LoggerType
  public Logger createLogger(final InjectionPoint ip) {
      return Logger.getLogger(ip.getMember().getDeclaringClass());
  }
}

El calificador:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface LoggerType {
}

Usando el registrador en su código de producción:

public class ProductionCode {
    @Inject
    @LoggerType
    private Logger logger;

    public void logSomething() {
        logger.info("something");
    }
}

Probando el registrador en su código de prueba (dando un EasyMock ejemplo):

@TestSubject
private ProductionCode productionCode = new ProductionCode();

@Mock
private Logger logger;

@Test
public void testTheLogger() {
   logger.info("something");
   replayAll();
   productionCode.logSomething();
}
 0
Author: GregD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-06 09:05:09

Usando Jmockit (1.21) pude escribir esta sencilla prueba. La prueba se asegura de que un mensaje de ERROR específico se llame una sola vez.

@Test
public void testErrorMessage() {
    final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( MyConfig.class );

    new Expectations(logger) {{
        //make sure this error is happens just once.
        logger.error( "Something went wrong..." );
        times = 1;
    }};

    new MyTestObject().runSomethingWrong( "aaa" ); //SUT that eventually cause the error in the log.    
}
 0
Author: Yarix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-10 10:20:20

Burlarse del Appender puede ayudar a capturar las líneas de registro. Buscar muestra en: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html

// Fully working test at: https://github.com/njaiswal/logLineTester/blob/master/src/test/java/com/nj/Utils/UtilsTest.java

@Test
public void testUtilsLog() throws InterruptedException {

    Logger utilsLogger = (Logger) LoggerFactory.getLogger("com.nj.utils");

    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    utilsLogger.addAppender(mockAppender);

    final List<String> capturedLogs = Collections.synchronizedList(new ArrayList<>());
    final CountDownLatch latch = new CountDownLatch(3);

    //Capture logs
    doAnswer((invocation) -> {
        LoggingEvent loggingEvent = invocation.getArgumentAt(0, LoggingEvent.class);
        capturedLogs.add(loggingEvent.getFormattedMessage());
        latch.countDown();
        return null;
    }).when(mockAppender).doAppend(any());

    //Call method which will do logging to be tested
    Application.main(null);

    //Wait 5 seconds for latch to be true. That means 3 log lines were logged
    assertThat(latch.await(5L, TimeUnit.SECONDS), is(true));

    //Now assert the captured logs
    assertThat(capturedLogs, hasItem(containsString("One")));
    assertThat(capturedLogs, hasItem(containsString("Two")));
    assertThat(capturedLogs, hasItem(containsString("Three")));
}
 0
Author: nishant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-12 10:01:19

Utilice el siguiente código. Estoy usando el mismo código para mi prueba de integración de spring donde estoy usando log back para el registro. Utilice el método assertJobIsScheduled para afirmar el texto impreso en el registro.

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;

private Logger rootLogger;
final Appender mockAppender = mock(Appender.class);

@Before
public void setUp() throws Exception {
    initMocks(this);
    when(mockAppender.getName()).thenReturn("MOCK");
    rootLogger = (Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
    rootLogger.addAppender(mockAppender);
}

private void assertJobIsScheduled(final String matcherText) {
    verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
        @Override
        public boolean matches(final Object argument) {
            return ((LoggingEvent)argument).getFormattedMessage().contains(matcherText);
        }
    }));
}
 0
Author: SUMIT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-11 13:53:57

Si está utilizando java.util.logging.Logger este artículo puede ser muy útil, crea un nuevo controlador y realiza aserciones en la salida del registro: http://octodecillion.com/blog/jmockit-test-logging /

 0
Author: Mehdi Karamosly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-29 17:09:25

Hay dos cosas que usted podría estar tratando de probar.

  • Cuando hay un evento de interés para el operador de mi programa, mi programa realiza una operación de registro adecuada, que puede informar al operador de ese evento.
  • Cuando mi programa realiza una operación de registro, el mensaje de registro que produce tiene el texto correcto.

Esas dos cosas son en realidad cosas diferentes, y por lo tanto podrían probarse por separado. Sin embargo, la prueba de la segunda (el texto de mensajes) es tan problemático, recomiendo no hacerlo en absoluto. Una prueba del texto de un mensaje consistirá en verificar que una cadena de texto (el texto esperado del mensaje) es la misma que, o puede derivarse trivialmente de, la cadena de texto utilizada en su código de registro.

  • Esas pruebas no prueban la lógica del programa en absoluto, solo prueban que un recurso (una cadena) es equivalente a otro recurso.
  • Las pruebas son frágiles; incluso un pequeño ajuste en el formato de un registro el mensaje rompe tus pruebas.
  • Las pruebas son incompatibles con la internacionalización (traducción) de su interfaz de registro.Las pruebas asumen que solo hay un posible texto de mensaje, y por lo tanto solo un posible lenguaje humano.

Tenga en cuenta que tener su código de programa (implementando alguna lógica de negocios, tal vez) llamando directamente a la interfaz de registro de texto es un diseño pobre (pero desafortunadamente muy comom). Código que es responsable de la lógica de negocio también está decidiendo algunos registros política y el texto de los mensajes de registro. Mezcla lógica de negocio con código de interfaz de usuario (sí, los mensajes de registro son parte de la interfaz de usuario de su programa). Esas cosas deberían estar separadas.

Por lo tanto, recomiendo que business logic no genere directamente el texto de los mensajes de registro. En su lugar, pídale que delegue en un objeto de registro.

  • La clase del objeto de registro debe proporcionar una API interna adecuada, que su objeto de negocio puede usar para expresar el evento que se ha producido usar objetos de su modelo de dominio, no cadenas de texto.
  • La implementación de su clase de registro es responsable de producir representaciones de texto de esos objetos de dominio, y representar una descripción de texto adecuada del evento, luego reenviar ese mensaje de texto al marco de registro de bajo nivel (como JUL, log4j o slf4j).
  • Su lógica de negocio es responsable solo de llamar a los métodos correctos de la API interna de su clase logger, pasando los objetos de dominio correctos, a describa los eventos reales que ocurrieron.
  • Su clase de registro concreto implements y interface, que describe la API interna que puede usar su lógica de negocio.
  • Su(s) clase (es) que implementa la lógica de negocio y debe realizar el registro tiene una referencia al objeto de registro al que delegar. La clase de la referencia es el abstract interface.
  • Utilice la inyección de dependencias para configurar la referencia al registrador.

A continuación, puede probar que sus clases de lógica de negocio correctamente informe a la interfaz de registro sobre los eventos, creando un mock logger, que implementa la API de registro interno, y usando la inyección de dependencias en la fase de configuración de su prueba.

Así:

 public class MyService {// The class we want to test
    private final MyLogger logger;

    public MyService(MyLogger logger) {
       this.logger = Objects.requireNonNull(logger);
    }

    public void performTwiddleOperation(Foo foo) {// The method we want to test
       ...// The business logic
       logger.performedTwiddleOperation(foo);
    }
 };

 public interface MyLogger {
    public void performedTwiddleOperation(Foo foo);
    ...
 };

 public final class MySl4jLogger: implements MyLogger {
    ...

    @Override
    public void performedTwiddleOperation(Foo foo) {
       logger.info("twiddled foo " + foo.getId());
    }
 }

 public final void MyProgram {
    public static void main(String[] argv) {
       ...
       MyLogger logger = new MySl4jLogger(...);
       MyService service = new MyService(logger);
       startService(service);// or whatever you must do
       ...
    }
 }

 public class MyServiceTest {
    ...

    static final class MyMockLogger: implements MyLogger {
       private Food.id id;
       private int nCallsPerformedTwiddleOperation;
       ...

       @Override
       public void performedTwiddleOperation(Foo foo) {
          id = foo.id;
          ++nCallsPerformedTwiddleOperation;
       }

       void assertCalledPerformedTwiddleOperation(Foo.id id) {
          assertEquals("Called performedTwiddleOperation", 1, nCallsPerformedTwiddleOperation);
          assertEquals("Called performedTwiddleOperation with correct ID", id, this.id);
       }
    };

    @Test
    public void testPerformTwiddleOperation_1() {
       // Setup
       MyMockLogger logger = new MyMockLogger();
       MyService service = new MyService(logger);
       Foo.Id id = new Foo.Id(...);
       Foo foo = new Foo(id, 1);

       // Execute
       service.performedTwiddleOperation(foo);

       // Verify
       ...
       logger.assertCalledPerformedTwiddleOperation(id);
    }
 }
 0
Author: Raedwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-12 14:42:05

Lo que he hecho si todo lo que quiero hacer es ver que alguna cadena fue registrada (en lugar de verificar instrucciones de registro exactas que es demasiado frágil) es redirigir StdOut a un búfer, hacer un contiene, luego restablecer StdOut:

PrintStream original = System.out;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
System.setOut(new PrintStream(buffer));

// Do something that logs

assertTrue(buffer.toString().contains(myMessage));
System.setOut(original);
 0
Author: Cheefachi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-20 16:48:07

La API para Log4J2 es ligeramente diferente. También es posible que esté utilizando su appender async. He creado un appender enganchado para esto:

    public static class LatchedAppender extends AbstractAppender implements AutoCloseable {

    private final List<LogEvent> messages = new ArrayList<>();
    private final CountDownLatch latch;
    private final LoggerConfig loggerConfig;

    public LatchedAppender(Class<?> classThatLogs, int expectedMessages) {
        this(classThatLogs, null, null, expectedMessages);
    }
    public LatchedAppender(Class<?> classThatLogs, Filter filter, Layout<? extends Serializable> layout, int expectedMessages) {
        super(classThatLogs.getName()+"."+"LatchedAppender", filter, layout);
        latch = new CountDownLatch(expectedMessages);
        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Configuration config = ctx.getConfiguration();
        loggerConfig = config.getLoggerConfig(LogManager.getLogger(classThatLogs).getName());
        loggerConfig.addAppender(this, Level.ALL, ThresholdFilter.createFilter(Level.ALL, null, null));
        start();
    }

    @Override
    public void append(LogEvent event) {
        messages.add(event);
        latch.countDown();
    }

    public List<LogEvent> awaitMessages() throws InterruptedException {
        assertTrue(latch.await(10, TimeUnit.SECONDS));
        return messages;
    }

    @Override
    public void close() {
        stop();
        loggerConfig.removeAppender(this.getName());
    }
}

Úsalo así:

        try (LatchedAppender appender = new LatchedAppender(ClassUnderTest.class, 1)) {

        ClassUnderTest.methodThatLogs();
        List<LogEvent> events = appender.awaitMessages();
        assertEquals(1, events.size());
        //more assertions here

    }//appender removed
 0
Author: robbo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-20 19:17:18

Aquí hay una solución de Logback simple y eficiente.
No requiere agregar / crear ninguna clase nueva.
Se basa en ListAppender : un whitebox logback appender donde se agregan entradas de registro en un campo public List que podríamos usar para hacer nuestras aserciones.

He aquí un ejemplo sencillo.

Clase Foo:

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

Clase de pie:

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

Las aserciones JUnit no suenan muy adaptadas para afirmar algunas propiedades específicas de la lista elemento.
Las bibliotecas Matcher / assertion como AssertJ o Hamcrest parecen mejores para eso:

Con AssertJ sería:

import org.assertj.core.api.Assertions;

Assertions.assertThat(listAppender.list)
          .extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
          .containsExactly(Tuple.tuple("start", Level.INFO), Tuple.tuple("finish", Level.INFO));
 0
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-12 19:26:53