Which of the following is difficult for Android to perform unit tests?-part3

Source: Internet
Author: User

Which of the following is difficult for Android to perform unit tests?-part3

Original article: how to make our android apps unit testable (PT. 1) Author: Matthew Dupree Translated by: Development Technology frontline www.devtf.cn Translator: chaossss Proofreader: tiiime status: Completed

It is difficult to perform unit tests in Android applications, sometimes even impossible. In the previous two blog posts, I have explained to you why unit testing in Android is so difficult. In the previous blog post, we analyzed and concluded that it was the application architecture officially promoted by Google that turned unit testing in Android into a disaster. Because Google seems to want us to put business logic in the component classes of applications (such as Activity, Fragment, Service, etc.) in the officially promoted architecture ......). This development method is also a development template we have been using.

In this blog post, I will list several methods for building Android applications. Using these methods for development makes unit testing easier. But as I said in the preface, the best way I advocate is always Square's blog post: Square: abandon Fragment from today! The general method used in. Because this method was developed by Android development engineers in Square, I will refer to this method in the following blog post as "Square Dafa ".

The core idea of the Square algorithm is to remove all the business logic in the Application Component class (such as Activity, Fragment, Service, etc ......), In addition, the business logic is transferred to business objects, which are pure Java objects injected by dependencies and Android-independent interfaces. If we use the Square method when developing applications, it would be much easier to perform unit tests. In this blog post, I will explain how Square Dafa helps us reconstruct the UI-independent application components (for example, SessionCalendarService we discussed in the previous blog ), it also makes unit tests easier.

Rebuilding UI-independent application components using the Square algorithm

It is relatively easy to reconstruct the UI-independent application components such as Service, ContentProvider, and BroadcastReceiver using the Square algorithm. Let me talk about it again: remove the business logic in these classes and put them in the Business Objects.

Because "business logic" is a word that is easily ambiguous, let me explain what it represents when I use the word "business logic. When I mention "business logic", its meaning is the same as that on Wikipedia: The program uses the rules in the real world to determine how data will be created and displayed, the part of the code that is stored and modified. Now we can reach a consensus on the meaning of the word "business logic". Let's see what the Square method is.

Let's take a look at how to use the Square algorithm to implement the SessionCalendarService I introduced in my previous blog. The specific code is as follows:

/** * Background {@link android.app.Service} that adds or removes session Calendar events through * the {@link CalendarContract} API available in Android 4.0 or above. */public class SessionCalendarService extends IntentService {    private static final String TAG = makeLogTag(SessionCalendarService.class);    //...    public SessionCalendarService() {        super(TAG);    }    @Override    protected void onHandleIntent(Intent intent) {        final String action = intent.getAction();        Log.d(TAG, "Received intent: " + action);        final ContentResolver resolver = getContentResolver();        boolean isAddEvent = false;        if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {            isAddEvent = true;        } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {            isAddEvent = false;        } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action) &&                PrefUtils.shouldSyncCalendar(this)) {            try {                getContentResolver().applyBatch(CalendarContract.AUTHORITY,                        processAllSessionsCalendar(resolver, getCalendarId(intent)));                sendBroadcast(new Intent(                        SessionCalendarService.ACTION_UPDATE_ALL_SESSIONS_CALENDAR_COMPLETED));            } catch (RemoteException e) {                LOGE(TAG, "Error adding all sessions to Google Calendar", e);            } catch (OperationApplicationException e) {                LOGE(TAG, "Error adding all sessions to Google Calendar", e);            }        } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {            try {                getContentResolver().applyBatch(CalendarContract.AUTHORITY,                        processClearAllSessions(resolver, getCalendarId(intent)));            } catch (RemoteException e) {                LOGE(TAG, "Error clearing all sessions from Google Calendar", e);            } catch (OperationApplicationException e) {                LOGE(TAG, "Error clearing all sessions from Google Calendar", e);            }        } else {            return;        }        final Uri uri = intent.getData();        final Bundle extras = intent.getExtras();        if (uri == null || extras == null || !PrefUtils.shouldSyncCalendar(this)) {            return;        }        try {            resolver.applyBatch(CalendarContract.AUTHORITY,                    processSessionCalendar(resolver, getCalendarId(intent), isAddEvent, uri,                            extras.getLong(EXTRA_SESSION_START),                            extras.getLong(EXTRA_SESSION_END),                            extras.getString(EXTRA_SESSION_TITLE),                            extras.getString(EXTRA_SESSION_ROOM)));        } catch (RemoteException e) {            LOGE(TAG, "Error adding session to Google Calendar", e);        } catch (OperationApplicationException e) {            LOGE(TAG, "Error adding session to Google Calendar", e);        }    }    //...}

As you can see, SessionCalendarService calls the helper method to be defined later. Once we take the fields declaration of these helper methods and classes into account, the Service class code has more than 400 lines. It is not a simple task to hold the business logic that occurs in such a large class, and as we saw in the previous blog, unit testing in SessionCalendarService is simply a fantasy.

Now let's take a look at how the code will be implemented using the Square method. I once again stressed that the Square algorithm requires us to migrate the business logic in the Android class to a business object. Here, the business object corresponding to SessionCalendarService is SessionCalendarUpdater. The Code is as follows:

public class SessionCalendarUpdater {    //...    private SessionCalendarDatabase mSessionCalendarDatabase;    private SessionCalendarUserPreferences mSessionCalendarUserPreferences;    public SessionCalendarUpdater(SessionCalendarDatabase sessionCalendarDatabase,                                  SessionCalendarUserPreferences sessionCalendarUserPreferences) {        mSessionCalendarDatabase = sessionCalendarDatabase;        mSessionCalendarUserPreferences = sessionCalendarUserPreferences;    }    public void updateCalendar(CalendarUpdateRequest calendarUpdateRequest) {        boolean isAddEvent = false;        String action = calendarUpdateRequest.getAction();        long calendarId = calendarUpdateRequest.getCalendarId();        if (ACTION_ADD_SESSION_CALENDAR.equals(action)) {            isAddEvent = true;        } else if (ACTION_REMOVE_SESSION_CALENDAR.equals(action)) {            isAddEvent = false;        } else if (ACTION_UPDATE_ALL_SESSIONS_CALENDAR.equals(action)                && mSessionCalendarUserPreferences.shouldSyncCalendar()) {            try {                mSessionCalendarDatabase.updateAllSessions(calendarId);            } catch (RemoteException | OperationApplicationException e) {                LOGE(TAG, "Error adding all sessions to Google Calendar", e);            }        } else if (ACTION_CLEAR_ALL_SESSIONS_CALENDAR.equals(action)) {            try {                mSessionCalendarDatabase.clearAllSessions(calendarId);            } catch (RemoteException | OperationApplicationException e) {                LOGE(TAG, "Error clearing all sessions from Google Calendar", e);            }        } else {            return;        }        if (!shouldUpdateCalendarSession(calendarUpdateRequest, mSessionCalendarUserPreferences)) {            return;        }        try {            CalendarSession calendarSessionToUpdate = calendarUpdateRequest.getCalendarSessionToUpdate();            if (isAddEvent) {                mSessionCalendarDatabase.addCalendarSession(calendarId, calendarSessionToUpdate);            } else {                mSessionCalendarDatabase.removeCalendarSession(calendarId, calendarSessionToUpdate);            }        } catch (RemoteException | OperationApplicationException e) {            LOGE(TAG, "Error adding session to Google Calendar", e);        }    }    private boolean shouldUpdateCalendarSession(CalendarUpdateRequest calendarUpdateRequest,                                                 SessionCalendarUserPreferences sessionCalendarUserPreferences) {        return calendarUpdateRequest.getCalendarSessionToUpdate() == null || !sessionCalendarUserPreferences.shouldSyncCalendar();    }}

I want to emphasize some of the key points: First, we do not need to use any new keywords because the dependencies of business objects are injected, it does not use new keywords at all, and this is the key to enabling class unit testing. Second, you will notice that the class does not depend exactly on the Android SDK, because the business objects are dependent on Android-specific implementations of Android-independent interfaces, so it does not need to depend on Android SDK.

How are these dependencies added to the SessionCalendarUpdater class? It is injected through the SessionCalendarService class:

/** * Background {@link android.app.Service} that adds or removes session Calendar events through * the {@link CalendarContract} API available in Android 4.0 or above. */public class SessionCalendarService extends IntentService {    private static final String TAG = makeLogTag(SessionCalendarService.class);    public SessionCalendarService() {        super(TAG);    }    @Override    protected void onHandleIntent(Intent intent) {        final String action = intent.getAction();        Log.d(TAG, "Received intent: " + action);        final ContentResolver resolver = getContentResolver();        Broadcaster broadcaster = new AndroidBroadcaster(this);        SessionCalendarDatabase sessionCalendarDatabase = new AndroidSessionCalendarDatabase(resolver,                                                                                             broadcaster);        SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);        SessionCalendarUserPreferences sessionCalendarUserPreferences = new AndroidSessionCalendarUserPreferences(defaultSharedPreferences);        SessionCalendarUpdater sessionCalendarUpdater                                    = new SessionCalendarUpdater(sessionCalendarDatabase,                                                                 sessionCalendarUserPreferences);        AccountNameRepository accountNameRepository = new AndroidAccountNameRepository(intent, this);        String accountName = accountNameRepository.getAccountName();        long calendarId = sessionCalendarDatabase.getCalendarId(accountName);        CalendarSession calendarSessionToUpdate = CalendarSession.fromIntent(intent);        CalendarUpdateRequest calendarUpdateRequest = new CalendarUpdateRequest(action, calendarId, calendarSessionToUpdate);        sessionCalendarUpdater.updateCalendar(calendarUpdateRequest);    }}

It is worth noting that the modified SessionCalendarService is full of new keywords, but these keywords won't cause any problems in the class. If we skip the key point in a few seconds, we will understand this: The SessionCalendarService class does not have any business logic, so the SessionCalendarService class no longer needs to perform unit tests. As long as we confirm that SessionCalendarService calls the updateCalendar () method in the SessionCalendarUpdater class, the only possible error in SessionCalendarService is the compile-time error. We do not need to implement the test unit at all, because this is the work of the compiler and has nothing to do with us.

Due to the reasons I mentioned in the previous two blog posts, splitting our Service classes will make it very easy to perform unit tests on the business logic, for example, we can write the code for unit test on the SessionCalendarUpdater class as follows:

public class SessionCalendarUpdaterTests extends TestCase {    public void testShouldClearAllSessions() throws RemoteException, OperationApplicationException {        SessionCalendarDatabase sessionCalendarDatabase = mock(SessionCalendarDatabase.class);        SessionCalendarUserPreferences sessionCalendarUserPreferences = mock(SessionCalendarUserPreferences.class);        SessionCalendarUpdater sessionCalendarUpdater = new SessionCalendarUpdater(sessionCalendarDatabase,                                                                                   sessionCalendarUserPreferences);        CalendarUpdateRequest calendarUpdateRequest = new CalendarUpdateRequest(SessionCalendarUpdater.ACTION_CLEAR_ALL_SESSIONS_CALENDAR,                                                                                0,                                                                                null);        sessionCalendarUpdater.updateCalendar(calendarUpdateRequest);        verify(sessionCalendarDatabase).clearAllSessions(0);    }}
Conclusion

To be able to perform unit tests, I think the modified Code is easier to read and maintain. It is certain that there are still many ways to make the Code better, but in the process of allowing the code to perform unit tests, I want to make the modified Code as similar as the style before the modification as possible, so I have not made any other changes. In the next blog, I will teach you how to use the Square method to reconstruct the UI components of an application (such as Fragment and Activity ).

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.