- Original link: How to make our ANDROID APPS UNIT testable (PT. 1)
- Original Author: Matthew Dupree
- Development technology Front www.devtf.cn
- Translator: Chaossss
- Reviewer: tiiime
- Status: Complete
Unit testing in Android apps is difficult, and sometimes impossible. In the previous two blog posts, I have explained to you why unit testing is so difficult in Android. And the last one we've come to the conclusion of this analysis: it is the application architecture that Google officially advocates that makes unit testing in Android a disaster. Because in an officially advocated architecture, Google seems to want us to put business logic in the application's component classes (for example: Activity,fragment,service, and so on ...). )。 This development is also a development template that we have been using for a long time.
In this blog post, I've listed several ways to architect Android apps, and using these methods for development can make unit testing easier. But as I said in the preface, the most popular approach is always the square post: square: Abandon fragment today! The common method used in the. Because this method was thought out by the Android development Engineer in Square, I would call this the "square Dafa" in the next blog post.
The core idea of Square Dafa is to remove all the business logic from the application component classes (for example: Activity,fragment,service, etc.). ), and transfer business logic to business objects, which are pure Java objects that are injected by dependency, and Android-agnostic interfaces for Android-specific implementations. If we were to use Square Dafa when we were developing our applications, it would be much easier to do unit testing. In this post, I'll explain how Square Dafa helps us refactor UI-agnostic application components (for example, the sessioncalendarservice we discussed in previous blog post) and makes it much easier to unit-test it.
Refactoring UI-agnostic application components with Square Dafa
It is relatively easy to refactor a UI-agnostic application component like Service,contentprovider,broadcastreceiver with Square Dafa. Let me repeat what we are going to do: Remove the business logic in these classes and put them in the business object.
Since "Business logic" is an easily ambiguous term, let me explain what it means when I use the word "business logic". When I refer to the "Business logic", its meaning is consistent with the interpretation on Wikipedia: The program is based on real-world rules to determine how the data will be created, displayed, stored and modified in that part of the code. So now we can agree on the meaning of the word "business logic", so let's see what Square Dafa is.
Let's take a look at how to use Square Dafa to implement the Sessioncalendarservice I introduced in my previous blog post, with the following code:
/** * Background {@link Android.app.Service} that adds or removes session Calendar events through * The {@link Calenda Rcontract} API available in Android 4.0 or above. */ Public class sessioncalendarservice extends intentservice { Private Static FinalString TAG = Makelogtag (Sessioncalendarservice.class);//... Public Sessioncalendarservice() {Super(TAG); }@Override protected void onhandleintent(Intent Intent) {FinalString action = Intent.getaction (); LOG.D (TAG,"Received Intent:"+ action);FinalContentresolver resolver = Getcontentresolver ();BooleanIsaddevent =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, Processallsessionscale Ndar (resolver, Getcalendarid (intent))); Sendbroadcast (NewIntent (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, processclearallsession S (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; }FinalUri uri = Intent.getdata ();FinalBundle extras = Intent.getextras ();if(uri = =NULL|| Extras = =NULL|| ! Prefutils.shouldsynccalendar ( This)) {return; }Try{Resolver.applybatch (calendarcontract.authority, Processsessioncalendar (resolver, getcalend Arid (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 invokes the helper method that will be defined later. Once we consider the field declarations of these helper methods and classes, the Service class has more than 400 lines of code. It is not easy to hold on to the business logic that takes place in such a large class, and as we saw in the last blog post, it is impossible to do unit testing in Sessioncalendarservice.
Now let's look at how the code will be implemented with Square Dafa. Let me emphasize again: Square Dafa requires that we migrate the business logic within the Android class into a business object. Here, sessioncalendarservice the corresponding business object is sessioncalendarupdater, the specific code is as follows:
Public class sessioncalendarupdater { //... PrivateSessioncalendardatabase msessioncalendardatabase;PrivateSessioncalendaruserpreferences msessioncalendaruserpreferences; Public Sessioncalendarupdater(Sessioncalendardatabase sessioncalendardatabase, sessioncalendaruserpreferences SessionC alendaruserpreferences) {msessioncalendardatabase = Sessioncalendardatabase; Msessioncalendaruserpreferences = sessioncalendaruserpreferences; } Public void Updatecalendar(Calendarupdaterequest calendarupdaterequest) {BooleanIsaddevent =false; String action = Calendarupdaterequest.getaction ();LongCalendarID = 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.shouldsyncca Lendar ()) {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, Sessioncalendaruserpreferenc Es sessioncalendaruserpreferences) {returnCalendarupdaterequest.getcalendarsessiontoupdate () = =NULL|| !sessioncalendaruserpreferences.shouldsynccalendar (); }}
I would like to highlight some of these points: first of all, it's important to note that we don't need any new keywords at all, because the business object's dependencies are injected, and it doesn't use the new keyword at all, which is the key to making the class unit test. Second, you will notice that the class does not depend exactly on the Android SDK, because the business object relies on Android-agnostic implementations of Android-independent interfaces, so it does not need to rely on the Android SDK.
So how are these dependencies added to the Sessioncalendarupdater class? is injected through the Sessioncalendarservice class:
/** * Background {@link Android.app.Service} that adds or removes session Calendar events through * The {@link Calenda Rcontract} API available in Android 4.0 or above. */ Public class sessioncalendarservice extends intentservice { Private Static FinalString TAG = Makelogtag (Sessioncalendarservice.class); Public Sessioncalendarservice() {Super(TAG); }@Override protected void onhandleintent(Intent Intent) {FinalString action = Intent.getaction (); LOG.D (TAG,"Received Intent:"+ action);FinalContentresolver resolver = Getcontentresolver (); Broadcaster BROADCASTER =NewAndroidbroadcaster ( This); Sessioncalendardatabase sessioncalendardatabase =NewAndroidsessioncalendardatabase (Resolver, broadcaster); Sharedpreferences defaultsharedpreferences = preferencemanager.getdefaultsharedpreferences ( This); Sessioncalendaruserpreferences sessioncalendaruserpreferences =NewAndroidsessioncalendaruserpreferences (defaultsharedpreferences); Sessioncalendarupdater Sessioncalendarupdater =NewSessioncalendarupdater (Sessioncalendardatabase, sessioncal Endaruserpreferences); Accountnamerepository accountnamerepository =NewAndroidaccountnamerepository (Intent, This); String AccountName = Accountnamerepository.getaccountname ();LongCalendarID = Sessioncalendardatabase.getcalendarid (AccountName); Calendarsession calendarsessiontoupdate = calendarsession.fromintent (Intent); Calendarupdaterequest calendarupdaterequest =NewCalendarupdaterequest (Action, CalendarID, Calendarsessiontoupdate); Sessioncalendarupdater.updatecalendar (calendarupdaterequest); }}
It is worth noting that the modified sessioncalendarservice are everywhere new keywords, but these keywords do not cause any problems in the class. If we take a few seconds to skim the essentials, we'll understand this: there's no business logic in the Sessioncalendarservice class, so the Sessioncalendarservice class no longer needs to be unit tested. As long as we determine that the Updatecalendar () method in the Sessioncalendarupdater class is called in Sessioncalendarservice, the Sessioncalendarservice The only thing that can happen is a compile-time error. We do not need to implement the test unit at all, because this is the work of the compiler, which is irrelevant to us.
Because of the related reasons I mentioned in the top two posts, splitting our service class into such a way makes unit testing of business logic very simple, such as the Code for unit testing of the Sessioncalendarupdater class can be written like this:
Public class sessioncalendarupdatertests extends TestCase { Public void testshouldclearallsessions()throwsRemoteException, operationapplicationexception {sessioncalendardatabase sessioncalendardatabase = mock (SessionCale Ndardatabase.class); Sessioncalendaruserpreferences sessioncalendaruserpreferences = mock (sessioncalendaruserpreferences.class); Sessioncalendarupdater Sessioncalendarupdater =NewSessioncalendarupdater (Sessioncalendardatabase, Sessioncalendaruserpreferences); Calendarupdaterequest calendarupdaterequest =NewCalendarupdaterequest (Sessioncalendarupdater.action_clear_all_sessions_calendar,0,NULL); Sessioncalendarupdater.updatecalendar (calendarupdaterequest); Verify (Sessioncalendardatabase). Clearallsessions (0); }}
Conclusion
To be able to do unit tests, I think the modified code becomes more readable and easier to maintain. To be sure, there are many ways to make the code better, but in the process of allowing the code to do unit testing, I want the modified code to be as similar to the pre-modification style as possible, so I didn't make any other changes. In the next blog post, I'll teach you how to refactor the UI components of your app using Square Dafa (for example: Fragment and Activity).
It's hard to do unit testing on Android-PART3