在Android Studio中运行JUnit(方法)测试时,有没有办法打印出Logcat(Log.i,Log.d)消息?
我可以看到System.out.print消息但没有logcat打印输出。
在运行配置(Android Studio的GUI窗口)中,有针对Android测试的测试的logcat选项,但不适用于JUnit测试。
这有可能吗?谢谢你的任何提示!
答案 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概念,可以提取Logger
和UnitTestLogger
的通用界面。
也就是说,我从未遇到过在单元测试中调查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 Logger的ConsoleHandler来记录消息,而不是使用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