Cách mô phỏng phương thức e trong Nhật ký


81

Ở đây Utils.java là lớp của tôi sẽ được kiểm tra và sau đây là phương thức được gọi trong lớp UtilsTest. Ngay cả khi tôi đang chế nhạo phương thức Log.e như hình dưới đây

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

Tôi nhận được một ngoại lệ sau đây

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)

Câu trả lời:


155

Điều này đã làm việc cho tôi. Tôi chỉ sử dụng JUnit và tôi đã có thể mô phỏng Loglớp học mà không cần bất kỳ lib bên thứ ba nào rất dễ dàng. Chỉ cần tạo một tệp Log.javabên trong app/src/test/java/android/utilvới nội dung:

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
Điều này thật tuyệt vời. Và nó né tránh sự cần thiết của PowerMockito. 10/10
Sipty

1
Câu trả lời hay, Lý thuyết của tôi là Nếu bạn phải sử dụng Mock API trong Unit Testing thì mã của bạn không đủ tổ chức để có thể kiểm tra đơn vị. Nếu bạn đang sử dụng các thư viện bên ngoài thì hãy sử dụng kiểm tra Tích hợp với thời gian chạy và các đối tượng thực. Trong tất cả các Ứng dụng Android của mình, tôi đã tạo lớp trình bao bọc LogUtil cho phép nhật ký dựa trên cờ, điều này giúp tôi tránh chế nhạo lớp Nhật ký và bật / tắt nhật ký bằng cờ. Trong quá trình sản xuất, tôi vẫn xóa tất cả các câu lệnh nhật ký bằng progaurd.
MG Developer

4
@MGDevelopert bạn nói đúng. IMO nên hiếm khi sử dụng kỹ thuật / thủ thuật này. Ví dụ: tôi chỉ làm điều đó cho Loglớp vì quá phổ biến và việc chuyển một trình bao bọc Nhật ký ở khắp mọi nơi làm cho mã khó đọc hơn. Trong hầu hết các trường hợp, tiêm phụ thuộc nên được sử dụng để thay thế.
Paglian

5
Hoạt động tốt. Ngay trước khi bạn sao chép-dán nó, hãy thêm tên gói
Michał Dobi Dobrzański

1
Sử dụng @DavidKennedy @file:JvmName("Log")và các chức năng cấp cao nhất.
Miha_x64

40

Bạn có thể đưa cái này vào tập lệnh gradle của mình:

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

Điều đó sẽ quyết định xem các phương thức chưa được điều khiển từ android.jar có nên ném ngoại lệ hay trả về giá trị mặc định hay không.


26
Từ Tài liệu: Thận trọng: Việc đặt thuộc tính returnDefaultValues ​​thành true nên được thực hiện cẩn thận. Các giá trị trả về null / 0 có thể tạo ra các hồi quy trong các thử nghiệm của bạn, rất khó để gỡ lỗi và có thể cho phép các thử nghiệm không đạt. Chỉ sử dụng nó như một phương sách cuối cùng.
Manish Kumar Sharma

31

Nếu sử dụng Kotlin, tôi khuyên bạn nên sử dụng một thư viện hiện đại như mockk có tích hợp xử lý tĩnh và nhiều thứ khác. Sau đó, nó có thể được thực hiện với điều này:

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

Giới thiệu +1 tuyệt vời, các bài kiểm tra đã được thông qua nhưng lỗi đã được báo cáo!
MHSFisher

1
Nếu bạn muốn nắm bắt Log.w, hãy thêm:every { Log.w(any(), any<String>()) } returns 0
MrK

dường như không làm việc với Log.wtf( every { Log.wtf(any(), any<String>()) } returns 0): faills biên soạn với lỗi: Unresolved reference: wtf. IDE lint không nói gì trong mã. Bất kỳ ý tưởng ?
Mackovich

Brilliant +1 !! ... Điều này đã hiệu quả với tôi khi sử dụng Mockk.
RKS

Tôi có thể sử dụng mockk để thực hiện các cuộc gọi Log.*sử dụng println()để xuất ra Nhật ký dự định không?
Emil S.

26

Sử dụng PowerMockito :

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

Và bạn tốt để đi. Lưu ý rằng PowerMockito sẽ không tự động mô phỏng các phương thức tĩnh kế thừa, vì vậy nếu bạn muốn mô phỏng một lớp ghi nhật ký tùy chỉnh mở rộng Nhật ký, bạn vẫn phải giả lập Nhật ký cho các cuộc gọi như MyCustomLog.e ().


1
Làm thế nào bạn có được PowerMockRunner trong Gradle ??
IgorGanapolsky

4
@IgorGanapolsky Xem câu trả lời của tôi tại đây .
plátano plomo

Kiểm tra câu trả lời của tôi trong Kotlin đây cho mocking Log.e và Log.println
kosiara - Bartosz Kosarzycki

PowerMockito có còn là giải pháp phổ biến trong năm 2019 cho Kotiln không? Hoặc chúng ta nên xem xét các thư viện giả khác (tức là MockK).
IgorGanapolsky

8

Sử dụng 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
Điều đáng nói là do một lỗi, đối với JUnit 4.12, sử dụng PowerMock> = 1.6.1. Nếu không, hãy thử chạy với JUnit 4.11
manasouza

5

Sử dụng PowerMockmột có thể giả lập các phương thức tĩnh Log.i / e / w từ trình ghi nhật ký Android. Tất nhiên, lý tưởng nhất là bạn nên tạo một giao diện ghi nhật ký hoặc một mặt tiền và cung cấp cách ghi nhật ký đến các nguồn khác nhau.

Đây là một giải pháp hoàn chỉnh trong 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")
    }
}

hãy nhớ thêm các phụ thuộc trong 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'
}

Để giả lập Log.printlnphương pháp sử dụng:

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

Điều đó có thể bằng cách nào đó trong Java không?
Bowi

@Bowi: xem giải pháp giả của tôi Log.v với System.out.println trong Java mà cũng làm việc với JDK11 stackoverflow.com/a/63642300/3569768
Yingding Wang

4

Tôi khuyên bạn nên sử dụng gỗ để khai thác gỗ của bạn.

Mặc dù nó sẽ không ghi lại bất cứ thứ gì khi chạy các bài kiểm tra nhưng nó không làm thất bại các bài kiểm tra của bạn một cách không cần thiết như cách lớp Android Log làm. Timber cung cấp cho bạn nhiều quyền kiểm soát thuận tiện đối với cả gỡ lỗi và xây dựng sản xuất ứng dụng của bạn.


4

Nhờ câu trả lời của @Paglian và nhận xét của @ Miha_x64, tôi đã có thể làm cho điều tương tự hoạt động cho kotlin.

Thêm tệp Log.kt sau vào 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...

Và xin lỗi, các cuộc gọi của bạn đến Log.xxx nên gọi các hàm này để thay thế.


2

Mockito không chế nhạo các phương thức tĩnh. Sử dụng PowerMockito trên đầu trang. Đây là một ví dụ.


1
@ user3762991 Ngoài ra, bạn cần thay đổi các đối sánh của mình. Bạn không thể sử dụng trình so khớp trong thenReturn(...)câu lệnh. Bạn cần chỉ định một giá trị hữu hình. Xem thêm thông tin tại đây
troig

Nếu phương thức e, d, v không thể được mô phỏng, chỉ vì giới hạn này mà mockito trở nên không sử dụng được?
user3762991

2
Nếu bạn không thể ăn một cái nĩa, nó có trở nên không sử dụng được không? Nó chỉ đơn giản là có một mục đích khác.
Antiohia

1

Một giải pháp khác là sử dụng Robolectric. Nếu bạn muốn dùng thử, hãy kiểm tra thiết lập của nó .

Trong build.gradle của mô-đun của bạn, hãy thêm phần sau

testImplementation "org.robolectric:robolectric:3.8"

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

Và trong lớp thử nghiệm của bạn,

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

Trong các phiên bản mới hơn của Robolectric (được thử nghiệm với 4.3), lớp thử nghiệm của bạn sẽ trông như sau:

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

    // tests ...
}

0

Nếu bạn đang sử dụng org.slf4j.Logger, thì chỉ cần chế nhạo Logger trong lớp thử nghiệm bằng PowerMockito đã phù hợp với tôi.

@RunWith(PowerMockRunner.class)
public class MyClassTest {

@Mock
Logger mockedLOG;

...
}

0

Mở rộng câu trả lời từ kosiara cho việc sử dụng PowerMockMockito trong Java với JDK11 để mô phỏng android.Log.vphương pháp với System.out.printlnthử nghiệm đơn vị trong Android Studio 4.0.1.

Đây là một giải pháp hoàn chỉnh trong 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!");
    }

}

Hãy nhớ thêm các phần phụ thuộc vào tệp build.gradle mô-đun của bạn nơi tồn tại bài kiểm tra đơn vị của bạn:

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}"
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.