Unit testing for a period of time, I found that many online articles about unit testing are about the concept, about the use of the framework, but for an actual project operation, because of the complexity of the project, the stability of the framework, and so on, it is often impossible to carry out. This blog is based on a practical project summary.

This series of articles will not cover the concept of unit testing and its various practical implications. Only from the implementation of the start, on its merits do not do analysis.

The unit test series will be divided into three posts:

  • Android unit test research and selection
  • Based on thePowermockAndroid Unit Testing common methods guide
  • Based on theCobertra&sonarqubeUnit test coverage statistics for

Research and selection

The Official Google document provides support for unit testing. The test and androidTest directories are created by default when the project is created. Unit tests and integration tests, respectively. Unit tests are tests of methods that are small in granularity and do not need to be run on a real machine. Integration test needs to run on the real machine every time, with larger granularity and longer running time, and is not conducive to some automated work.

The core of this series starts with unit testing for automation purposes.

One of the most difficult topics in unit testing is the issue of Android.jar. Because of the android.* official class, when running a unit test, only the method declaration, all internal methods throw new RuntimeException(‘Stub’). An error is reported whenever an official class is called, such as a View, Intent, Activity, etc., causing the unit test to fail.

One common solution is to do this architecturally, decoupling some of the code logic from the official classes, such as MVP, and testing it for Presenter, because most of the logic is in Presenter, so it’s ok.

Unfortunately, when you do unit testing, it’s very difficult to start with a new project. The architecture is very difficult to change.

In our project, all the logic is written in the Activity, and once we test the logic, we can’t get around the official class.

There are two solutions to unit testing mentioned in the official documentation.

One way is to Mock it out and give the proxy a call from the official class without actually calling the related methods of the official class.

The other is Robolectric, which performs integration testing as a unit test by emulating the Android VIRTUAL machine on the JVM.

Robolectric(give up)

Since this framework was not used in the end, I will introduce it first.

The framework essentially builds an Android virtual machine that essentially runs an app when it runs unit tests. So its test logic is more inclined to APpium and other UI tests, query a control, simulate click, verify logic.

Because it simulates virtual machines, it extends the official method to provide a series of ShadowXXX classes for easy verification and emulation. Get the current pop-up dialog, the last pop-up toast, etc.

At the time of the research, the latest version of the framework is 4.3, and since 4.0, it has started to be compatible with the official test library under Androidx. test. Some column operations can be done through the official Espresso. So it’s nice to think that a set of code, skills that run on the console, can run on the simulator.

But!!

Because our projects are all written in the Activity, some business logic uses private methods, so it is very complicated to verify the correctness of logic through querying controls and displaying UI.

Here’s an example:

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.skip:
                startNextActivity(false);
                break;
            case R.id.splash_image:
                startNextActivity(true);
                break;
            default:
                break; }}private synchronized void startNextActivity(boolean isClick) {}
Copy the code

I want to verify that different views click and call startNextActivity with the correct parameters. If validation from the UI is complicated, and if startNextActivity’s internal logic is flawed or tricky, the UI validation may not be accurate.

Is there a solution? I’m sure there is. Mock.

The most common way to Mock private methods is Powermock, but !!!!!

The framework and Robolectric had various compatibility issues that didn’t get resolved after my hair fell out all over the place.

So we gave up !!!! Note the abandonment of Robolectric!!!!

Mock (use)

Mock is a common method used in unit testing. It involves modifying the method to be called, simulating the return value of the called method, and so on. I won’t talk nonsense here.

The official documentation recommends using Mockito for mock operations, but the library does not support mocks for static methods, private methods, final, etc.

Powermock was mentioned above, which provides more powerful mocks and Mockito support, which is basically the same.

For example, for the code above, use the Powermock method to verify the logic as follows:

    @Test
    public void onClickSkip(a) throws Exception {
        // Mock Activity, all methods of the activity will not be executed
        LauncherActivity activity = PowerMockito.mock(LauncherActivity.class);
        // Specify that the activity's onClick is not mock to invoke real logic for unit testing
        PowerMockito.doCallRealMethod().when(activity, "onClick", ArgumentMatchers.any(View.class));

		 // Mock official class
        View view = PowerMockito.mock(View.class);
        // Specify the return value of getId
        PowerMockito.doReturn(R.id.skip).when(view, "getId");
		 // Call the test method
        activity.onClick(view);
     	 // Verify that the specified method and argument are used
        PowerMockito.verifyPrivate(activity).invoke("startNextActivity".false);
    }

Copy the code

Code comments are clear, no nonsense.

Along these lines, you can actually validate most of the unit test logic.

conclusion

With that in mind, I decided to write unit tests based on Powermock.