【问题标题】:Log messages in android studio junit test在 android studio junit 测试中记录消息
【发布时间】:2016-06-04 09:17:02
【问题描述】:

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

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

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

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

【问题讨论】:

  • 你知道怎么做了吗?我现在面临同样的问题......
  • @IgorGanapolsky,你可以在下面看到我的答案
  • 不知道你有没有得到答案,但你可以在下面查看我的答案
  • 你可以在 Kotlin 中使用 println(...)

标签: android android-studio junit logcat


【解决方案1】:

在单元测试中不会看到 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 调用都返回默认值,那么您的测试套件不可靠。我建议你先解决这个问题,然后再继续,因为它会在未来以这样或那样的方式咬你。

【讨论】:

  • 您可以完全避免更改 Log 调用,只需将 Logger 实例命名为 Log 而不是 mLLogger。
  • 为了清楚起见,我会将UnitTestLogger 重命名为ConsoleLogger
  • @Eido95 我同意ConsoleLogger 将是一个更具描述性的名称。
【解决方案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】:

    我一直在寻找同样的东西,但从未找到直接的答案。我知道这个问题已有一年多的历史了,但仍然可以在这里找到答案以供将来参考。

    在本地 JVM 上运行单元测试时,android.util.Log 类直接记录到 Logcat,并且 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'
    

    接下来,我使用史蒂夫的回答 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");
        }
    }
    

    希望这对某人有所帮助!

    【讨论】:

    • 我建议不惜一切代价避免使用 PowerMock,并使用标准的 OOP 实践来实现所需的行为
    • “我建议不惜一切代价避免使用 PowerMock” - 好吧,你解释一下为什么?
    • 正如我所说:“使用标准的 OOP 实践来实现所需的行为”。 “破解”字节码看似简单的解决方案,其实不然,而且这条路很草率。有关如何正确执行此操作的详细信息,请参阅我的答案
    【解决方案4】:

    我会采用@Vasiliy 的方法,但会稍作改动。除了使用System.out.println 进行日志记录,我们实际上可以使用Java Logger 配置ConsoleHandler 在测试输出屏幕上记录消息。

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

    第 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 步: 基于 java.util.Logging 的 Logger 实现

    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 步: 基于 android.util.Log 的 Logger 实现

    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 步: 为 over 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 步:初始化

    安卓

    应用onCreate方法

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

    朱尼特

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

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

    现在您可以在 Android Studio 的 Test Run Window 中观察日志消息

    【讨论】:

      【解决方案5】:

      如果您仍然不喜欢黑客解决方案(使用 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 方式更清洁。

      【讨论】:

        【解决方案6】:

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

          -Drobolectric.logging=stdout
        

        并且记录到Log.x() 将进入标准输出。更多选项,Roboelectric ShadowLog 类实现android.util.Log

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多