Как я могу проверить с junit, что предупреждение было зарегистрировано с log4j?
Я тестирую метод, который регистрирует предупреждения, когда что-то пошло не так и возвращает null.
что-то вроде:
private static final Logger log = Logger.getLogger(Clazz.class.getName());
....
if (file == null || !file.exists()) {
// if File not found
log.warn("File not found: "+file.toString());
} else if (!file.canWrite()) {
// if file is read only
log.warn("File is read-only: "+file.toString());
} else {
// all checks passed, can return an working file.
return file;
}
return null;
Я хотел бы протестировать с помощью junit предупреждение о том, что в дополнение к возврату нулевого значения во всех случаях (например, файл не найден, файл доступен только для чтения).
любые идеи?
спасибо, asaf: -)
UPDATE
Моя реализация ответа Аарона (плюс примечание peter):
public class UnitTest {
...
@BeforeClass
public static void setUpOnce() {
appenders = new Vector<Appender>(2);
// 1. just a printout appender:
appenders.add(new ConsoleAppender(new PatternLayout("%d [%t] %-5p %c - %m%n")));
// 2. the appender to test against:
writer = new StringWriter();
appenders.add(new WriterAppender(new PatternLayout("%p, %m%n"),writer));
}
@Before
public void setUp() {
// Unit Under Test:
unit = new TestUnit();
// setting test appenders:
for (Appender appender : appenders) {
TestUnit.log.addAppender(appender);
}
// saving additivity and turning it off:
additivity = TestUnit.log.getAdditivity();
TestUnit.log.setAdditivity(false);
}
@After
public void tearDown() {
unit = null;
for (Appender appender : appenders) {
TestUnit.log.removeAppender(appender);
}
TestUnit.log.setAdditivity(additivity);
}
@Test
public void testGetFile() {
// start fresh:
File file;
writer.getBuffer().setLength(0);
// 1. test null file:
System.out.println(" 1. test null file.");
file = unit.getFile(null);
assertNull(file);
assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
writer.getBuffer().setLength(0);
// 2. test empty file:
System.out.println(" 2. test empty file.");
file = unit.getFile("");
assertNull(file);
assertTrue(writer.toString(), writer.toString().startsWith("WARN, File not found"));
writer.getBuffer().setLength(0);
}
спасибо, ребята,
Ответы
Ответ 1
В настройке unit test:
- Получить тот же журнал
- Сделать это не аддитивным
-
Добавьте добавление, которое запоминает сообщения в списке:
public class TestAppender extends AppenderSkeleton {
public List<String> messages = new ArrayList<String>();
public void doAppend(LoggingEvent event) {
messages.add( event.getMessage().toString() );
}
}
-
Добавить приложение в журнал
Теперь вы можете позвонить своему коду. После теста вы найдете все сообщения журнала в списке. Добавьте уровень журнала, если хотите (messages.add( event.getLevel() + " " + event.getMessage() );
).
В tearDown()
удалите приложение еще раз и включите аддитивность.
Ответ 2
Используя Mockito, вы можете протестировать журнал, произошедший во время теста, с минимальным кодом плиты котла, простой пример:
@RunWith(MockitoJUnitRunner.class)
public class TestLogging {
@Mock AppenderSkeleton appender;
@Captor ArgumentCaptor<LoggingEvent> logCaptor;
@Test
public void test() {
Logger.getRootLogger().addAppender(appender);
...<your test code here>...
verify(appender).doAppend(logCaptor.capture());
assertEquals("Warning message should have been logged", "Caution!", logCaptor.getValue().getRenderedMessage());
}
}
Ответ 3
Альтернативой решению Aaron было бы настроить WriterAppender с прикрепленным StringWriter. В конце теста вы можете проверить содержимое строки вывода журнала.
Это немного проще реализовать (нет необходимости в настраиваемом коде), однако он менее гибкий в отношении проверки результатов, поскольку вы получаете только результат как обычный текст. В некоторых случаях это может затруднить проверку вывода, чем с решением Aaron.
Ответ 4
Примеры в этом посте были очень полезны, но я нашел их немного запутанными.
Поэтому я добавляю упрощенную версию для вышеупомянутого с некоторыми незначительными изменениями.
- Я добавляю свой appender в корневой журнал.
Таким образом, и при условии, что аддитивно верно по умолчанию, мне не нужно беспокоиться о потере моих событий из-за иерархии журналов. Убедитесь, что это соответствует конфигурации файла log4j.properties.
- Я переопределяю append, а не doAppend.
Append в AppenderSkeleton имеет дело с фильтрацией уровня, поэтому я не хочу пропустить это.
doAppend вызовет append, если уровень прав.
public class TestLogger {
@Test
public void test() {
TestAppender testAppender = new TestAppender();
Logger.getRootLogger().addAppender(testAppender);
ClassUnderTest.logMessage();
LoggingEvent loggingEvent = testAppender.events.get(0);
//asset equals 1 because log level is info, change it to debug and
//the test will fail
assertTrue("Unexpected empty log",testAppender.events.size()==1);
assertEquals("Unexpected log level",Level.INFO,loggingEvent.getLevel());
assertEquals("Unexpected log message"
,loggingEvent.getMessage().toString()
,"Hello Test");
}
public static class TestAppender extends AppenderSkeleton{
public List<LoggingEvent> events = new ArrayList<LoggingEvent>();
public void close() {}
public boolean requiresLayout() {return false;}
@Override
protected void append(LoggingEvent event) {
events.add(event);
}
}
public static class ClassUnderTest {
private static final Logger LOGGER =
Logger.getLogger(ClassUnderTest.class);
public static void logMessage(){
LOGGER.info("Hello Test");
LOGGER.debug("Hello Test");
}
}
}
log4j.properties
log4j.rootCategory=INFO, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d %p [%c] - %m%n
# un-comment this will fail the test
#log4j.logger.com.haim.logging=DEBUG
Ответ 5
Вместо прямого вызова log4j используйте защищенный метод в своем классе.
Что-то вроде:
protected void log(String message, Level level)
{
//delegates to log4j
}
Затем создайте подкласс тестируемого класса, который отключит этот метод, чтобы вы могли проверить, что он вызывается как ожидалось.
class MyTest extends <class under test>
{
boolean somethingLogged = false;
protected void log(String message, Level level)
{
somethingLogged = true;
}
}
а затем утвердить на основе somethingLogged. Вы можете добавить условную логику в методе t переопределения метода на основе ожидаемого сообщения/уровня.
Вы можете пойти дальше и записать все вызовы, а затем выполнить поиск по зарегистрированным сообщениям или проверить, что они были зарегистрированы в правильном порядке и т.д.
Ответ 6
Я приспосабливаю ответ Хаима к чему-то более RAII:
public static class TestAppender extends AppenderSkeleton {
@Override
protected void append(LoggingEvent event) {
messages.add(event.getRenderedMessage());
}
@Override
public void close() { }
@Override
public boolean requiresLayout() { return false; }
protected final List<String> messages = new ArrayList<>();
}
static class LogGuard implements AutoCloseable {
protected final TestAppender appender;
LogGuard(Level level) {
appender = new TestAppender();
appender.setThreshold(level);
Logger.getRootLogger().addAppender(appender);
}
@Override
public void close() throws Exception {
Logger.getRootLogger().removeAppender(appender);
}
}
И тогда использование просто:
try (LogGuard log = new LogGuard(Level.WARN)) { // if you want WARN or higher
// do what causes the logging
Assert.assertTrue(log.appender.messages.stream().anyMatch(m -> m.equals("expected"));
}