Як знущатися над методом e у журналі


81

Тут 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)

Відповіді:


155

Це в мене вийшло. Я використовую лише JUnit, і мені вдалося знущатися над Logкласом без будь-якої сторонньої бібліотеки дуже просто. Просто створіть файл Log.javaвсередині app/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...
}

20
Це криваво блискуче. І це ухиляється від потреби в PowerMockito. 10/10
Сіпті

1
Гарна відповідь, моя теорія полягає в тому, що якщо вам доведеться використовувати Mock API в модульному тестуванні, тоді ваш код недостатньо організований, щоб його можна було перевірити. Якщо ви використовуєте зовнішні бібліотеки, то використовуйте тести інтеграції з середовищем виконання та реальними об'єктами. У всіх своїх програмах для Android я створив клас обгортки LogUtil, який включає журнали на основі прапора, це допомагає мені уникнути глузування класу журналу та вмикати / вимикати журнали з прапором. У виробництві я все одно видаляю всі журнали з прогаурдом.
MG Developer

4
@MGDevelopert ви маєте рацію. ІМО цю техніку / фокус слід застосовувати навряд чи. Наприклад, я роблю це лише для Logкласу, оскільки занадто повсюдно, і передача оболонки журналу скрізь робить код менш читабельним. У більшості випадків замість цього слід застосовувати ін’єкцію залежності.
Paglian

5
Працює добре. Перед тим, як скопіювати-вставити, додайте ім'я пакета
Міхал Добі Добжанський

1
@DavidKennedy використання @file:JvmName("Log")та функції верхнього рівня.
Miha_x64

40

Ви можете помістити це у свій сценарій gradle:

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

Це вирішить, чи слід відміняти методи з android.jar видавати винятки або повертати значення за замовчуванням.


26
З Документів: Застереження. Встановлення властивості returnDefaultValues ​​в значення true має виконуватися обережно. Повернені значення нуль / нуль можуть вводити регресії у ваші тести, які важко налагоджувати і можуть дозволити проходження невдалих тестів. Використовуйте його лише в крайньому випадку.
Manish Kumar Sharma

31

Якщо ви використовуєте Kotlin, я б порадив використовувати сучасну бібліотеку, як mockk, яка має вбудовану обробку статики та багато іншого. Тоді це можна зробити за допомогою цього:

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.wtf( every { Log.wtf(any(), any<String>()) } returns 0): компіляції faills з помилкою: Unresolved reference: wtf. IDE lint нічого не говорить у коді. Будь-яка ідея?
Мачкович

Блискуче +1 !! ... Це працювало у мене під час використання Mockk.
RKS

Можу чи я використовувати mockk робити виклики Log.*використовувати println()для виведення призначений Ввійти?
Еміль С.

26

Використання PowerMockito :

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

І вам добре їхати. Зауважте, що PowerMockito не буде автоматично знущатися над успадкованими статичними методами, тому, якщо ви хочете знущатись над власним класом ведення журналу, який розширює журнал, ви все одно повинні знущатись над журналом для таких викликів, як MyCustomLog.e ().


1
Як ви отримали PowerMockRunner у Gradle ??
Ігор Ганапольський

4
@IgorGanapolsky Дивіться мою відповідь тут .
plátano plomo

Перевірте моя відповідь в Котлин тут за глузливий Log.e і Log.println
kosiara - Бартош Kosarzycki

Чи є PowerMockito все ще популярним рішенням у 2019 році для Kotiln? Або ми повинні подивитися на інші бутафорські бібліотеки (тобто MockK).
Ігор Ганапольський

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одного можна знущатись із статичних методів Log.i / e / w із реєстратора Android. Звичайно, в ідеалі вам слід створити інтерфейс реєстрації або фасад і забезпечити спосіб ведення журналу до різних джерел.

Це повне рішення в 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: см моє рішення знущатися Log.v з System.out.println в Java , який також працює з JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang

4

Я б рекомендував використовувати деревину для вашої лісозаготівлі.

Хоча він нічого не реєструє під час запуску тестів, але не провалює тести без потреби, як це робить клас Android Log. Лісоматеріали надають вам багато зручного контролю як над налагодженням, так і над виробничим складом вашого додатка.


4

Завдяки відповіді @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 повинні натомість викликати функції тез.


2

Mockito не висміює статичні методи. Зверху використовуйте PowerMockito. Ось приклад.


1
@ user3762991 Також вам потрібно змінити збіги. Ви не можете використовувати відповідник у thenReturn(...)виписці. Потрібно вказати відчутну вартість. Більше інформації дивіться тут
troig

Якщо метод e, d, v не можна знущатись, лише через це обмеження mockito стає непридатним для використання?
користувач3762991

2
Якщо ви не можете з'їсти виделку, вона стає непридатною для використання? Це просто має інше призначення.
Антіохія,

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, тоді просто знущання над Logger у тестовому класі за допомогою PowerMockito мені спрацювало.

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}

0

Розширення відповіді від kosiara за використання PowerMock та Mockito в Java з JDK11, щоб знущатися над 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}"
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.