单元测试日志打印相关接口及类 Logger

时间:2024-11-07 09:22:35

LoggerFactory 简介

单元测试常用日志打印工具LoggerFactory。

LoggerFactory 代码结构
LoggerFactory 代码结构

LoggerFactory 是 JUnit 平台中的一个类,用于创建 Logger 实例。它被设计用于提供日志记录功能,使得 JUnit 在执行测试时能够记录信息、警告、错误等。

LoggerFactory 的主要目的是为 JUnit 提供一个集中式的日志记录机制,允许在测试过程中记录重要的信息,同时保持与不同日志实现的灵活性。这样做可以增强调试能力和监控测试执行过程中的重要事件。

package org.junit.platform.commons.logging;

/**
 * Factory for the {@link Logger} facade for JUL.
 *
 * @since 1.0
 */
@API(status = INTERNAL, since = "1.0")
public final class LoggerFactory {

	private LoggerFactory() {
		/* no-op */
	}

	private static final Set<LogRecordListener> listeners = ConcurrentHashMap.newKeySet();


    private static final class DelegatingLogger implements Logger {}



}

创建 Logger 实例(示例)

 LoggerFactory 的主要职责是根据传入的参数创建适当的 Logger 实例。这个实例可以用于记录不同级别的日志,例如调试信息、错误信息等。

	/**
	 * Get a {@link Logger} for the specified class.
	 *
	 * @param clazz the class for which to get the logger; never {@code null}
	 * @return the logger
	 */
	public static Logger getLogger(Class<?> clazz) {
		// NOTE: we cannot use org.junit.platform.commons.util.Preconditions here
		// since that would introduce a package cycle.
		if (clazz == null) {
			throw new JUnitException("Class must not be null");
		}

		return new DelegatingLogger(clazz.getName());
	}

调用方式:

public class Test {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
}

使用原生的Logger有个问题就是,日志打印的时候传入的参数对象不合适,需要自己再封装,愿意如下,看Logger 接口的定义。

Logger 接口

package org.junit.platform.commons.logging;

/**
 * The {@code Logger} API serves as a simple logging facade for
 * {@code java.util.logging} (JUL).
 *
 * @since 1.0
 */
@API(status = INTERNAL, since = "1.0")
public interface Logger {
	/**
	 * Log the provided {@code Throwable} and message from the provided
	 * {@code messageSupplier} at error level.
	 *
	 * <p>Maps to {@link java.util.logging.Level#SEVERE} in JUL.
	 */
	void error(Throwable throwable, Supplier<String> messageSupplier);

	/**
	 * Log the provided {@code Throwable} and message from the provided
	 * {@code messageSupplier} at info level.
	 *
	 * <p>Maps to {@link java.util.logging.Level#INFO} in JUL.
	 */
	void info(Throwable throwable, Supplier<String> messageSupplier);

	/**
	 * Log the provided {@code Throwable} and message from the provided
	 * {@code messageSupplier} at trace level.
	 *
	 * <p>Maps to {@link java.util.logging.Level#FINER} in JUL.
	 */
	void trace(Throwable throwable, Supplier<String> messageSupplier);

}

自定义Logger

因为库函数中的日志工具比较抽象,所以自定义Logger是很关键的。

一个保存数据时验证UI行为的功能。

import org.junit.platform.commons.logging.LoggerFactory;

/**
* 保存数据,验证是否弹窗的功能
*/

@RunWith(MockitoJUnitRunner.class)    
public class TestSave {

    private static final String TAG = "TestSave";

    //内部自定义日志打印接口,没使用库的类就要自己定义接口
    @Mock
    private Logger logger;  // Mocking the Logger class used for Log.d statements

    @Mock
    private SaveActivity activity;
    
    // Instance of the class to test, initialised in setUp
    private SaveActivityUnderTest saveActivityUnderTest;

    //过程空值变量,类似flag的作用
    // Helper mock boolean variables to control the test cases
    private boolean mIsShowSaveDialog;
    private boolean mIsApnListChanged;


    @Before
    public void setUp() {
        saveActivityUnderTest = new SaveActivityUnderTest();
        saveActivityUnderTest .logger = logger;
        saveActivityUnderTest .activity = activity;
    }


    // Moved the class outside any other class scope to avoid issues with instantiation
    static class SaveActivityUnderTest {
        private Logger logger;
        private SaveActivity activity;

        public void onMenuSave() {
            logger.info(TAG, "MENU_SAVE: mIsShowSaveDialog = " + mIsShowSaveDialog);
            if (mIsShowSaveApnDialog) {
                if (mIsListChanged) {
                    activity.showSaveDialog();
                } else {
                    activity.finish();
                }
            } else {
                if (activity.validateAndSaveData()) {
                    activity.finish();
                }
            }
        }
    }

    //省略测试代码

    //自定义接口,这是还没有实现的。
    private interface Logger {
        void d(String tag, String message);
        void info(String tag, String message);
        void error(String message);
        void warn(String message);
    }


    private interface SaveActivity {
        boolean validateAndSaveData();
        void showSaveDialog();
        void finish();
    }


}

参考源码库定制适合自己类的log。

LoggerFactory 常量类

final class 不能被继承。

package com.demo.test.util;

import org.junit.platform.commons.JUnitException;

import java.util.logging.Level;
import java.util.logging.LogRecord;

public final class LoggerFactory {
    private LoggerFactory() {
        /* no-op */
    }

    public static Logger getLogger(Class<?> clazz) {
        // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here
        // since that would introduce a package cycle.
        if (clazz == null) {
            throw new JUnitException("Class must not be null");
        }

        return new TestLogger(clazz.getName());
    }

    private static final class TestLogger implements Logger {

        private static final String FQCN = TestLogger.class.getName();

        private final String name;

        private final java.util.logging.Logger julLogger;

        TestLogger(String name) {
            this.name = name;
            this.julLogger = java.util.logging.Logger.getLogger(this.name);
        }

        @Override
        public void error(String tag, String message) {
            System.out.println(tag + ": " + message);

        }

        @Override
        public void warn(String tag, String message) {

        }

        @Override
        public void info(String message) {
            log(Level.INFO, null, message);
        }

        @Override
        public void info(Throwable throwable, String message) {
            log(Level.INFO, throwable, message);
        }

        @Override
        public void info(String tag, String message) {
            System.out.println(tag + ": " + message);
        }

        @Override
        public void config(String tag, String message) {

        }

        @Override
        public void debug(String tag, String message) {

        }

        @Override
        public void trace(String tag, String message) {

        }

        private void log(Level level, Throwable throwable, String msg) {
            boolean loggable = this.julLogger.isLoggable(level);
            if (loggable) {
                LogRecord logRecord = createLogRecord(level, throwable, msg);
            }
        }

        private LogRecord createLogRecord(Level level, Throwable throwable, String message) {
            String sourceClassName = null;
            String sourceMethodName = null;
            boolean found = false;
            for (StackTraceElement element : new Throwable().getStackTrace()) {
                String className = element.getClassName();
                if (FQCN.equals(className)) {
                    found = true;
                }
                else if (found) {
                    sourceClassName = className;
                    sourceMethodName = element.getMethodName();
                    break;
                }
            }

            LogRecord logRecord = new LogRecord(level, message);
            logRecord.setLoggerName(this.name);
            logRecord.setThrown(throwable);
            logRecord.setSourceClassName(sourceClassName);
            logRecord.setSourceMethodName(sourceMethodName);
            logRecord.setResourceBundleName(this.julLogger.getResourceBundleName());
            logRecord.setResourceBundle(this.julLogger.getResourceBundle());

            return logRecord;
        }
    }


}

Logger接口

package com.demo.test.util;

public interface Logger {
    void error(String tag, String message);
    void warn(String tag, String message);

    void info(String message);
    void info(Throwable throwable, String message);

    void info(String tag, String message);
    void config(String tag, String message);
    void debug(String tag, String message);
    void trace(String tag, String message);
}