Kaydedicideki bir iletide bir JUnit iddiası nasıl yapılır


207

Ben durumunu bildirmek için bir Java günlük üzerinde çağıran bazı test altında kod var. JUnit test kodunda, bu günlükçüye doğru günlük girişinin yapıldığını doğrulamak istiyorum. Aşağıdaki satırlarda bir şey:

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

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

Bunun özel olarak uyarlanmış bir kaydedici (veya işleyici veya biçimlendirici) ile yapılabileceğini düşünüyorum, ancak zaten var olan bir çözümü yeniden kullanmayı tercih ederim. (Ve dürüst olmak gerekirse, logRecord'a nasıl kaydedileceğimi net değil, ama bunun mümkün olduğunu varsayalım.)

Yanıtlar:


142

Buna birkaç kez de ihtiyacım var. Aşağıda, ihtiyaçlarınıza göre ayarlamak istediğiniz küçük bir örneği bir araya getirdim. Temel olarak, kendinizinkini oluşturur Appenderve istediğiniz günlükçüye eklersiniz. Her şeyi toplamak istiyorsanız, kök kaydedici başlamak için iyi bir yerdir, ancak isterseniz daha belirgin bir şekilde kullanabilirsiniz. İşiniz bittiğinde Appender'ı kaldırmayı unutmayın, aksi takdirde bir bellek sızıntısı oluşturabilirsiniz. Aşağıda bunu test içinde yaptım, ama setUpveya @Beforeve / tearDownveya @Afterihtiyaçlarınıza bağlı olarak daha iyi yerler olabilir.

Ayrıca, aşağıdaki uygulama her şeyi bir Listbellekte toplar . Çok oturum açıyorsanız, sıkıcı girişleri bırakmak veya günlüğü diskteki geçici bir dosyaya yazmak için bir filtre eklemeyi düşünebilirsiniz (İpucu: LoggingEventis Serializable, böylece günlük mesajınız varsa olay nesnelerini serileştirebilmeniz gerekir. dır-dir.)

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

4
Harika çalışıyor. Yapacağım tek iyileştirme aramak logger.getAllAppenders(), sonra adım atmak ve appender.setThreshold(Level.OFF)her birini çağırmak (ve bittiğinde onları sıfırlamak!). Bu, oluşturmaya çalıştığınız "kötü" iletilerin test günlüklerinde görünmemesini ve bir sonraki geliştiriciyi korkutmasını sağlar.
Coderer

1
Log4j
2.x'de

1
Bunun için teşekkürler. Ancak LogBack kullanıyorsanız, ListAppender<ILoggingEvent>kendi özel uygulayıcınızı oluşturmak yerine kullanabilirsiniz .
17'de sinujohn

2
ama bu slf4j için işe yaramıyor! bununla çalışmak için nasıl değiştirebilirim biliyor musunuz?
Shilan

3
@sd Loggerto org.apache.logging.log4j.core.Logger(arabirimin uygulama sınıfı) seçeneğini kullanırsanız, setAppender()/removeAppender()yeniden erişebilirsiniz .
David Moles

62

İşte basit ve verimli bir Logback çözümü.
Herhangi bir yeni sınıf eklemek / oluşturmak gerekmez.
Aşağıdakilere dayanır ListAppender: public Listiddialarımızı yapmak için kullanabileceğimiz bir alana günlük girişlerinin eklendiği bir beyaz kutu geri dönüş eki .

İşte basit bir örnek.

Foo sınıfı:

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 sınıfı:

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
        // addAppender is outdated now
        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());
    }
}

JUnit iddiaları, liste öğelerinin bazı belirli özelliklerini iddia etmek için çok uyarlanmış görünmüyor.
AssertJ veya Hamcrest olarak eşleştirici / iddia kütüphaneleri bunun için daha iyi görünür:

AssertJ ile:

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

Bir Hata kaydederseniz testin başarısız olmasını nasıl durdurursunuz?
Ghilteras

@Ghilteras Anladığımdan emin değilim. Bir hatayı günlüğe kaydetmek testinizin başarısız olmasına neden olmamalıdır. Ne açıklıyorsun?
davidxxx

Ayrıca, mocktest edilen sınıfı hatırlamayın . newOperatör ile başlatmanız gerekiyor
Dmytro Chasovskyi

36

Bu (şaşırtıcı) hızlı ve yararlı cevaplar için çok teşekkürler; beni çözümüm için doğru yola koydular.

Ben kodbase bunu kullanmak istiyorum vardı, onun logger mekanizması olarak java.util.logging kullanır ve ben bu kodlarda tamamen log4j veya logger arabirimleri / cepheleri değiştirmek için yeterince hissediyorum. Ancak bu önerilere dayanarak, bir julhandler uzantısını 'hackledim' ve bu bir tedavi olarak çalışıyor.

Kısa bir özet. Uzat 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(){}
}

Açıkçası, istediğiniz / istediğiniz / ihtiyaç duyduğunuz kadar depolayabilir LogRecordveya bir taşma elde edene kadar hepsini bir yığına itebilirsiniz.

Junit testi için hazırlıkta, bir tane yaratırsınız java.util.logging.Loggerve buna yeni bir LogHandlerşey eklersiniz :

@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);

Çağrı setUseParentHandlers(), normal işleyicileri susturmaktır, böylece (bu junit-test çalıştırması için) gereksiz günlük kaydı olmaz. Test altındaki kodunuz bu günlükçüyü kullanmak için ne gerekiyorsa yapın, testi çalıştırın ve assert

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

(Tabii ki, bu çalışmanın büyük bir bölümünü bir @Beforeyönteme taşıyacak ve çeşitli başka iyileştirmeler yapacaksınız, ancak bu, bu sunumu karmaşık hale getirecektir.)


16

Başka bir seçenek de Appender'ı taklit etmek ve mesajın bu appender'a kaydedilip kaydedilmediğini doğrulamaktır. Log4j 1.2.x ve mockito örneği:

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

16

Etkili bir şekilde bağımlı bir sınıfın yan etkisini test ediyorsunuz. Birim testi için yalnızca aşağıdakileri doğrulamanız gerekir:

logger.info()

doğru parametre ile çağrıldı. Bu nedenle, logger'ı taklit etmek için alaycı bir çerçeve kullanın ve bu da kendi sınıfınızın davranışını test etmenizi sağlayacaktır.


3
Çoğu kaydedicinin tanımladığı özel bir statik son alanı nasıl alay ettiniz? Powermockito? İyi eğlenceler ..
Stefano L

Stefano: Bu son alan bir şekilde başlatıldı, gerçek şey yerine Mock enjekte etmek için çeşitli yaklaşımlar gördüm. Muhtemelen ilk etapta test edilebilirlik için bir miktar tasarım gerektirmektedir. blog.codecentric.de/en/2011/11/…
djna

Mehdi'nin dediği gibi, muhtemelen uygun bir Handler kullanmak yeterli olabilir,
djna

11

Alaycı burada bir seçenektir, ancak zor olabilir, çünkü kaydediciler genellikle özel statik finaldir - bu yüzden alaycı bir günlükçünün ayarlanması çok kolay olmaz veya test edilen sınıfın değiştirilmesini gerektirir.

Özel bir Appender (veya adı ne olursa olsun) oluşturabilir ve kaydedebilirsiniz - yalnızca sınama yapılandırma dosyası veya çalışma zamanı aracılığıyla (bir şekilde, günlükleme çerçevesine bağlı olarak). Ve sonra bu eki (yapılandırma dosyasında bildirilmişse statik olarak veya çalışma zamanını ekliyorsanız geçerli başvurusu ile) alabilir ve içeriğini doğrulayabilirsiniz.


10

@ RonaldBlaschke'nin çözümünden esinlenerek şunu buldum:

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

}

... bunu yapmanızı sağlar:

@Rule public Log4JTester logTest = new Log4JTester();

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

Muhtemelen hamcrest'i daha akıllıca kullanmasını sağlayabilirsiniz, ama bunu bu şekilde bıraktım.


6

Log4j2 için çözüm biraz farklıdır çünkü AppenderSkeleton artık mevcut değildir. Ayrıca, MutableLogEvent birden çok günlük iletisi üzerinde yeniden kullanıldığından, birden çok günlük iletisini bekliyorsanız Mockito veya benzer bir kitaplığı ArgumentCaptor ile oluşturmak için çalışmaz. Log4j2 için bulduğum en iyi çözüm:

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

5

Diğerlerinden bahsedildiği gibi alaycı bir çerçeve kullanabilirsiniz. Bu işi yapmak için sınıfınızdaki logger'ı açığa çıkarmalısınız (her ne kadar ben bir kamu ayarlayıcı oluşturmak yerine paketi özel yapmayı tercih ederim).

Diğer çözüm elle sahte bir kaydedici oluşturmaktır. Sahte kaydedici (daha fazla fikstür kodu) yazmak zorundasınız, ancak bu durumda alaycı çerçeveden kaydedilen koda karşı testlerin gelişmiş okunabilirliğini tercih ederim.

Böyle bir şey yapardım:

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

5

Vay. Bunun neden bu kadar zor olduğundan emin değilim. Ben slf4j üzerinde log4j2 kullandım çünkü yukarıdaki kod örneklerinden herhangi birini kullanamadı bulundu. Bu benim çözümüm:

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

4

İşte geri dönüş için ne yaptım.

Bir TestAppender sınıfı oluşturdum:

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

Sonra testng birim test sınıfının üst kısmında bir yöntem oluşturdum:

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

Src / test / resources içinde tanımlanan bir logback-test.xml dosyası var ve bir test ekleyicisi ekledim:

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

ve bu eki kök eke ekledi:

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

Şimdi ana test sınıfımdan uzanan test derslerimde eki alabilir ve son iletiyi günlüğe kaydedebilir ve iletiyi, düzeyi, atılabilir olanı doğrulayabilirim.

ILoggingEvent lastEvent = testAppender.getLastEvent();
assertEquals(lastEvent.getMessage(), "...");
assertEquals(lastEvent.getLevel(), Level.WARN);
assertEquals(lastEvent.getThrowableProxy().getMessage(), "...");

GetAppender yönteminin nerede tanımlandığını görmüyorum?!?
bioinfornatics

getAppender bir ch.qos.logback.classic.Logger bir yöntemdir
kfox

4

Junit 5 (Jüpiter) için Spring'in OutputCaptureExtension oldukça faydalıdır. Spring Boot 2.2'den beri mevcuttur ve spring-boot-test yapısında mevcuttur.

Örnek (Javadoc'tan alınmıştır):

@ExtendWith(OutputCaptureExtension.class)
class MyTest {
    @Test
    void test(CapturedOutput output) {
        System.out.println("ok");
        assertThat(output).contains("ok");
        System.err.println("error");
    }

    @AfterEach
    void after(CapturedOutput output) {
        assertThat(output.getOut()).contains("ok");
        assertThat(output.getErr()).contains("error");
    }
}

Ben günlük ifadeleri aksine inanmak getOut()ya getErr().
Ram

Bu aradığım cevap (soru bahar önyükleme ile ilgili olmasa da)!
helleye

3

Bana gelince JUnitile test basitleştirebilirsiniz Mockito. Bunun için aşağıdaki çözümü öneriyorum:

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

Bu yüzden farklı mesaj miktarına sahip testler için hoş bir esnekliğe sahibiz


1
Hemen hemen aynı kod bloklarını tekrarlamak için Logtoj2 için neredeyse 1to1 benim için çalışıyor eklemek istiyorum. when(appender.isStarted()).thenReturn(true); when(appender.getName()).thenReturn("Test Appender");
İçe aktarma

3
Here is the sample code to mock log, irrespective of the version used for junit or sping, springboot.

import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.Appender;
import org.mockito.ArgumentMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Test;

import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class MyTest {
  private static Logger logger = LoggerFactory.getLogger(MyTest.class);

    @Test
    public void testSomething() {
    ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
    final Appender mockAppender = mock(Appender.class);
    when(mockAppender.getName()).thenReturn("MOCK");
    root.addAppender(mockAppender);

    //... do whatever you need to trigger the log

    verify(mockAppender).doAppend(argThat(new ArgumentMatcher() {
      @Override
      public boolean matches(final Object argument) {
        return ((LoggingEvent)argument).getFormattedMessage().contains("Hey this is the message I want to see");
      }
    }));
  }
}

1
Bu benim için çalıştı. Satır 'when (mockAppender.getName ()). ThenReturn ("MOCK")' benim için gerekli değildi.
Mayank Raghav

1

Log4J2 için API biraz farklıdır. Ayrıca zaman uyumsuz ekini kullanıyor olabilirsiniz. Bunun için mandallı bir ek oluşturdum:

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

Şöyle kullanın:

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

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

    }//appender removed

1

Log4J 2.x içinde ortak arabirimin ve yöntemlerini org.apache.logging.log4j.Loggeriçermediğini unutmayın .setAppender()removeAppender()

Ancak çok süslü bir şey yapmıyorsanız, bunu org.apache.logging.log4j.core.Loggerbu yöntemleri ortaya çıkaran uygulama sınıfına yayınlayabilmelisiniz.

Mockito ve AssertJ ile bir örnek :

// Import the implementation class rather than the API interface
import org.apache.logging.log4j.core.Logger;
// Cast logger to implementation class to get access to setAppender/removeAppender
Logger log = (Logger) LogManager.getLogger(MyClassUnderTest.class);

// Set up the mock appender, stubbing some methods Log4J needs internally
Appender appender = mock(Appender.class);
when(appender.getName()).thenReturn("Mock Appender");
when(appender.isStarted()).thenReturn(true);

log.addAppender(appender);
try {
    new MyClassUnderTest().doSomethingThatShouldLogAnError();
} finally {
    log.removeAppender(appender);
}

// Verify that we got an error with the expected message
ArgumentCaptor<LogEvent> logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);
verify(appender).append(logEventCaptor.capture());
LogEvent logEvent = logEventCaptor.getValue();
assertThat(logEvent.getLevel()).isEqualTo(Level.ERROR);
assertThat(logEvent.getMessage().getFormattedMessage()).contains(expectedErrorMessage);

0

Bahsetmeye değer başka bir fikir, eski bir konu olmasına rağmen, kaydedicinizi enjekte etmek için bir CDI üreticisi oluşturmaktır, böylece alay etmek kolaylaşır. (Ve artık "tüm logger deyimini" beyan etmek zorunda kalmama avantajını da veriyor, ancak konu dışı)

Misal:

Enjekte edilecek günlükçü oluşturma:

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

Niteleyici:

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

Kayıt cihazını üretim kodunuzda kullanma:

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

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

Kaydediciyi test kodunuzda test etme (bir easyMock örneği vererek):

@TestSubject
private ProductionCode productionCode = new ProductionCode();

@Mock
private Logger logger;

@Test
public void testTheLogger() {
   logger.info("something");
   replayAll();
   productionCode.logSomething();
}

0

Jmockit (1.21) kullanarak bu basit testi yazabildim. Test, belirli bir HATA mesajının sadece bir kez çağrılmasını sağlar.

@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

Appender'ın atılması, günlük satırlarının yakalanmasına yardımcı olabilir. Örneği şu adreste bulabilirsiniz : 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

Aşağıdaki kodu kullanın. Ben günlük geri oturum açmak için kullanıyorum benim bahar entegrasyon testi için aynı kodu kullanıyorum. Günlükte yazdırılan metni onaylamak için assertJobIsScheduled yöntemini kullanın.

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

Test etmeye çalıştığınız iki şey var.

  • Programımın operatörünün ilgisini çeken bir olay olduğunda, programım operatörü söz konusu olay hakkında bilgilendirebilecek uygun bir kayıt işlemi gerçekleştiriyor mu?
  • Programım bir günlük tutma işlemi gerçekleştirdiğinde, ürettiği günlük iletisi doğru metne sahip mi?

Bu iki şey aslında farklı şeylerdir ve bu yüzden ayrı ayrı test edilebilir. Ancak, ikinciyi (mesajların metni) test etmek çok sorunludur, bunu yapmamayı öneriyorum. Bir mesaj metninin testi sonuç olarak bir metin dizesinin (beklenen mesaj metni), günlük kodunuzda kullanılan metin dizesiyle aynı olduğunu veya önemsiz olarak türetilebildiğini kontrol etmeyi içerir.

  • Bu testler program mantığını hiç test etmez, sadece bir kaynağın (dize) başka bir kaynağa eşdeğer olduğunu test eder.
  • Testler kırılgandır; günlük mesajının biçimlendirilmesinde küçük bir değişiklik bile testlerinizi kırar.
  • Testler, kayıt arayüzünüzün uluslararasılaştırılması (çeviri) ile uyumsuzdur.Testler, sadece bir olası mesaj metni ve dolayısıyla sadece bir olası insan dili olduğunu varsayar.

Program kodunuzun (belki de bazı iş mantığını uygulayarak) doğrudan metin günlüğü arabirimini çağırmanın kötü tasarım (ancak ne yazık ki çok commom) olduğunu unutmayın. İş mantığından sorumlu olan kod aynı zamanda bazı kayıt politikalarına ve günlük mesajları metnine de karar vermektedir. İş mantığını kullanıcı arabirimi koduyla karıştırır (evet, günlük iletileri programınızın kullanıcı arabiriminin bir parçasıdır). Bunlar ayrı olmalı.

Bu nedenle, iş mantığının günlük mesajları metnini doğrudan oluşturmamasını öneriyorum. Bunun yerine bir günlük nesnesine delege edin.

  • Günlük nesnesinin sınıfı, iş nesnenizin, metin dizelerini değil, etki alanı modelinizin nesnelerini kullanarak gerçekleşen olayı ifade etmek için kullanabileceği uygun bir dahili API sağlamalıdır.
  • Günlük sınıfınızın uygulanması, bu etki alanı nesnelerinin metin temsillerini oluşturmaktan ve etkinliğin uygun bir metin açıklamasını yapmaktan, ardından bu kısa iletiyi düşük düzey günlük çerçevesine (JUL, log4j veya slf4j gibi) iletmekten sorumludur.
  • İş mantığınız, gerçekleşen gerçek olayları tanımlamak için yalnızca logger sınıfınızın dahili API'sinin doğru yöntemlerini çağırmaktan, doğru etki alanı nesnelerini iletmekten sorumludur.
  • Sizin somut günlüğü sınıf implementsbirinterfaceİş mantığınızın kullanabileceği dahili API'yi açıklayan .
  • İş mantığını uygulayan ve günlük kaydı gerçekleştirmesi gereken sınıf (lar) ınız, temsilci seçme için günlük nesnesine bir başvuru içeriyor. Referansın sınıfı özettir interface.
  • Kaydediciye başvuruyu ayarlamak için bağımlılık enjeksiyonu kullanın.

Daha sonra, dahili günlük API'sini uygulayan bir sahte günlük oluşturucu oluşturarak ve testinizin kurulum aşamasında bağımlılık enjeksiyonunu kullanarak iş mantık sınıflarınızın günlükler arabirimine olaylar hakkında doğru bir şekilde anlattığını test edebilirsiniz.

Bunun gibi:

 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

Tüm yapmak istediğim ne yaptım (sadece çok kırılgan olan kesin günlük deyimlerini doğrulamak yerine) bazı dize günlüğe kaydedildiğini görmek için bir arabellek StdOut yönlendirmek, içeren bir yapmak, sonra StdOut sıfırlamak için:

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

1
Ben java.util.logging( System.setErr(new PrintStream(buffer));stderr günlüğe kaydeder çünkü kullandım rağmen) ile çalıştı , ama çalışmıyor (arabellek boş kalır). System.err.println("foo")doğrudan kullanırsam , çalışır, bu yüzden günlükleme sisteminin kendi aldığı çıkış akışının kendi referansını tuttuğunu varsayalım System.err, bu yüzden System.setErr(..)çağrım günlük sistemi başladıktan sonra olduğu gibi günlük çıktısı üzerinde hiçbir etkiye sahip değildir.
hoijui

0

Benzer bir soruyu log4j için yanıtladım -için -i-test-ile-junit-that-a-uyarı-log-ile-log4

Bu daha yeni ve Log4j2 (2.11.2 ile test edilmiş) ve junit 5 ile örnek;

    package com.whatever.log;

    import org.apache.logging.log4j.Level;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.core.Logger;
    import org.apache.logging.log4j.core.*;
    import org.apache.logging.log4j.core.appender.AbstractAppender;
    import org.apache.logging.log4j.core.config.Configuration;
    import org.apache.logging.log4j.core.config.LoggerConfig;
    import org.apache.logging.log4j.core.config.plugins.Plugin;
    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    import org.apache.logging.log4j.core.config.plugins.PluginElement;
    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;

    import java.util.ArrayList;
    import java.util.List;
    import static org.junit.Assert.*;

class TestLogger {

    private TestAppender testAppender;
    private LoggerConfig loggerConfig;
    private final Logger logger = (Logger)
            LogManager.getLogger(ClassUnderTest.class);

    @Test
    @DisplayName("Test Log Junit5 and log4j2")
    void test() {
        ClassUnderTest.logMessage();
        final LogEvent loggingEvent = testAppender.events.get(0);
        //asset equals 1 because log level is info, change it to debug and
        //the test will fail
        assertTrue(testAppender.events.size()==1,"Unexpected empty log");
        assertEquals(Level.INFO,loggingEvent.getLevel(),"Unexpected log level");
        assertEquals(loggingEvent.getMessage().toString()
                ,"Hello Test","Unexpected log message");
    }

    @BeforeEach
    private void setup() {
        testAppender = new TestAppender("TestAppender", null);

        final LoggerContext context = logger.getContext();
        final Configuration configuration = context.getConfiguration();

        loggerConfig = configuration.getLoggerConfig(logger.getName());
        loggerConfig.setLevel(Level.INFO);
        loggerConfig.addAppender(testAppender,Level.INFO,null);
        testAppender.start();
        context.updateLoggers();
    }

    @AfterEach
    void after(){
        testAppender.stop();
        loggerConfig.removeAppender("TestAppender");
        final LoggerContext context = logger.getContext();
        context.updateLoggers();
    }

    @Plugin( name = "TestAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE)
    static class TestAppender extends AbstractAppender {

        List<LogEvent> events = new ArrayList();

        protected TestAppender(String name, Filter filter) {
            super(name, filter, null);
        }

        @PluginFactory
        public static TestAppender createAppender(
                @PluginAttribute("name") String name,
                @PluginElement("Filter") Filter filter) {
            return new TestAppender(name, filter);
        }

        @Override
        public void append(LogEvent event) {
            events.add(event);
        }
    }

    static class ClassUnderTest {
        private static final Logger LOGGER =  (Logger) LogManager.getLogger(ClassUnderTest.class);
        public static void logMessage(){
            LOGGER.info("Hello Test");
            LOGGER.debug("Hello Test");
        }
    }
}

Aşağıdaki maven bağımlılıklarını kullanma

 <dependency>
 <artifactId>log4j-core</artifactId>
  <packaging>jar</packaging>
  <version>2.11.2</version>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
</dependency>

Bunu denedim ve hat üzerinde kurulum yöntemi içinde bir hata var loggerConfig = configuration.getLoggerConfig (logger.getName ()); Hata değil erişim org.apache.logging.log4j.spi.LoggerContextShutdownEnabled için sınıf dosyası bulunamadı org.apache.logging.log4j.spi.LoggerContextShutdownEnabled edebilirsiniz olduğu
carlos palma

Kodu inceledim ve bazı küçük değişiklikler yaptım, ama benim için çalıştı. Bağımlılıkları kontrol etmenizi ve tüm ithalatların doğru olduğundan emin olmanızı öneririm
Haim Raman

Merhaba, Haim. Ben geri dönüş çözümü uygulamak sona erdi ... ama bence haklısın, bunu uygulamak için başka bir log4j sürümü yapılmış bir ithalat temizlemek zorunda kaldı.
carlos palma

-1

Log4j2 kullanıyorsanız, https://www.dontpanicblog.co.uk/2018/04/29/test-log4j2-with-junit/ adresindeki çözüm, günlüklerin iletildiğini iddia etmeme izin verdi.

Çözüm şöyle gider:

  • Bir log4j ekleyicisini ExternalResource kuralı olarak tanımlama

    public class LogAppenderResource extends ExternalResource {
    
    private static final String APPENDER_NAME = "log4jRuleAppender";
    
    /**
     * Logged messages contains level and message only.
     * This allows us to test that level and message are set.
     */
    private static final String PATTERN = "%-5level %msg";
    
    private Logger logger;
    private Appender appender;
    private final CharArrayWriter outContent = new CharArrayWriter();
    
    public LogAppenderResource(org.apache.logging.log4j.Logger logger) {
        this.logger = (org.apache.logging.log4j.core.Logger)logger;
    }
    
    @Override
    protected void before() {
        StringLayout layout = PatternLayout.newBuilder().withPattern(PATTERN).build();
        appender = WriterAppender.newBuilder()
                .setTarget(outContent)
                .setLayout(layout)
                .setName(APPENDER_NAME).build();
        appender.start();
        logger.addAppender(appender);
    }
    
    @Override
    protected void after() {
        logger.removeAppender(appender);
    }
    
    public String getOutput() {
        return outContent.toString();
        }
    }
  • ExternalResource kuralınızı kullanan bir test tanımlayın

    public class LoggingTextListenerTest {
    
        @Rule public LogAppenderResource appender = new LogAppenderResource(LogManager.getLogger(LoggingTextListener.class)); 
        private LoggingTextListener listener = new LoggingTextListener(); //     Class under test
    
        @Test
        public void startedEvent_isLogged() {
        listener.started();
        assertThat(appender.getOutput(), containsString("started"));
        }
    }

Src / test / resources dosyasının bir parçası olarak log4j2.xml dosyasına sahip olmayı unutmayın

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.