在android studio junit test中记录消息

时间:2016-02-22 19:32:19

标签: android android-studio junit logcat

在Android Studio中运行JUnit(方法)测试时,有没有办法打印出Logcat(Log.i,Log.d)消息?

我可以看到System.out.print消息但没有logcat打印输出。

在运行配置(Android Studio的GUI窗口)中,有针对Android测试的测试的logcat选项,但不适用于JUnit测试。

这有可能吗?谢谢你的任何提示!

6 个答案:

答案 0 :(得分:8)

单元测试中不会出现Logcat输出,因为Logcat是Android功能--JUnit测试只能使用标准Java,因此Android功能无法正常工作。

你在单元测试中可以做的是注入"测试双打"进入测试组件。但Log.x来电是静态的,因此您无法覆盖它们(无需解析为PowerMock,您应该不惜一切代价避免这种情况)。

因此,第一步是引入一个非静态类,它将作为Log.x调用的代理:

/**
 * This class is a non-static logger
 */
public class Logger {

    public void e(String tag, String message) {
        Log.e(tag, message);
    }

    public void w(String tag, String message) {
        Log.w(tag, message);
    }

    public void v(String tag, String message) {
        Log.v(tag, message);
    }

    public void d(String tag, String message) {
        Log.d(tag, message);
    }
}

在您现在有Log.x个电话的每个地方使用此课程。

第二步是编写重定向到标准输出的Logger的测试双实现:

public class UnitTestLogger extends Logger{

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

    // similar for other methods
}

最后一步是在单元测试中注入UnitTestLogger而不是Logger

@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {

    private Logger mLogger = new UnitTestLogger();

    private SomeClass SUT;

    @Before
    public void setup() throws Exception {
        SUT = new SomeClass(/* other dependencies here */ mLogger);
    }

}

如果您想严格遵守OOP概念,可以提取LoggerUnitTestLogger的通用界面。

也就是说,我从未遇到过在单元测试中调查Log.x次调用的需要。我怀疑你也不需要它。您可以在调试模式下运行单元测试,并在调试器中逐行遍历代码,这比尝试调查logcat输出要快得多......

一般建议:

如果您正在测试的代码包含Log.x静态调用,并且您的单元测试没有崩溃 - 那么您就遇到了问题。

我猜测所有测试都是使用Robolectric运行的,或者您在build.gradle中有此语句:unitTests.returnDefaultValues = true

如果您使用Robolectric运行所有测试,那么它只是效率不高,但如果所有Android调用都返回默认值,那么测试套件不可靠。我建议你在继续进行之前解决这个问题,因为将来会以这种或那种方式咬你。

答案 1 :(得分:2)

我正在寻找同样的事情,并没有找到一个正确的答案。我知道这个问题已经过了一年多了,但是,在这里找到答案以备将来参考会很好。

android.util.Log类直接登录到Logcat,并且在本地JVM上运行单元测试时,android.util.Log的实现不可用。尝试在单元测试中使用Log类时会收到错误,因为"用于运行单元测试的android.jar文件不包含任何实际代码(这些API仅由Android系统提供设备上的图像)。"
See Android Documentation on Unit Testing

因此,如果你真的想使用android.util.Log,你需要在本地模拟它并利用System.out.print打印到控制台。首先,将PowerMockito添加到您的项目中。如果您正在使用Gradle,则只需添加以下依赖项:

testCompile 'junit:junit:4.12'
testCompile 'org.powermock:powermock:1.6.5'
testCompile 'org.powermock:powermock-module-junit4:1.6.5'
testCompile 'org.powermock:powermock-api-mockito:1.6.5'

接下来,我使用Steve的回答here来确定如何使用Mockito返回传递给模拟对象的参数。

结果如下:

import android.util.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public class SomeUnitTest {

    @Test
    public void testSomething() {
        System.out.println("Running test"); 
        PowerMockito.mockStatic(Log.class);

        // Log warnings to the console        
        when(Log.w(anyString(), anyString())).thenAnswer(new Answer<Void>()      {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                if (args.length > 1) { //cause I'm paranoid
                    System.out.println("Tag:" + args[0] + " Msg: " + args[1]);
                }
                return null;
            }
        });
        Log.w("My Tag", "This is a warning");
    }
}

希望这有助于某人!

答案 2 :(得分:2)

根据这篇文章https://medium.com/@gal_41749/android-unitests-and-log-class-9546b6480006

只需创建 Log.java 文件并将其放入 test/java/android/util/

package android.util;

public class Log {
    public static int d(String tag, String msg) {
        System.out.println("DEBUG: " + tag + ": " + msg);
        return 0;
    }

    public static int i(String tag, String msg) {
        System.out.println("INFO: " + tag + ": " + msg);
        return 0;
    }

    public static int w(String tag, String msg) {
        System.out.println("WARN: " + tag + ": " + msg);
        return 0;
    }

    public static int e(String tag, String msg) {
        System.out.println("ERROR: " + tag + ": " + msg);
        return 0;
    }
    
}

答案 3 :(得分:1)

我会使用@Vasiliy的方法,但会有一点点变化。实际上,我们可以使用配置了Java LoggerConsoleHandler来记录消息,而不是使用System.out.println进行记录。

这可以通过下面列出的简单步骤来实现

步骤1: 定义自己的日志级别

  public enum LogLevel {
    VERBOSE,
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    SILENT
}

步骤2: 定义Logger抽象

public abstract class Logger {

    ....

    public abstract void debug(String tag, String message);

    public abstract void error(String tag, String message);
   ....

}

第3步: Logger的基于java.util.Logging的实现

public class JavaLogger extends Logger {

    private java.util.logging.Logger mLogger;

    public JavaLogger(LogLevel logLevel, String name) {
        mLogger = java.util.logging.Logger.getLogger(name);
        ConsoleHandler handler = new ConsoleHandler();
        Level level = mapLogLevel(logLevel);
        handler.setLevel(level);
        mLogger.addHandler(handler);
        mLogger.setLevel(level);
    }

    @Override
    public void debug(String tag, String message) {
        if (isLoggable(LogLevel.DEBUG)) {
            mLogger.finer(message);
        }
    }

    @Override
    public void error(String tag, String message) {
        if (isLoggable(LogLevel.ERROR)) {
            mLogger.severe(message);
        }
    }

    ....

    private Level mapLogLevel(LogLevel logLevel) {
        Level level = Level.OFF;
        switch (logLevel) {
            case INFO:
                level = Level.INFO;
                break;

            case WARNING:
                level = Level.WARNING;
                break;

            case ERROR:
                level = Level.SEVERE;
                break;

            case VERBOSE:
                level = Level.FINEST;
                break;

            case DEBUG:
                level = Level.FINER;
                break;

            case SILENT:
                level = Level.OFF;
                break;

            default:
                // no impl
        }

        return level;
    }
}

第4步: Logger的基于android.util.Log的实现

public class AndroidLogger extends Logger {

public AndroidLogger(LogLevel logLevel) {
    super(logLevel);
}

 ....

@Override
public void debug(String tag, String message) {
    if (isLoggable(LogLevel.DEBUG)) {
        Log.d(tag, message, null);
    }
}

@Override
public void error(String tag, String message) {
    if (isLoggable(LogLevel.ERROR)) {
        Log.e(tag, message, tr);
    }
}

....

}

第5步: 为Logger的实现创建一个简单的包装器类

   public class AppLogger { 

    private static Logger sLogger;

   public static void init(Logger logger) {
        sLogger = logger;
    }

   public static void debug(final String tag, String message) {
        sLogger.debug(tag, message);
    }

    public static void error(final String tag, String message) {
        sLogger.error(tag, message, null);
    }

    public static void error(final String tag, String message, Throwable t) {
        sLogger.error(tag, message, t);
    }

    ... 
}

步骤6:初始化

Android

应用程序onCreate方法

AppLogger.init(new AndroidLogger(LogLevel.DEBUG));

Junit

AppLogger可以在@BeforeClass或@Before中初始化

AppLogger.init(new JavaLogger(LogLevel.DEBUG, "test-logger"));

现在您可以在Android Studio的“测试运行”窗口中观察日志消息

答案 4 :(得分:0)

如果您仍然喜欢使用该黑客解决方案(使用PowerMockito),则可以实现以下类来模拟所有android日志功能。

import android.util.Log;

import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.HashMap;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyString;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public abstract class AndroidLogMock {

    private static HashMap<Integer, String> LOG_LEVELS = new HashMap<Integer, String>(){{
        put(Log.VERBOSE, "VERBOSE");
        put(Log.DEBUG, "DEBUG");
        put(Log.INFO, "INFO");
        put(Log.WARN, "WARN");
        put(Log.ERROR, "ERROR");
        put(Log.ASSERT, "ASSERT");
    }};

    private static Answer<?>  getLogInvocation(int logLevel) {
        return (InvocationOnMock invocation) -> {
            Object[] args = invocation.getArguments();

            if(args.length > 1 && args.length < 3) {
                AndroidLogMock.log(logLevel, args[0].toString(), args[1].toString());
            } else if (args.length > 1 && args[2] instanceof Throwable) { //cause I'm paranoid
                AndroidLogMock.log(logLevel, args[0].toString(), args[1].toString(), (Throwable) args[2]);
            } else {
                new Exception("no matching function found with correct number and/or type of arguments");
            }

            return null;
        };
    }

    private static void log(int logLevel, String tag, String message) {
        System.out.println("[" + LOG_LEVELS.get(logLevel) + "}" + " Tag:" + tag + " Msg: " + message);
    }

    private static void log(int logLevel, String tag, String message, Throwable throwable) {
        AndroidLogMock.log(logLevel, tag, message);

        System.out.println("Exception: ");
        throwable.printStackTrace();
    }

    @BeforeClass
    public static void setupLogMocks() {
        PowerMockito.mockStatic(Log.class);

        when(Log.v(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.VERBOSE));
        when(Log.v(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.VERBOSE));

        when(Log.d(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.DEBUG));
        when(Log.d(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.DEBUG));

        when(Log.i(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.INFO));
        when(Log.i(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.INFO));

        when(Log.w(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.WARN));
        when(Log.w(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.WARN));

        when(Log.e(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.ERROR));
        when(Log.e(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.ERROR));

        when(Log.wtf(anyString(), anyString())).thenAnswer(AndroidLogMock.getLogInvocation(Log.ASSERT));
        when(Log.wtf(anyString(), anyString(), any(Throwable.class))).thenAnswer(AndroidLogMock.getLogInvocation(Log.ASSERT));
    }
}

含进口:

testImplementation 'junit:junit:4.12'
testImplementation 'org.powermock:powermock-core:2.0.4'
testImplementation 'org.powermock:powermock-module-junit4:2.0.4'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.4'

要使用,请扩展您的UnitTest类:

public class SomeUnitTest extends AndroidLogMock {

    @Test
    public void testSomething() {
       Log.w("My Tag", "This is a warning");
       Log.e("My Tag", "This is an error", new Exception("Error"));
    }

}

但是@Vasiliy是严厉的,遵循OOP的方式比较干净。

答案 5 :(得分:0)

在启动时将 JVM 选项设置为:

  -Drobolectric.logging=stdout

登录到 Log.x() 将进入标准输出。对于更多选项,Roboelectric ShadowLog 类实现了 android.util.Log

http://robolectric.org/javadoc/3.0/org/robolectric/shadows/ShadowLog.html