Today, we're going to add some functionality to test the entry of invalid values. Let's get busy.
First, I'll make a copy of an existing test using ActivityInstrumentationTestCase2. I know it's kind of slow, but I'm going to be accessing resources and I think I need it for that. Actually, I'm not sure. Let's try accessing a resource from a test that uses ActivityUnitTestCase. I'm having trouble with deciding. I think I just need to play it safe and follow along with the examples from Google. What were those? Oh, yeah, it was Spinner. Let's take a look at some of its code:
 @Override
 protected void setUp() throws Exception {
  super.setUp();
  setActivityInitialTouchMode(false);
  mActivity = getActivity();
  mSpinner = (Spinner) mActivity
    .findViewById(com.android.example.spinner.R.id.Spinner01);
  mPlanetData = mSpinner.getAdapter();
 } // end of setUp() method definition
 
In the setup, it's grabbing the control it's intending to test, plus it's adapter.
 // this is run only once at the start of the app,
 // to make sure the project is initialized correctly
 
 public void testPreConditions() {
  assertTrue(mSpinner.getOnItemSelectedListener() != null);
  assertTrue(mPlanetData != null);
  assertEquals(mPlanetData.getCount(), ADAPTER_COUNT);
 } // end of testPreConditions() method definition
Here it's checking to make sure everything's been initialized.
    public void testSpinnerUI() {
        mActivity.runOnUiThread(
          new Runnable() {
            public void run() {
              mSpinner.requestFocus();
              mSpinner.setSelection(INITIAL_POSITION);
            } // end of run() method definition
          } // end of anonymous Runnable object instantiation
        ); // end of invocation of runOnUiThread
    
This looks like it creates a new thread. It sets the focus on the control and sets the selection on it. I remember reading something about how you need to test UI on the UT thread - but it was done as an annotation.
Here's the rest of the method:
  this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
    for (int i = 1; i <= TEST_POSITION; i++) {
      this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
    } // end of for loop
    this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
    mPos = mSpinner.getSelectedItemPosition();
    mSelection = (String)mSpinner.getItemAtPosition(mPos);
    TextView resultView =
      (TextView) mActivity.findViewById(
        com.android.example.spinner.R.id.SpinnerResult
      );
    String resultText = (String) resultView.getText();
    assertEquals(resultText,mSelection);
  } // end of testSpinnerUI() method definition
Ok, let's model test activity based on spinner code. I also found another link, 
http://mobile.tutsplus.com/tutorials/android/android-sdk-junit-testing/
which looks like a nice simple example.
We'll try something like this:
 public void testSendKeys() {
        mActivity.runOnUiThread(
          new Runnable() {
            public void run() {
              mLevel.requestFocus();
              //mLevel.setSelection(INITIAL_POSITION);
            } // end of run() method definition
          } // end of anonymous Runnable object instantiation
        ); // end of invocation of runOnUiThread
        
        /// now on level 
        sendKeys(NUMBER_5);  
        // now on start quiz number  
        sendKeys(NUMBER_1);
  
        // now on end quiz number
        sendKeys(NUMBER_1000);  
         
        // save
        sendKeys("ENTER");  
        
        Assert.assertEquals(true, true);
    }
Ok, it crashed on an index out of bounds. I noticed it looked like it wasn't clearing the fields before it populated them. It was too fast. Let's unplug an run it on the emulator, which is much slower.  Woah - way too slow. Back to the device.
Ok, after a little experimentation, this sequence gives me a passing test:
 // / now on level
  sendKeys(KeyEvent.KEYCODE_BACK);  
  sendKeys(KeyEvent.KEYCODE_DEL);  
  sendKeys(NUMBER_5);
  
  // now on start quiz number
  sendKeys(KeyEvent.KEYCODE_BACK);  
  sendKeys(KeyEvent.KEYCODE_DEL);  
  sendKeys(NUMBER_1);
  // now on end quiz numbe
  for (int i = 0; i < 5; i++) {
   sendKeys(KeyEvent.KEYCODE_BACK);   
  }
  for (int i = 0; i < 5; i++) {
   sendKeys(KeyEvent.KEYCODE_DEL);   
  }
  sendKeys(NUMBER_1000);
  // save
  sendKeys("ENTER");
  Assert.assertEquals(true, true);
Now, let's see what happens when we put a crazy value in there somewhere.
  sendKeys(NUMBER_9999);
9999 is a larger number then the available vocabulary limit. 
I'm just having a lot of difficulty figuring out what's going on when the test runs; on the device, it goes by too fast, and sometimes the screen size changes. Also, it's not at clear it's entering the digits I want. Plus, I know it should be crashing, and it isn't. The emulator is incredibly slow. 
So, I'm going to go to plan B and try Robotium. This is a frustrating thing. A morning's worth of work, pretty much trashed. This is the downside of TDD on Android. The tools are hard to work with. 
Let's see, is there a tutorial or something? 
Yeah. Ok, a youtube video. Ok, download the sample project. Ok, it needs notepad list. Ok, got that. Run the test. Ok, seems to work. 
Let's make a copy of that project. Ok, don't forget to change the target package name in the manifest; and the package name. Add the target package to the projects tab in settings. 
ok, let's try something like this:
solo.clickOnButton(0);
Wow, very cool. Not only did it press the correct button, it went to the next activity screen, without my having to specifically load it. Multi activities, something I hadn't even gotten close to with the Android testing framework.
I like the Robotium api - all the events are easy to pick out. It supports things like clear text. 
Lets see if we can enter some text...
  solo.clickOnButton(0);
  solo.clearEditText(0);
  solo.enterText(0, "17");
Ok, how to I save it? 
  solo.clickOnButton(0);
  solo.clearEditText(0);
  solo.enterText(0, "17");
  solo.clickOnButton(0);
Index out of bounds - perfect!  Amazing - this is so much easier than Android testing framework.
Ok, let's fix up the code that causes this problem. Suddenly, I'm back into production code. Actually - this should work for the lifecycle checks I had to comment out, which I went to such pains to create.  
Let's fix index out of bounds.
Ouch. Again, adding test code to return a value is just a bad idea. Especially a hardcoded one. It was ok when I was building the function, but anything hardcoded has to be cleaned out pretty quickly. Let's comment out that code.  
Pass. 
The problem is, I now have tests to run from two separate projects. I wonder how many of the android junit test I could bring into this one. Either that, or just test everything functionally. I think there's some I couldn't really do that for, that really do test actual methods. 
Well, that's good for now. Let's pick up some more of the great Robotium in the next post.
 
No comments:
Post a Comment