如何在日志中模拟方法e


80

这里的Utils.java是我要测试的类,下面是在UtilsTest类中调用的方法。即使我嘲笑Log.e方法,如下所示

 @Before
  public void setUp() {
  when(Log.e(any(String.class),any(String.class))).thenReturn(any(Integer.class));
            utils = spy(new Utils());
  }

我收到以下异常

java.lang.RuntimeException: Method e in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.util.Log.e(Log.java)
    at com.xxx.demo.utils.UtilsTest.setUp(UtilsTest.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Answers:


152

这为我解决了。我仅使用JUnit,并且能够非常轻松地模拟没有任何第三方libLog类。只要创建一个文件里面有内容:Log.javaapp/src/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;
    }

    // add other methods if required...
}

19
这是血腥的辉煌。并且它避免了对PowerMockito的需求。10/10
Sipty

1
好的答案,我的理论是,如果您必须在单元测试中使用Mock API,那么您的代码就组织得不够好,无法进行单元测试。如果使用外部库,则将集成测试与运行时和真实对象一起使用。在我所有的Android应用程序中,我都创建了一个包装类LogUtil,该包装类基于标志启用日志,这有助于避免模拟Log类并使用标志启用/禁用日志。在生产中,无论如何我都会用progaurd删除所有日志语句。
MG开发人员

4
@MGDevelopert你是对的。IMO几乎不应使用此技术/技巧。例如,我只对Log类这样做,因为它太普遍了,并且在各处传递Log包装器会使代码的可读性降低。在大多数情况下,应该使用依赖注入。
Paglian

5
效果不错。在复制粘贴之前,添加包名称:package android.util;
米哈尔DOBI多布然斯基

1
@DavidKennedy的使用@file:JvmName("Log")和顶级功能。
Miha_x64,19年

39

您可以将其放入gradle脚本中:

android {
   ...
   testOptions { 
       unitTests.returnDefaultValues = true
   }
}

这将决定android.jar中未模拟的方法是否应该引发异常或返回默认值。


25
从文档: 警告:将returnDefaultValues属性设置为true时应格外小心。空/零返回值会在测试中引入回归,这很难调试,并且可能允许失败的测试通过。仅将其用作万不得已的方法。
Manish Kumar Sharma

29

如果使用Kotlin,我建议您使用像mathk这样的现代库,它具有对静电和许多其他东西的内置处理。然后就可以做到这一点:

mockkStatic(Log::class)
every { Log.v(any(), any()) } returns 0
every { Log.d(any(), any()) } returns 0
every { Log.i(any(), any()) } returns 0
every { Log.e(any(), any()) } returns 0

很棒的介绍+1,测试通过了,但是错误已经报告了!
MHSFisher

1
如果要捕获Log.w,请添加:every { Log.w(any(), any<String>()) } returns 0
MrK

似乎不适用于Log.wtfevery { Log.wtf(any(), any<String>()) } returns 0):编译失败,错误为:Unresolved reference: wtf。IDE皮棉在代码中什么也没说。任何的想法 ?
Mackovich

辉煌+1 !! ...这在使用Mockk时对我有用。
RKS

我可以使用mockk拨打电话来Log.*使用println(),以输出预定的日志?
Emil S.

26

使用PowerMockito

@RunWith(PowerMockRunner.class)
@PrepareForTest({Log.class})
public class TestsToRun() {
    @Test
    public void test() {
        PowerMockito.mockStatic(Log.class);
    }
}

而且你很好。请注意,PowerMockito不会自动模拟继承的静态方法,因此,如果要模拟扩展Log的自定义日志记录类,则仍必须模拟Log以调用MyCustomLog.e()。


1
您如何在Gradle中获得PowerMockRunner
IgorGanapolsky

4
@IgorGanapolsky在这里查看我的答案。
普拉塔诺plomo

这里查看我在Kotlin中的 答案,以嘲笑Log.e和Log.println
kosiara-Bartosz Kosarzycki 18/09/5

PowerMockito在2019年仍然是Kotiln的流行解决方案吗?还是我们应该看看其他模拟库(即MockK)。
IgorGanapolsky '19

8

使用PowerMockito。

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassNameOnWhichTestsAreWritten.class , Log.class})
public class TestsOnClass() {
    @Before
    public void setup() {
        PowerMockito.mockStatic(Log.class);
    }
    @Test
    public void Test_1(){

    }
    @Test
    public void Test_2(){

    }
 }

1
值得一提的是,由于存在错误,对于JUnit 4.12,请使用PowerMock> = 1.6.1。否则,请尝试使用JUnit 4.11
manasouza

5

使用PowerMock一个可以模拟来自Android记录器的Log.i / e / w静态方法。当然,理想情况下,您应该创建一个日志记录界面或外观,并提供一种记录到不同来源的方法。

这是Kotlin中的完整解决方案:

import org.powermock.modules.junit4.PowerMockRunner
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest

/**
 * Logger Unit tests
 */
@RunWith(PowerMockRunner::class)
@PrepareForTest(Log::class)
class McLogTest {

    @Before
    fun beforeTest() {
        PowerMockito.mockStatic(Log::class.java)
        Mockito.`when`(Log.i(any(), any())).then {
            println(it.arguments[1] as String)
            1
        }
    }

    @Test
    fun logInfo() {
        Log.i("TAG1,", "This is a samle info log content -> 123")
    }
}

记得在gradle中添加依赖项:

dependencies {
    testImplementation "junit:junit:4.12"
    testImplementation "org.mockito:mockito-core:2.15.0"
    testImplementation "io.kotlintest:kotlintest:2.0.7"
    testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'
    testImplementation 'org.powermock:powermock-core:2.0.0-beta.5'
    testImplementation 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
    testImplementation 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
}

模拟Log.println方法使用:

Mockito.`when`(Log.println(anyInt(), any(), any())).then {
    println(it.arguments[2] as String)
    1
}

Java是否也有可能?
Bowi

@ Bowi:请参阅我的解决方案模拟Log.v和Java中的system.out.println,它也可以与JDK11一起使用stackoverflow.com/a/63642300/3569768
王应定

4

我建议您使用木材进行伐木。

尽管在运行测试时它不会记录任何内容,但是它不会像android Log类那样不必要地使测试失败。Timber使您可以方便地控制应用程序的调试和生产版本。


2

Mockito不会模拟静态方法。在顶部使用PowerMockito。是一个例子。


1
@ user3762991另外,您还需要更改匹配器。您不能在thenReturn(...)语句中使用匹配器。您需要指定一个有形值。在此处
troig '16

如果不能模拟e,d,v方法,仅由于此限制,模拟就变得不可用了吗?
user3762991 '16

2
如果您不能吃叉子,它会变得不可用吗?它只是有另一个目的。
Antiohia

2

感谢@Paglian的回答和@ Miha_x64的评论,我能够使Kotlin发挥同样的作用。

在下面添加以下Log.kt文件 app/src/test/java/android/util

@file:JvmName("Log")

package android.util

fun e(tag: String, msg: String, t: Throwable): Int {
    println("ERROR: $tag: $msg")
    return 0
}

fun e(tag: String, msg: String): Int {
    println("ERROR: $tag: $msg")
    return 0
}

fun w(tag: String, msg: String): Int {
    println("WARN: $tag: $msg")
    return 0
}

// add other functions if required...

而且,对Log.xxx的调用应改为调用这些函数。


1

另一种解决方案是使用Robolectric。如果要尝试,请检查其设置

在模块的build.gradle中,添加以下内容

testImplementation "org.robolectric:robolectric:3.8"

android {
  testOptions {
    unitTests {
      includeAndroidResources = true
    }
  }
}

在您的测试课中

@RunWith(RobolectricTestRunner.class)
public class SandwichTest {
  @Before
  public void setUp() {
  }
}

在Robolectric的较新版本(已通过4.3测试)中,测试类应如下所示:

@RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowLog.class)
public class SandwichTest {
    @Before
    public void setUp() {
        ShadowLog.setupLogging();
    }

    // tests ...
}

0

如果您使用的是org.slf4j.Logger,那么仅使用PowerMockito在测试类中模拟Logger即可。

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}

0

从扩展的答案kosiara使用PowerMock的Mockito的JavaJDK11嘲笑android.Log.v与方法System.out.println为单位Android Studio中4.0.1测试。

这是Java中的完整解决方案:

import android.util.Log;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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.ArgumentMatchers.any;

@RunWith(PowerMockRunner.class)
@PrepareForTest(Log.class)
public class MyLogUnitTest {
    @Before
    public void setup() {
        // mock static Log.v call with System.out.println
        PowerMockito.mockStatic(Log.class);
        Mockito.when(Log.v(any(), any())).then(new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                String TAG = (String) invocation.getArguments()[0];
                String msg = (String) invocation.getArguments()[1];
                System.out.println(String.format("V/%s: %s", TAG, msg));
                return null;
            }
        });
    }

    @Test
    public void logV() {
        Log.v("MainActivity", "onCreate() called!");
    }

}

切记在存在单元测试的模块build.gradle文件中添加依赖项:

dependencies {
    ...

    /* PowerMock android.Log for OpenJDK11 */
    def mockitoVersion =  "3.5.7"
    def powerMockVersion = "2.0.7"
    // optional libs -- Mockito framework
    testImplementation "org.mockito:mockito-core:${mockitoVersion}"
    // optional libs -- power mock
    testImplementation "org.powermock:powermock-module-junit4:${powerMockVersion}"
    testImplementation "org.powermock:powermock-api-mockito2:${powerMockVersion}"
    testImplementation "org.powermock:powermock-module-junit4-rule:${powerMockVersion}"
    testImplementation "org.powermock:powermock-module-junit4-ruleagent:${powerMockVersion}"
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.