Как заставить вызовы log4j error() вызывать исключение в тестах JUnit?

#java #junit #log4j

#java #junit #log4j

Вопрос:

У меня есть проект Java, который тестируется с JUnit (сочетание стилей Junit 3 и 4), где тестируемые классы могут регистрировать ошибку log4j. Я хотел бы, чтобы модульный тест завершался неудачей, если такая ошибка регистрируется.

Существует ли общий способ настроить log4j или инфраструктуру модульного тестирования так, чтобы любой вызов метода log4j error() в тестируемом коде вызывал исключение во время выполнения и, следовательно, завершал тест неудачно? AOP может быть одним из способов, но меня интересуют и другие возможности.

Цель здесь состоит в том, чтобы отсеять места в коде, где log4j error() используется неправильно. То есть, когда ошибка регистрируется, но никаких исключений или обработки ошибок не произошло, либо это на самом деле не ошибка, либо она есть и должна быть вызвана.

например:

 public class MyTest extends TestCase {
    public void testLogAnError() {
        // Want to make this fail
        new MyClass().logAnError();
    }
}

public class MyClass() {
    static Logger logger = Logger.getLogger("foo");

    public void logAnError() {
        // I'm logging an error, but not doing anything about it
        logger.error("Something bad, or is it?");
        // TODO throw an exception or don't log an error if there isn't one
    }
}
  

Обновление: вот как выглядит решение, которое я использую в настоящее время. Он основан на ответе sbridges (добавлен в тестовый класс):

 private static final Appender crashAndBurnAppender = new NullAppender () {
    public void doAppend(LoggingEvent event) {
         if(event.getLevel() == Level.ERROR) {
              throw new AssertionError("logged at error:"   event.getMessage());
         }
    }
};
  

Затем в программе установки:

 Logger.getRootLogger().addAppender(crashAndBurnAppender);
  

И демонтаж:

 Logger.getRootLogger().removeAppender(crashAndBurnAppender);
  

Ответ №1:

Вы можете создать новое приложение, которое выдает AssertionError при входе в систему на уровне ошибки.

Что-то вроде,

 class TestAppender extends AppenderSkeleton {
    public void doAppend(LoggingEvent event) {
         if(event.getLevel() == Level.Error) {
              throw new AssertionError("logged at error:"   event.getMessage());
         }
    }
}
  

В вашем тесте выполните,

 Logger.getRootLogger().addAppender(new TestAppender());
  

Редактировать: как указал Ральф, удалите TestAppender после завершения теста.

Комментарии:

1. Вам нужно отменить Logger.getRootLogger().addAppender(new TestAppender()); после теста! Поскольку регистратор статичен, это добавляющее устройство также будет присутствовать в следующем тесте, если оно не будет удалено! — Вы должны сделать это с помощью @After метода или оператора finally, потому что «обычный» код в вашем тесте не будет выполнен после исключения.

Ответ №2:

Решение Log4j2

Я только что наткнулся на это, но поскольку я использую версию Log4j 2, мне пришлось адаптировать код. Это заняло у меня довольно много времени, поскольку большинство ресурсов посвящено запутанному беспорядку конфигурационных фабрик, конфигураторов, контекстов регистратора и всего остального. То, что я сделал, может быть, не самым чистым или официальным способом, но это просто.

Определение такого служебного класса с двумя общедоступными статическими методами:

 private static Appender appender;

private static void enableThrowOnLog(Level level) {
    // Downcast: log4j.Logger -> log4j.core.Logger
    Logger rootLogger = (Logger) LogManager.getRootLogger();

    appender = new ThrowingAppender(level);
    appender.start();
    rootLogger.addAppender(appender);
}

public static void disableThrowOnLog() {
    // Downcast: log4j.Logger -> log4j.core.Logger
    Logger rootLogger = (Logger) LogManager.getRootLogger();

    appender.stop();
    rootLogger.removeAppender(appender);
    appender = null;
}

private static class ThrowingAppender extends AbstractAppender {
    private final Level level;

    public ThrowingAppender(Level level) {
        // Last parameter: ignoreExceptions -- must be false or exceptions will be swallowed
        super("ThrowingAppender", new AbstractFilter() {}, PatternLayout.createDefaultLayout(), false);
        this.level = level;
    }

    @Override
    public synchronized void append(LogEvent event) {
        Level eventLevel = event.getLevel();
        if (eventLevel.isMoreSpecificThan(this.level)) {
            String message = event.getMessage().getFormattedMessage();
            throw new AppenderLoggingException(eventLevel   " level was logged:n> "   message);
        }
    }
}
  

В идеале, вы вызываете enableThrowOnLog() внутри метода JUnit 4 @BeforeClass и disableThrowOnLog() внутри @AfterClass метода, чтобы изменения в глобальной системе регистрации были должным образом отменены.