Eu também precisei disso várias vezes. Reunimos uma pequena amostra abaixo, que você deseja ajustar às suas necessidades. Basicamente, você cria o seu próprio Appender
e o adiciona ao criador de logs que deseja. Se você deseja coletar tudo, o registrador raiz é um bom ponto de partida, mas você pode usar um mais específico, se desejar. Não se esqueça de remover o Appender quando terminar, caso contrário, você poderá criar um vazamento de memória. Abaixo, fiz isso dentro do teste, mas setUp
ou @Before
e / tearDown
ou @After
podem ser lugares melhores, dependendo de suas necessidades.
Além disso, a implementação abaixo coleta tudo List
na memória. Se você estiver registrando muito, considere adicionar um filtro para eliminar entradas chatas ou gravar o log em um arquivo temporário no disco (Dica: LoggingEvent
is Serializable
, portanto, você poderá serializar apenas os objetos de evento, se sua mensagem de log é.)
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()
, depois avançar e chamarappender.setThreshold(Level.OFF)
cada um (e redefini-los quando terminar!). Isso garante que as mensagens "ruins" que você está tentando gerar não apareçam nos logs de teste e assustem o próximo desenvolvedor.ListAppender<ILoggingEvent>
vez de criar seu próprio aplicativo personalizado.Logger
paraorg.apache.logging.log4j.core.Logger
(a classe de implementação da interface), terá acessosetAppender()/removeAppender()
novamente.Aqui está uma solução Logback simples e eficiente.
Não é necessário adicionar / criar nenhuma nova classe.
Ele se baseia em
ListAppender
: um aplicativo de logback de caixa branca, onde as entradas de log são adicionadas em umpublic List
campo que poderíamos usar para fazer nossas afirmações.Aqui está um exemplo simples.
Classe Foo:
Classe FooTest:
As asserções JUnit não parecem muito adaptadas para afirmar algumas propriedades específicas dos elementos da lista.
As bibliotecas de correspondências / asserções como AssertJ ou Hamcrest parecem melhores para isso:
Com AssertJ seria:
fonte
mock
a classe que está sendo testada. Você precisa instanciá-lo comnew
operadorMuito obrigado por estas (surpreendentemente) respostas rápidas e úteis; eles me colocaram no caminho certo para a minha solução.
A base de código onde eu quero usar isso, usa java.util.logging como mecanismo de logger, e não me sinto em casa o suficiente nesses códigos para mudar completamente isso para log4j ou para interfaces / fachadas de logger. Mas com base nessas sugestões, eu 'hackeei' uma extensão de manipulador de mensagens e isso funciona como um deleite.
Um breve resumo segue. Estender
java.util.logging.Handler
:Obviamente, você pode armazenar o quanto quiser / desejar / precisar do
LogRecord
, ou empurrá-los todos para uma pilha até obter um estouro.Na preparação para o teste de junção, você cria um
java.util.logging.Logger
e adiciona um novoLogHandler
a ele:A chamada para
setUseParentHandlers()
é silenciar os manipuladores normais, para que (nesta execução de teste de junção) não ocorra log desnecessário. Faça o que seu código em teste precisar para usar esse criador de logs, execute o teste e asserEquality:(Obviamente, você moveria grande parte deste trabalho para um
@Before
método e faria várias outras melhorias, mas isso atrapalharia esta apresentação.)fonte
Outra opção é zombar do Appender e verificar se a mensagem foi registrada neste appender. Exemplo para Log4j 1.2.xe mockito:
fonte
Efetivamente, você está testando um efeito colateral de uma classe dependente. Para teste de unidade, você precisa apenas verificar se
foi chamado com o parâmetro correto. Portanto, use uma estrutura de simulação para emular o criador de logs e isso permitirá que você teste o comportamento de sua própria classe.
fonte
Zombar é uma opção aqui, embora seja difícil, porque os registradores geralmente são finais estáticos particulares - portanto, definir um logger simulado não seria fácil, ou exigiria modificação da classe em teste.
Você pode criar um Appender personalizado (ou como quer que seja chamado) e registrá-lo - por meio de um arquivo de configuração somente de teste ou em tempo de execução (de certa forma, dependente da estrutura de registro). E então você pode obter esse appender (estaticamente, se declarado no arquivo de configuração ou por sua referência atual, se você estiver conectando-o em tempo de execução) e verificar seu conteúdo.
fonte
Inspirado na solução de @ RonaldBlaschke, eu vim com isso:
... o que permite que você faça:
Você provavelmente poderia usar o hamcrest de uma maneira mais inteligente, mas eu deixei assim.
fonte
Para log4j2, a solução é um pouco diferente porque o AppenderSkeleton não está mais disponível. Além disso, o uso do Mockito ou de uma biblioteca semelhante para criar um Appender com um ArgumentCaptor não funcionará se você estiver esperando várias mensagens de log porque o MutableLogEvent é reutilizado em várias mensagens de log. A melhor solução que encontrei para o log4j2 é:
fonte
Como mencionado nos outros, você pode usar uma estrutura de zombaria. Para que isso funcione, é necessário expor o criador de logs em sua classe (embora eu prefira torná-lo privado em vez de criar um setter público).
A outra solução é criar um logger falso manualmente. Você precisa escrever o logger falso (mais código de fixação), mas neste caso eu preferiria a legibilidade aprimorada dos testes contra o código salvo da estrutura de simulação.
Eu faria algo assim:
fonte
Uau. Não sei por que isso foi tão difícil. Descobri que não consegui usar nenhum dos exemplos de código acima porque estava usando o log4j2 sobre o slf4j. Esta é a minha solução:
fonte
Aqui está o que eu fiz para o logback.
Eu criei uma classe TestAppender:
Em seguida, no pai da minha classe de teste de unidade de teste, criei um método:
Eu tenho um arquivo logback-test.xml definido em src / test / resources e adicionei um aplicativo de teste:
e adicionou este appender ao appender raiz:
Agora, nas minhas classes de teste que se estendem da minha classe de teste principal, posso obter o appender, registrar a última mensagem e verificar a mensagem, o nível e o lançamento.
fonte
Para o dia 5 de junho (Júpiter), o OutputCaptureExtension da Spring é bastante útil. Está disponível desde o Spring Boot 2.2 e está disponível no artefato spring-boot-test .
Exemplo (extraído de javadoc):
fonte
getOut()
ougetErr()
.Quanto a mim, você pode simplificar seu teste usando
JUnit
withMockito
. Proponho a seguinte solução para isso:É por isso que temos boa flexibilidade para testes com diferentes quantidades de mensagens
fonte
when(appender.isStarted()).thenReturn(true); when(appender.getName()).thenReturn("Test Appender");
e alterar LoggingEvent -> LogEventfonte
A API para Log4J2 é um pouco diferente. Além disso, você pode estar usando seu aplicativo assíncrono. Eu criei um appender bloqueado para isso:
Use-o assim:
fonte
Observe que no Log4J 2.x, a interface pública
org.apache.logging.log4j.Logger
não inclui os métodossetAppender()
eremoveAppender()
.Mas se você não estiver fazendo algo muito sofisticado, poderá convertê-lo na classe de implementação
org.apache.logging.log4j.core.Logger
, que expõe esses métodos.Aqui está um exemplo com Mockito e AssertJ :
fonte
Outra idéia que vale a pena mencionar, embora seja um tópico mais antigo, é a criação de um produtor de CDI para injetar seu criador de logs para que a zombaria se torne fácil. (E também oferece a vantagem de não precisar mais declarar a "declaração do logger inteiro", mas isso é fora de tópico)
Exemplo:
Criando o logger para injetar:
O qualificador:
Usando o logger no seu código de produção:
Testando o logger no seu código de teste (dando um exemplo do easyMock):
fonte
Usando o Jmockit (1.21), consegui escrever este teste simples. O teste garante que uma mensagem de erro específica seja chamada apenas uma vez.
fonte
Zombar do Appender pode ajudar a capturar as linhas de log. Encontre exemplo em: http://clearqa.blogspot.co.uk/2016/12/test-log-lines.html
fonte
Use o código abaixo. Estou usando o mesmo código para meu teste de integração de primavera, em que estou usando o log back para log. Use o método assertJobIsScheduled para afirmar o texto impresso no log.
fonte
se você estiver usando
java.util.logging.Logger
este artigo, pode ser muito útil, ele cria um novo manipulador e faz asserções no log Saída: http://octodecillion.com/blog/jmockit-test-logging/fonte
Há duas coisas que você pode estar tentando testar.
Essas duas coisas são realmente diferentes e, portanto, podem ser testadas separadamente. No entanto, testar o segundo (o texto das mensagens) é tão problemático que eu recomendo não fazê-lo. Um teste de um texto de mensagem consistirá em verificar se uma sequência de texto (o texto esperado da mensagem) é a mesma ou pode ser derivada trivialmente da sequência de texto usada no seu código de registro.
Observe que o código do seu programa (implementando alguma lógica de negócios, talvez) chamando diretamente a interface de log de texto é um design ruim (mas infelizmente muito comum). O código responsável pela lógica de negócios também está decidindo alguma política de log e o texto das mensagens de log. Ele combina a lógica comercial com o código da interface do usuário (sim, as mensagens de log fazem parte da interface do usuário do seu programa). Essas coisas devem ser separadas.
Portanto, recomendo que a lógica comercial não gere diretamente o texto das mensagens de log. Em vez disso, delegar para um objeto de log.
implements
aninterface
, que descreve a API interna que sua lógica de negócios pode usar.interface
.Em seguida, você pode testar se suas classes de lógica de negócios informam corretamente a interface de log sobre eventos, criando um criador de log simulado, que implementa a API de log interno, e usando a injeção de dependência na fase de configuração do seu teste.
Como isso:
fonte
O que eu fiz se tudo que eu quero fazer é ver que alguma string foi registrada (em vez de verificar as instruções de log exatas que são muito quebradiças) é redirecionar o StdOut para um buffer, fazer a contém e redefinir o StdOut:
fonte
java.util.logging
(embora eu tenha usadoSystem.setErr(new PrintStream(buffer));
, porque ele registra no stderr), mas não funciona (o buffer permanece vazio). se eu usá-System.err.println("foo")
lo diretamente, ele funciona, portanto, presumo que o sistema de registro mantenha sua própria referência do fluxo de saída, do qual é retiradoSystem.err
; portanto, minha chamada paraSystem.setErr(..)
não tem efeito na saída do registro, como ocorre após a inicialização do sistema de registro.Respondi a uma pergunta semelhante para log4j, veja como posso testar com junit que um aviso foi registrado com log4
Isso é mais recente e exemplo com o Log4j2 (testado com 2.11.2) e 5 de junho;
Usando as seguintes dependências do maven
fonte
Se você estiver usando o log4j2, a solução de https://www.dontpanicblog.co.uk/2018/04/29/test-log4j2-with-junit/ me permitiu afirmar que as mensagens foram registradas.
A solução é assim:
Definir um aplicativo log4j como uma regra ExternalResource
Defina um teste que use sua regra ExternalResource
Não se esqueça de ter o log4j2.xml como parte do src / test / resources
fonte