Android unit testing tips

In the past, the single test was done to complete the task, but now it is actually to test the correctness of the code, and it is easy to encounter pitfalls. I just didn’t write a summary before, and now I remember some good single test methods, as well as the pitfalls I stepped on.

1. Log output

I think this is the most important thing. When testing the code, you must judge where the code goes according to the output log. Before, you paid too much attention to the line coverage, but you didn’t pay much attention to this part. It took a lot of time to deal with this part.

Log.xxx cannot output logs to the console, only System.out.print can be seen, so we need a static method of mock Log to convert its output to System.out.print:

PowerMockito.mockStatic(Log.class);
Mockito.when(Log.e(anyString(), anyString())).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.err.println(String.format("Error: /%s: %s", TAG, msg));
        return null;
    }
});

Other log levels are similar, and some main methods can be written in a class, which is convenient for us to call later.

It should be noted here that PowerMockito cannot mock the return value to be empty (maybe I didn't find it?).

2. Test template

Generally, Robolectric and PowerMock are used together, but some parameters must be set when matching, otherwise the two are likely to conflict. For example, when the two are together, the RuntimeEnvironment.application under Robolectric will become null, so a template is prepared. Equivalent to the integration test environment:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
// 这个就是为了避免PowerMock把Robolectric的一些东西mock掉
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({Log.class})

public class TestTemplate {
    // 这个相当于setUp()函数,不加是mock不了的.
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockLogUtils.mockLog();
    }
}

rely:

testImplementation "org.powermock:powermock-module-junit4:1.6.6"
testImplementation "org.powermock:powermock-module-junit4-rule:1.6.6"
testImplementation "org.powermock:powermock-api-mockito:1.6.6"
testImplementation "org.powermock:powermock-classloading-xstream:1.6.6"
testImplementation 'org.robolectric:robolectric:3.8'
// 以下非必须,robolectric还有很多这样的影子库
testImplementation 'org.robolectric:shadows-httpclient:3.3.2'

PS: This template has a flaw. If you want to mock a Context, it will conflict with robo. In this case, create another test class and use PowerMock.runner to test it.

3. Test skills:

(1) In addition to covering branches, you also need to consider boundaries:
passing empty parameters, calling functions multiple times, paying attention to execution time, etc. (let’s add together)

(2) Whitebox is a good thing:
it completes a lot of reflection functions, setInternalState and getInternalState, which can directly help us set and obtain property changes in the object under test. After some functions are executed, they can be verified with verify or assert .

(3) Test exception throwing:
In Test(expect = Exception.class), expect adds the exception you want.

(4) The difference between when...thenReturn and doReturn...when:
(a)when(...) thenReturn(...) will call the real method, if you don't want to call the real method but want to mock, don't use this method;
(b)doReturn(…) when(…) will not call the real method

(5) The problem of PrepareForTest.
Recently, I found a problem. I added the annotation PrepareForTest({Log.class}) at the class level. When testing a method, I also added a PrepareForTest({XXX.class}) at the method level. As a result, it said Log when running this method. The .class method does not have prepare. I don’t know why. I found that once the prepare is separated, it will not work. If the two classes are prepared on the class or method at the same time, it will be fine.

(6) Test asynchronous methods
If the method to be tested will be executed asynchronously, such as inside new Thread to execute, or use the thread pool to execute, normally the result cannot be seen, because the Test method will execute System.exit () drop, the child thread may not have been created at this time. You can live in Thread.sleep() at the end of the Test method and wait for the child thread to execute.

(7) Test Handler
Handler is unique to Android, so it is directly skipped during local single test. If you want to execute the callback of Handler is correct, you can mock it, and execute it manually after getting the parameters passed to it:

 @Mock
Handler handler;

@Before
@PrepareForTest({ Handler.class})
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    PowerMockito.mock(Handler.class);
    // mock它的post方法,其它类似
    PowerMockito.doAnswer(new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            Runnable runnable = (Runnable) invocation.getArguments()[0];
            runnable.run();
            return null;
        }
    }).when(handler).post((Runnable) any());
}

(8) Spy an object, and the returned object can be mocked. Here is the difference between spy and mock:
①The mocked object is completely fake, and any method that calls it will not be called. It is generally used for dependent objects of the tested object.
②spy, if you need to call a real method, you must actually create this class object, but at this time if you want to mock some of its methods, an error will be reported when calling when(obj), because obj cannot be mocked at this time , if you want to mock, just spy(obj), it will return a new obj, and you can mock it. Compared with the mocked object, it is "partial simulation". You didn't mock some of its methods, it still will call the real method.

Guess you like

Origin blog.csdn.net/aa642531/article/details/109256999