It's hard to do unit testing on Android-part1

Source: Internet
Author: User

  • Original link: against Android Unit Tests
  • Original Author: Matthew Dupree
  • Development technology Front www.devtf.cn
  • Translator: Chaossss
  • Reviewer: tiiime
  • Status: Complete

As I said in the preface, the difficulty of testing in Android is a consensus among many Android developers. After the last blog post, many of my peers replied to me and expressed support for my Views:

-andy Dyer (@dammitandy) April 13, 2015

@philosohacker made a big word! He wrote a blog post on how to better test Android apps, and address stamp me
-andy Dyer (@dammitandy) April 13, 2015

Crouching Groove, I finally know the right posture for testing apps on Android! Thank you: Address stamp me

Very much agree with "Android SDK rejection Android test Unit" view. You should make your code and SDK contacts as small as possible.

-pascal Hartig (@passy) April 12, 2015

So it's hard to do unit testing on Android, and the things I've mentioned have been made clear enough. But in fact, the root of the problem is more obvious than that. Part of the difficulty of unit testing is that you often need to do a lot of complicated and meaningless things to start roboletric so that your test units can operate at a reasonable speed. But in my opinion, this is not the key factor, the key is: Google designed Android SDK and Google's official advocated application architecture mode makes testing difficult, in some cases, testing is even impossible.

This may feel like a general statement, and to prove my conclusion, I will use a whole series of posts today to illustrate my point. In the next blog post, I'll try to explain why the officially encouraged application development approach makes unit testing difficult or impossible. In addition, I'll explore ways to enhance Android Testability by refactoring application architectures in these posts so that the app's code doesn't need to rely directly on the Android SDK. By introducing how Android makes testing difficult, I'll extend a few suggestions for refactoring applications that I think about, and I'll list them in the fourth post of the series.

A (seemingly) simple test example

To analyze this problem, we might start with a simple test paradigm. The example uses the Google iosched application source code, because I said in the first it: Iosched is the Android Developer's Reference template.

The iosched application has a page about IO conference details:

There is a "+" button in the page, we can use this button to add an IO to the calendar of the corresponding date, if the IO assembly has been added to the calendar, the "+" button will become a "check" button, if the user click on the "Check" button, IO assembly will be removed from the calendar. Open the corresponding source code we will find that: the state of this button is stored in an instance variable called mstarred, Activity will be based on the state of the button to start a service,service will be in its onStop () method to add the corresponding IO assembly to the user's calendar/ The corresponding IO assembly is removed from the user's calendar.

@Override Public void OnStop() {Super. OnStop ();if(minitstarred! = mstarred) {if(Uiutils.getcurrenttime ( This) < Msessionstart) {//Update calendar event through the calendar API on Android 4.0 or new versions.Intent Intent =NULL;if(mstarred) {//Set up intent to add session to Calendar, if it doesn ' t exist already.Intent =NewIntent (Sessioncalendarservice.action_add_session_calendar, Msessionuri);                Intent.putextra (Sessioncalendarservice.extra_session_start, Msessionstart);                Intent.putextra (Sessioncalendarservice.extra_session_end, msessionend);                Intent.putextra (Sessioncalendarservice.extra_session_room, mroomname);            Intent.putextra (Sessioncalendarservice.extra_session_title, mtitlestring); }Else{//Set up Intent-remove session from Calendar, if exists.Intent =NewIntent (Sessioncalendarservice.action_remove_session_calendar, Msessionuri);                Intent.putextra (Sessioncalendarservice.extra_session_start, Msessionstart);                Intent.putextra (Sessioncalendarservice.extra_session_end, msessionend);            Intent.putextra (Sessioncalendarservice.extra_session_title, mtitlestring); } intent.setclass ( This, Sessioncalendarservice.class); StartService (Intent);if(mstarred)            {setupnotification (); }        }    }}

Writing a test unit in this code looks like a smart choice, but the problem is: You can't write a test unit for such a code. You may not understand, why not write Ah? Don't worry, in order to find the problem, we might as well go back to the Android system and think about what would normally be the code that could be tested.

As we all know, a test unit contains three steps: Prepare, test, assert. To successfully demonstrate the assertion steps in our tests, we need to get the status of the program after executing the code in the test step. We might call this status "post-Test state." For a test unit, there are only three ways to get the post-Test status.

The first approach is to check the return value of the method executed in the test step, and it is not difficult to write down such a test code:

publicvoidtestSquare() {    new MathNerd();    int result = mathNerd.square(2);    4);}

The second approach is to obtain references to the accessible attributes in the object being tested, and assert that the properties you have obtained are what you want:

publicvoidtestSquare() {    new MathNerd();    int result = mathNerd.square(2);    assertTrue(mathNerd.didDoMath());}

The third option is to check the state of the dependency being injected into the object. These dependencies may be inaccessible, but because you inject them in the test function, you will certainly hold their references

public  void  testsquare  () {Calculator Calculator = new  Calculator ();    Terriblemathstudent terriblemathstudent = new  terriblemathstudent (Calculator);    //terriblemathstudent uses calculator to does this     int  result = Terriblemathstudent.square (2 ); The Iosched app has a IO session detail screens that looks like this : Asserttrue (CALCU Lator.diddomath ());}  

So now that we've listed the methods that we can do to complete the assertion steps in a test unit, it's time to explain why the Android code just can't be tested: The answer is simple, and we simply have no way of completing the test unit assertion step in the OnStop () method--because OnStop () method has no return value at all. There's no sessiondetailactivity. An accessible property that helps us verify that Sessioncalendarservice is started with the correct command. Finally, the code that launches Sessioncalendarservice does not belong to the dependency injected into sessiondetailactivity.

Do you think it's over? Unfortunately, it's worse than that. It is also impossible to complete the test unit preparation steps in OnStop (). In order to complete the preparation steps in the test unit, you must be able to change all the factors that affect the state after the program is tested, and you might want to make them known as "predictive testing". And there's no way we can change the predictive state in the OnStop () method, so we can't unit test the app.

In the OnStop () method, the predictive state is a Boolean value called Minitstarred, and you see that the code written in OnStop () knows what I mean:

@Override Public void OnStop() {Super. OnStop ();if(minitstarred! = mstarred) {if(Uiutils.getcurrenttime ( This) < Msessionstart) {//Update calendar event through the calendar API on Android 4.0 or new versions.Intent Intent =NULL;if(mstarred) {//Set up intent to add session to Calendar, if it doesn ' t exist already.Intent =NewIntent (Sessioncalendarservice.action_add_session_calendar, Msessionuri);                Intent.putextra (Sessioncalendarservice.extra_session_start, Msessionstart);                Intent.putextra (Sessioncalendarservice.extra_session_end, msessionend);                Intent.putextra (Sessioncalendarservice.extra_session_room, mroomname);            Intent.putextra (Sessioncalendarservice.extra_session_title, mtitlestring); }Else{//Set up Intent-remove session from Calendar, if exists.Intent =NewIntent (Sessioncalendarservice.action_remove_session_calendar, Msessionuri);                Intent.putextra (Sessioncalendarservice.extra_session_start, Msessionstart);                Intent.putextra (Sessioncalendarservice.extra_session_end, msessionend);            Intent.putextra (Sessioncalendarservice.extra_session_title, mtitlestring); } intent.setclass ( This, Sessioncalendarservice.class); StartService (Intent);if(mstarred)            {setupnotification (); }        }    }}

This boolean value is set by the callback method after the ContentProvider query operation is complete:

privatevoidonSessionQueryComplete(Cursor cursor) {    finalboolean0;    //...    if (!mIsKeynote) {        false);    }    //...}

The reason that minitstarred is initialized in the Loader callback method is that if the user adds an IO assembly event selected in Sessiondetailactivity to their calendar, it is used to identify the IO The variables that the assembly is added to the user's calendar will be stored in the table in the database that contains all IO assemblies. As we all know, Google wants us to use Loader to get the data in the database, so minitstarred is initialized in the Loader callback method.

Note: But Google's own use of Loader data is very difficult, see video: Click me!

Minitstarred is used to determine whether an app must start Sessincalendarservice update the user's calendar and Add/remove information for an IO conference. If the information obtained from the database indicates that the assembly has been added to the user's calendar, and the Add button has been converted to "check", we do not need to do anything.

So, if we are going to test in the OnStop () method, we want to determine whether the OnStop () method actually launches a Service that updates the user's calendar based on the state of the "+" button. Unfortunately, Minitstarred is a private instance variable that is initialized in the Loader callback, which can be cumbersome if you want to change its value in the test unit. So let's think about changing the test unit's predictive state.

The first option is to change the public properties of the affected object's predictive state:

Note: There are obviously many ways to change the public properties of the test state. But if, for example, the predictive state is determined by the dependency of the subject being tested, we can of course change the predicted state of the tested object by changing the public property of the object's dependency.

 Public void Testsquare() {Terriblemathstudent terriblemathstudent =NewTerriblemathstudent (); Terriblemathstudent.setcalculator (NewBrokencalculator ());//terriblemathstudent uses broken calculator to does this, so this student really sucks    intresult = Terriblemathstudent.square (2); Assertnotequals (Result,4);//we Pity the terrible math student, so we give him a working calculatorTerriblemathstudent.setcalculator (NewCalculator ()); result = Terriblemathstudent.square (2); Assertequals (Result,4);}

The second option is to change the parameters of the incoming test method:

public  void  testsquare  () {mathnerd mathnerd = new  mathnerd ();    //math nerds only if calculators when they need them  int  result = Mathnerd.squarebignumber (new     Brokencalculator (), 54321 );    Assertnotequals (result, 2950771041 ); result = Mathnerd.squarebignumber (new  Calculator (),     54321 ); Assertequals (result, 2950771041 );}  

Just like changing the post-test state, neither of these solutions solves our problem, so it is impossible to complete the test unit preparation step in the OnStop () method, the root of the problem is that there is no such thing as a sessiondetailactivity that can be used to change The public attribute of the minitstarred. In addition, the OnStop () method does not treat minitstarred as a parameter, so we cannot change the predicted state by changing the parameter value of the OnStop ().

Conclusion

Through the analysis of the above series of sources, every developer who expects to do unit testing stands on the edge of the cliff: even if it is Google's so-called development template application-a seemingly simple module in iosched, unit testing is difficult. If you've ever tried to add a test unit to your business logic in your activity, I can tell you why it hurts so much: in most cases, adding the appropriate test unit to the activity is just not possible. In some cases, because you have no way to change the predicted state of the object being tested, you will not be able to complete the unit test preparation steps. In other cases, you cannot complete an assertion step for an object because you cannot access the associated post-test state. and Sessiondetailactivity's OnStop () method is the perfect example, because the two cases above are vividly reflected in it.

There are a lot of similar code in Android that can't be tested, which makes me wonder if the main culprit in writing the code that can't be tested is the application architecture with Android platform features. So in the next blog post, I'll explore this hypothesis in depth.

It's hard to do unit testing on Android-part1

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.