วิธีการยืนยัน JUnit บนข้อความในตัวบันทึก


206

ฉันมี code-under-test ที่เรียกใช้ตัวบันทึก Java เพื่อรายงานสถานะ ในรหัสทดสอบ JUnit ฉันต้องการตรวจสอบว่ามีการบันทึกรายการที่ถูกต้องในตัวบันทึกนี้ บางสิ่งบางอย่างตามบรรทัดต่อไปนี้:

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

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

ฉันคิดว่าสิ่งนี้สามารถทำได้ด้วยตัวบันทึก (หรือตัวจัดการหรือตัวจัดรูปแบบ) ที่ดัดแปลงเป็นพิเศษ แต่ฉันต้องการใช้โซลูชันที่มีอยู่แล้วอีกครั้ง (และตามจริงแล้วมันไม่ชัดเจนสำหรับฉันเกี่ยวกับวิธีบันทึกที่บันทึกจากเครื่องบันทึก แต่สมมติว่าเป็นไปได้)

คำตอบ:


142

ฉันต้องการสิ่งนี้หลายครั้งเช่นกัน ฉันได้รวบรวมตัวอย่างเล็ก ๆ ด้านล่างซึ่งคุณต้องการปรับให้เข้ากับความต้องการของคุณ โดยทั่วไปคุณสร้างของคุณเองAppenderและเพิ่มลงในคนตัดไม้ที่คุณต้องการ หากคุณต้องการรวบรวมทุกอย่างตัวบันทึกรูทเป็นจุดเริ่มต้นที่ดี แต่คุณสามารถใช้วิธีที่เฉพาะเจาะจงกว่านี้หากคุณต้องการ อย่าลืมลบ Appender เมื่อเสร็จแล้วมิฉะนั้นคุณอาจสร้างหน่วยความจำรั่ว ด้านล่างนี้ผมได้ทำมันภายในทดสอบ แต่setUpหรือ@BeforeและtearDownหรือ@Afterอาจจะเป็นสถานที่ที่ดีกว่าขึ้นอยู่กับความต้องการของคุณ

นอกจากนี้การใช้งานด้านล่างจะรวบรวมทุกอย่างListในหน่วยความจำ หากคุณบันทึกจำนวนมากคุณอาจพิจารณาเพิ่มตัวกรองเพื่อปล่อยรายการที่น่าเบื่อหรือเขียนบันทึกลงไฟล์ชั่วคราวบนดิสก์ (คำแนะนำ: LoggingEventคือSerializable, ดังนั้นคุณควรจะสามารถทำให้วัตถุของเหตุการณ์เป็นอนุกรมได้หากข้อความบันทึกของคุณ คือ.)

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
มันใช้งานได้ดี การปรับปรุงเพียงอย่างเดียวที่ฉันจะทำคือการโทรlogger.getAllAppenders()จากนั้นก้าวผ่านและโทรหาappender.setThreshold(Level.OFF)แต่ละรายการ (และรีเซ็ตเมื่อคุณทำเสร็จแล้ว!) สิ่งนี้ทำให้แน่ใจได้ว่าข้อความ "ไม่ดี" ที่คุณพยายามสร้างไม่แสดงขึ้นในบันทึกการทดสอบและทำให้นักพัฒนารายต่อไปเสียสติ
Coderer

1
ใน Log4j 2.x มีความซับซ้อนมากขึ้นเล็กน้อยตามที่คุณต้องการสร้างปลั๊กอินลองดูที่นี่: stackoverflow.com/questions/24205093/…
paranza

1
ขอบคุณสำหรับสิ่งนี้. แต่ถ้าคุณใช้ LogBack คุณสามารถใช้ListAppender<ILoggingEvent>แทนการสร้าง appender ของคุณเอง
sinujohn

2
แต่มันใช้ไม่ได้กับ slf4j! คุณรู้หรือไม่ว่าฉันจะเปลี่ยนมันให้ทำงานกับมันได้อย่างไร?
Shilan

3
@sd หากคุณส่งLoggerไปยังorg.apache.logging.log4j.core.Logger(คลาสการใช้งานสำหรับอินเทอร์เฟซ) คุณจะสามารถเข้าถึงsetAppender()/removeAppender()อีกครั้ง
David Moles

59

นี่คือวิธีการแก้ปัญหาการเข้าสู่ระบบที่เรียบง่ายและมีประสิทธิภาพ
ไม่จำเป็นต้องเพิ่ม / สร้างคลาสใหม่ใด ๆ
มันขึ้นอยู่กับListAppender: appender logback whitebox ที่บันทึกรายการจะถูกเพิ่มในpublic Listเขตข้อมูลที่เราสามารถใช้เพื่อให้การยืนยันของเรา

นี่คือตัวอย่างง่ายๆ

ชั้นฟู:

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:

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 ไม่ส่งผลดีต่อการยืนยันคุณสมบัติเฉพาะบางอย่างขององค์ประกอบรายการ
ไลบรารี Matcher / assertion เนื่องจาก AssertJ หรือ Hamcrest นั้นดูดีกว่าสำหรับเรื่องนี้:

ด้วย AssertJ มันจะเป็น:

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

คุณจะหยุดการทดสอบล้มเหลวได้อย่างไรถ้าคุณบันทึกข้อผิดพลาด
Ghilteras

@Gilteras ฉันไม่แน่ใจว่าจะเข้าใจ การบันทึกข้อผิดพลาดไม่ควรทำให้การทดสอบล้มเหลว คุณอธิบายอะไร
davidxxx

นอกจากนี้อย่าลืมmockชั้นเรียนที่อยู่ระหว่างการทดสอบ คุณต้องยกตัวอย่างมันด้วยnewโอ
เปอร์

35

ขอบคุณมากสำหรับคำตอบที่รวดเร็วและมีประโยชน์ (น่าประหลาดใจ) เหล่านี้ พวกเขาทำให้ฉันถูกวิธีสำหรับการแก้ไขปัญหาของฉัน

codebase ถูกฉันต้องการใช้นี้ใช้ java.util.logging เป็นกลไกการบันทึกและฉันไม่รู้สึกเหมือนอยู่บ้านพอในรหัสเหล่านั้นเพื่อเปลี่ยนที่สมบูรณ์เพื่อ log4j หรือ logger interfaces / facades แต่จากคำแนะนำเหล่านี้ฉัน 'แฮ็กอัพ' ส่วนขยายผู้เล่นที่ล่อลวงและทำงานได้เหมือนการรักษา

สรุปสั้น ๆ ดังนี้ ขยาย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(){}
}

เห็นได้ชัดว่าคุณสามารถจัดเก็บได้มากเท่าที่คุณต้องการ / ต้องการ / ต้องการจากLogRecordหรือผลักพวกเขาทั้งหมดลงในกองจนกว่าคุณจะได้รับมากเกินไป

ในการเตรียมการสำหรับการทดสอบ junit คุณสร้างjava.util.logging.Loggerและเพิ่มสิ่งใหม่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);

การเรียกใช้setUseParentHandlers()เพื่อปิดเสียงตัวจัดการปกติเพื่อให้ (สำหรับการทดสอบ junit นี้) ไม่มีการบันทึกที่ไม่จำเป็นเกิดขึ้น ทำทุกสิ่งที่รหัสของคุณภายใต้การทดสอบจำเป็นต้องใช้ตัวบันทึกนี้เรียกใช้การทดสอบและยืนยันคุณสมบัติ:

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

(แน่นอนว่าคุณจะย้ายส่วนใหญ่ของงานนี้ไปเป็น@Beforeวิธีการและทำการปรับปรุงอื่น ๆ ที่หลากหลาย แต่จะทำให้งานนำเสนอนี้ยุ่งเหยิง)


16

อีกทางเลือกหนึ่งคือการเยาะเย้ย Appender และตรวจสอบว่าข้อความถูกบันทึกไว้ใน appender นี้ ตัวอย่างสำหรับ Log4j 1.2.x และ 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);
    }
}

16

อย่างมีประสิทธิภาพคุณกำลังทดสอบผลข้างเคียงของคลาสที่ต้องพึ่งพา สำหรับการทดสอบหน่วยคุณต้องทำการยืนยันเท่านั้น

logger.info()

ถูกเรียกด้วยพารามิเตอร์ที่ถูกต้อง ดังนั้นใช้กรอบการจำลองเพื่อเลียนแบบคนตัดไม้และที่จะช่วยให้คุณทดสอบพฤติกรรมของชั้นเรียนของคุณเอง


3
คุณจำลองสนามสุดท้ายส่วนตัวอย่างไรซึ่งคนตัดไม้ส่วนใหญ่กำหนดไว้อย่างไร Powermockito? ขอให้สนุก ..
Stefano L

สเตฟาโน: สนามสุดท้ายนั้นเริ่มต้นได้อย่างไรฉันได้เห็นวิธีการต่าง ๆ ในการฉีด Mocks แทนที่จะเป็นของจริง อาจต้องมีการออกแบบระดับหนึ่งสำหรับการทดสอบในครั้งแรก blog.codecentric.de/th/2011/11/…
djna

ดังที่ Mehdi กล่าวว่าอาจใช้ Handler ที่เหมาะสมอาจจะเพียงพอ
djna

11

การเยาะเย้ยเป็นตัวเลือกที่นี่แม้ว่ามันจะเป็นเรื่องยากเพราะคนตัดไม้โดยทั่วไปเป็นคนสุดท้ายคงที่ - ดังนั้นการตั้งค่าตัวลอกเลียนแบบจะไม่เป็นชิ้นส่วนของเค้กหรือจะต้องมีการดัดแปลงชั้นเรียนภายใต้การทดสอบ

คุณสามารถสร้าง Appender แบบกำหนดเอง (หรือสิ่งที่เรียกว่า) และลงทะเบียน - ผ่านไฟล์กำหนดค่าทดสอบอย่างเดียวหรือรันไทม์ (ในทางใดทางหนึ่งขึ้นอยู่กับเฟรมเวิร์กการบันทึก) และจากนั้นคุณจะได้รับ appender (ทั้งแบบคงที่หากประกาศในไฟล์กำหนดค่าหรือตามการอ้างอิงปัจจุบันหากคุณเสียบมันรันไทม์) และตรวจสอบเนื้อหาของมัน


10

แรงบันดาลใจจากวิธีการแก้ปัญหาของ @ RonaldBlaschke ฉันมาด้วยสิ่งนี้:

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

}

... ซึ่งอนุญาตให้คุณทำ:

@Rule public Log4JTester logTest = new Log4JTester();

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

คุณอาจทำให้มันใช้ hamcrest อย่างชาญฉลาด แต่ฉันทิ้งมันไว้ที่นี้


6

สำหรับ log4j2 การแก้ปัญหาจะแตกต่างกันเล็กน้อยเนื่องจาก AppenderSkeleton ไม่สามารถใช้ได้อีกต่อไป นอกจากนี้การใช้ Mockito หรือไลบรารีที่คล้ายกันเพื่อสร้าง Appender ด้วย ArgumentCaptor จะไม่ทำงานหากคุณคาดว่าจะได้รับข้อความบันทึกหลายข้อความเพราะ MutableLogEvent จะถูกใช้ซ้ำผ่านข้อความบันทึกหลายรายการ ทางออกที่ดีที่สุดที่ฉันพบสำหรับ log4j2 คือ:

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

ดังกล่าวจากคนอื่น ๆ คุณสามารถใช้กรอบการเยาะเย้ย เพื่อให้การทำงานคุณต้องเปิดเผยคนตัดไม้ในชั้นเรียนของคุณ (แม้ว่าฉันจะแนะนำให้ทำแพคเกจส่วนตัวแทนการสร้างเซ็ตสาธารณะ)

อีกวิธีหนึ่งคือการสร้างคนตัดไม้ปลอมด้วยมือ คุณต้องเขียน logger ปลอม (รหัสการติดตั้งเพิ่มเติม) แต่ในกรณีนี้ฉันต้องการความสามารถในการอ่านที่เพิ่มขึ้นของการทดสอบเทียบกับรหัสที่บันทึกไว้จากกรอบการเยาะเย้ย

ฉันจะทำสิ่งนี้:

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

ว้าว. ฉันไม่แน่ใจว่าทำไมมันถึงยากขนาดนี้ ฉันพบว่าฉันไม่สามารถใช้ตัวอย่างโค้ดใด ๆ ข้างต้นเพราะฉันใช้ log4j2 บน slf4j นี่คือทางออกของฉัน:

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

นี่คือสิ่งที่ฉันทำสำหรับการย้อนกลับ

ฉันสร้างคลาส 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();
    }
}

จากนั้นใน parent ของคลาส testng ของฉัน test ฉันสร้างวิธีการ:

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

ฉันมีไฟล์ logback-test.xml ที่กำหนดใน src / test / resources และฉันเพิ่ม appender ทดสอบ:

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

และเพิ่ม appender นี้ใน appender ราก:

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

ตอนนี้ในชั้นเรียนการทดสอบของฉันที่ขยายจากระดับการทดสอบผู้ปกครองของฉันฉันจะได้รับ appender และได้รับข้อความล่าสุดเข้าสู่ระบบและตรวจสอบข้อความระดับระดับที่สามารถโยนได้

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

ฉันไม่เห็นวิธีการที่ getAppender กำหนด?!?
bioinfornatics

getAppender เป็นวิธีการใน ch.qos.logback.classic.Logger
kfox

4

สำหรับ Junit 5 (Jupiter) Spring ของOutputCaptureExtensionมีประโยชน์มาก มันมีตั้งแต่ฤดูใบไม้ผลิ Boot 2.2 และสามารถใช้ได้ในฤดูใบไม้ผลิบูตทดสอบสิ่งประดิษฐ์

ตัวอย่าง (นำมาจาก javadoc):

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

ผมเชื่อว่างบล็อกจะแตกต่างหรือgetOut() getErr()
ราม

นี่คือคำตอบที่ฉันกำลังมองหา (แม้ว่าคำถามจะไม่เกี่ยวข้องกับการบูทสปริง)!
helleye

3

สำหรับฉันคุณสามารถลดความซับซ้อนของการทดสอบของคุณโดยใช้กับJUnit Mockitoฉันเสนอวิธีแก้ปัญหาต่อไปนี้:

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

นั่นเป็นเหตุผลที่เรามีความยืดหยุ่นที่ดีสำหรับการทดสอบที่มีปริมาณข้อความที่แตกต่างกัน


1
ในการที่จะไม่ทำบล็อครหัสซ้ำกันเกือบจะต้องการเพิ่มที่เกือบ 1to1 ทำงานให้ฉันสำหรับ Log4j2 เพียงแค่เปลี่ยนการนำเข้าเป็น "org.apache.logging.log4j.core", โยนตัวบันทึกเป็น "org.apache.logging.log4j.core.Logger", เพิ่ม when(appender.isStarted()).thenReturn(true); when(appender.getName()).thenReturn("Test Appender"); และเปลี่ยน LoggingEvent -> LogEvent
Aliaksei Yatsau

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
สิ่งนี้ใช้ได้สำหรับฉัน บรรทัด 'เมื่อ (mockAppender.getName ()) .Return ("MOCK") ไม่จำเป็นสำหรับฉัน
Mayank Raghav

1

API สำหรับ Log4J2 นั้นแตกต่างกันเล็กน้อย นอกจากนี้คุณอาจใช้แอปซิงค์ของ async ฉันสร้าง appender latched สำหรับสิ่งนี้:

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

ใช้แบบนี้:

        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 ส่วนต่อประสานสาธารณะorg.apache.logging.log4j.Loggerไม่รวมsetAppender()และremoveAppender()วิธีการ

แต่ถ้าคุณไม่ทำอะไรที่แฟนซีเกินไปคุณควรที่จะส่งไปยังคลาสการนำไปใช้org.apache.logging.log4j.core.Loggerซึ่งจะเปิดเผยวิธีการเหล่านั้น

นี่คือตัวอย่างของMockitoและAssertJ :

// 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

ความคิดอื่นที่ควรค่าแก่การกล่าวถึงแม้ว่าจะเป็นหัวข้อที่เก่ากว่าก็คือการสร้างผู้ผลิต CDI เพื่ออัดคนตัดไม้ของคุณเพื่อให้การล้อเลียนกลายเป็นเรื่องง่าย (และมันยังให้ประโยชน์โดยไม่ต้องประกาศ "คำสั่ง logger ทั้งหมด" อีกต่อไป แต่นั่นเป็นหัวข้อ)

ตัวอย่าง:

การสร้างคนตัดไม้ที่จะฉีด:

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

รอบคัดเลือก:

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

ใช้ logger ในรหัสการผลิตของคุณ:

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

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

การทดสอบตัวบันทึกในรหัสทดสอบของคุณ (ยกตัวอย่าง easyMock):

@TestSubject
private ProductionCode productionCode = new ProductionCode();

@Mock
private Logger logger;

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

0

ใช้ Jmockit (1.21) ฉันสามารถเขียนแบบทดสอบนี้ได้ การทดสอบทำให้แน่ใจว่าข้อความข้อผิดพลาดเฉพาะถูกเรียกเพียงครั้งเดียว

@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 สามารถช่วยจับเส้นบันทึก ค้นหาตัวอย่างได้ที่: 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

ใช้รหัสด้านล่าง ฉันใช้รหัสเดียวกันสำหรับการทดสอบการรวมสปริงซึ่งฉันใช้ล็อกกลับเพื่อเข้าสู่ระบบ ใช้วิธีการ assertJobIsScheduled เพื่อยืนยันข้อความที่พิมพ์ในบันทึก

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

มีสองสิ่งที่คุณอาจลองทดสอบ

  • เมื่อมีเหตุการณ์ที่น่าสนใจต่อผู้ดำเนินการโปรแกรมของฉันโปรแกรมของฉันจะทำการบันทึกที่เหมาะสมซึ่งสามารถแจ้งผู้ดำเนินการของเหตุการณ์นั้นได้
  • เมื่อโปรแกรมของฉันทำการบันทึกข้อความข้อความที่สร้างขึ้นนั้นมีข้อความที่ถูกต้องหรือไม่

สองสิ่งนั้นต่างกันจริงและสามารถทดสอบแยกกันได้ อย่างไรก็ตามการทดสอบครั้งที่สอง (ข้อความ) เป็นปัญหามากฉันขอแนะนำไม่ให้ทำเลย ในที่สุดการทดสอบข้อความจะประกอบด้วยการตรวจสอบว่าสตริงข้อความหนึ่งข้อความ (ข้อความที่คาดหมาย) นั้นเหมือนกับหรืออาจได้มาจากสตริงข้อความที่ใช้ในรหัสการบันทึกของคุณเล็กน้อย

  • การทดสอบเหล่านั้นไม่ได้ทดสอบตรรกะของโปรแกรมเลยพวกเขาทดสอบว่าหนึ่งทรัพยากร (สตริง) เทียบเท่ากับทรัพยากรอื่น
  • การทดสอบเปราะบาง; แม้แต่การปรับแต่งเล็กน้อยกับการจัดรูปแบบของข้อความบันทึกจะเป็นการหยุดการทดสอบของคุณ
  • การทดสอบเข้ากันไม่ได้กับอินเตอร์เนชันแนล (แปล) อินเตอร์เฟซการบันทึกของคุณการทดสอบถือว่ามีข้อความข้อความเพียงข้อความเดียวที่เป็นไปได้และทำให้มีเพียงภาษามนุษย์เดียวที่เป็นไปได้

โปรดทราบว่าการมีรหัสโปรแกรมของคุณ (อาจใช้ตรรกะทางธุรกิจบ้าง) การโทรไปยังส่วนต่อประสานข้อความโดยตรงเป็นการออกแบบที่ไม่ดี (แต่น่าเสียดายอย่างยิ่ง) รหัสที่รับผิดชอบเกี่ยวกับตรรกะทางธุรกิจก็ตัดสินใจเช่นกันนโยบายการบันทึกและข้อความของบันทึก มันผสมผสานตรรกะทางธุรกิจกับรหัสส่วนต่อประสานกับผู้ใช้ (ใช่ข้อความบันทึกเป็นส่วนหนึ่งของส่วนต่อประสานผู้ใช้ของโปรแกรม) สิ่งเหล่านั้นควรแยกจากกัน

ฉันจึงแนะนำว่าตรรกะทางธุรกิจไม่ได้สร้างข้อความของข้อความบันทึกโดยตรง แทนที่จะให้มันเป็นตัวแทนวัตถุการบันทึก

  • คลาสของวัตถุบันทึกควรมี API ภายในที่เหมาะสมซึ่งวัตถุธุรกิจของคุณสามารถใช้เพื่อแสดงเหตุการณ์ที่เกิดขึ้นโดยใช้วัตถุของรูปแบบโดเมนของคุณไม่ใช่สตริงข้อความ
  • การใช้คลาสการบันทึกของคุณมีหน้าที่ในการสร้างการรับรองข้อความของวัตถุโดเมนเหล่านั้นและการสร้างคำอธิบายข้อความที่เหมาะสมของเหตุการณ์จากนั้นส่งต่อข้อความนั้นไปยังเฟรมเวิร์กการบันทึกระดับต่ำ (เช่น JUL, log4j หรือ slf4j)
  • ตรรกะทางธุรกิจของคุณมีหน้าที่รับผิดชอบในการโทรหาวิธีการที่ถูกต้องของ API ภายในของคลาสตัวบันทึกของคุณผ่านวัตถุโดเมนที่ถูกต้องเพื่ออธิบายเหตุการณ์จริงที่เกิดขึ้น
  • ระดับการเข้าสู่ระบบที่เป็นรูปธรรมของคุณซึ่งอธิบายถึง API ภายในตรรกะทางธุรกิจของคุณอาจใช้implementsinterface
  • คลาสของคุณที่ใช้ตรรกะทางธุรกิจและต้องทำการบันทึกมีการอ้างอิงถึงวัตถุการบันทึกเพื่อมอบหมาย interfaceชั้นของการอ้างอิงเป็นนามธรรม
  • ใช้การฉีดพึ่งพาเพื่อตั้งค่าการอ้างอิงถึงคนตัดไม้

จากนั้นคุณสามารถทดสอบว่าคลาสตรรกะทางธุรกิจของคุณบอกอินเตอร์เฟสการบันทึกเกี่ยวกับเหตุการณ์ได้อย่างถูกต้องโดยสร้าง mock logger ซึ่งใช้ API การบันทึกภายในและใช้การฉีดพึ่งพาในขั้นตอนการตั้งค่าการทดสอบของคุณ

แบบนี้:

 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

สิ่งที่ฉันทำถ้าทั้งหมดที่ฉันต้องการจะทำคือเห็นว่าบางสายถูกบันทึก (ตรงข้ามกับการตรวจสอบคำสั่งบันทึกที่แน่นอนซึ่งเป็นเพียงเปราะเกินไป) คือการเปลี่ยนเส้นทาง StdOut ไปยังบัฟเฟอร์ทำมีแล้วรีเซ็ต 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);

1
ฉันลองมันด้วยjava.util.logging(แม้ว่าฉันจะใช้System.setErr(new PrintStream(buffer));เพราะมันบันทึกไปยัง stderr) แต่มันไม่ทำงาน (บัฟเฟอร์ยังคงว่างเปล่า) ถ้าฉันใช้System.err.println("foo")โดยตรงมันใช้งานได้ดังนั้นฉันคิดว่าระบบการบันทึกเก็บการอ้างอิงของตัวเองของเอาต์พุตสตรีมเอาไว้ซึ่งจะใช้System.errเพื่อให้การเรียกของฉัน System.setErr(..)ไม่มีผลต่อเอาต์พุตของล็อกเนื่องจากเกิดขึ้นหลังจากระบบบันทึก init
hoijui

0

ฉันตอบคำถามที่คล้ายกันสำหรับ log4j ดูวิธีสามารถฉันทดสอบกับ Junit ว่าคำเตือนที่ถูกบันทึกด้วย Log4

นี่คือรุ่นที่ใหม่กว่าและตัวอย่างด้วย Log4j2 (ทดสอบกับ 2.11.2) และรุ่น 5

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

การใช้การพึ่งพา Maven ต่อไปนี้

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

ฉันลองสิ่งนี้และพบข้อผิดพลาดภายในวิธีการตั้งค่าบนบรรทัด loggerConfig = configuration.getLoggerConfig (logger.getName ()); ข้อผิดพลาดไม่สามารถเข้าถึงไฟล์คลาส org.apache.logging.log4j.spi.LoggerContextShutdownEnabled สำหรับ org.apache.logging.log4j.spi.LoggerContextShutdownEnabled ไม่พบ
carlos palma

ฉันตรวจสอบรหัสและทำการเปลี่ยนแปลงเล็กน้อย แต่ก็ใช้ได้กับฉัน ฉันขอแนะนำให้คุณตรวจสอบการอ้างอิงและตรวจสอบให้แน่ใจว่าการนำเข้าทั้งหมดถูกต้อง
ฮาอิมรามาน

สวัสดีฮาอิม ฉันลงเอยด้วยการนำวิธีการแก้ปัญหากลับมาใช้ ... แต่ฉันคิดว่าคุณถูกต้องเพื่อที่จะนำสิ่งนั้นมาใช้ในการทำความสะอาดสิ่งที่นำเข้าฉันทำด้วยรุ่น log4j อื่น
carlos palma

-1

หากคุณใช้ log4j2 โซลูชันจากhttps://www.dontpanicblog.co.uk/2018/04/29/test-log4j2-with-junit/ อนุญาตให้ฉันยืนยันข้อความที่ถูกบันทึกไว้

วิธีแก้ปัญหาจะเป็นดังนี้:

  • กำหนด app4 log4j เป็นกฎ ExternalResource

    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 ของคุณ

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

อย่าลืมมี log4j2.xml เป็นส่วนหนึ่งของ src / test / resources

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.