Wednesday, May 11, 2011

Android SQL unit test - testing the read function

In the previous post, we succeeded in installing a prepackaged database on the Android device. In this entry, we're going to add a method to our SQLLite helper class to retrive a specific row from it, based on level and number. Then, if all goes well, we'll convert the application from reading XML to reading from the SQLLite database. Let's get started.

First, the unit test. This will simply look up a specific row and ensure that a given field matches what's expected to be in the field. It will use a variation on the selectAll method discussed in the previous post. The first test will look something like this:

public void testGetOneRow() {

List myList = null;

DataBaseHelper myDbHelper = mActivity.getDbHelper();

try {

myDbHelper.openDataBase();
myList = myDbHelper.selectByLevelAndNumber(1, 1);
myDbHelper.close();

} catch (SQLException sqle) {
throw sqle;
}

int count = myList.size();
Assert.assertEquals(1, count);
}

All this really does is ensure that a single record is selected, and pushes off the actual testing of equality to a subsequent test. So, lets put it into the initial activity test, where it won't compile. Then we'll add the method. This is in accordance with the "three laws of TDD" I've been attempting to follow, see http://tinyurl.com/cb99lp.

A quick word about testing within the initial activity as opposed to a separate test module for the db helper - that is, the db helper needs a context to be initialized, so it needs an activity. It's already a subclass of Android's SQLiteOpenHelper, and I'm not sure if it's would be a great design idea to make it an activity anyway. Here's the test.

public void testGetOneRow() {

List myList = null;

DataBaseHelper myDbHelper = mActivity.getDbHelper();

try {

myDbHelper.openDataBase();
myList = myDbHelper.selectByLevelAndNumber(1, 1);
myDbHelper.close();

} catch (SQLException sqle) {
throw sqle;
}

int count = myList.size();

Assert.assertEquals(1, count);

}

As you can see, this is pretty much the same as the test of selectAll, with the one difference being the select statement. We'll use the eclipse quick fix to put the method into the helper class, and code it, this time based on the selectAll method.

Upon review, I see I've coded the selectAll method to only select one field. What I'll do is add all the fields I need in one fell swoop in this method, although I don't technically need them all just to pass the test. Hopefully, I'm not over-shooting - it's just adding column names.

Ok, now I've added the column names. Next is to add the where clause. The lookup clause now looks like this:

Cursor cursor = this.myDataBase.query("all_words",
new String[] {"level", "number", "kanji", "hiragana", "english" }, "level = ? and number = ?", new String[] {"1", "1"}, null, null, null);

The level and number are hardcoded for now. I'll change that after I check the count and the equality in tests. The nice thing about capturing the tests is that I won't lose them once I convert to substituting the parameters in.

Ok, when I run the test I get that "invalid apk" message I got earlier. I'll kill the app on the device and see if it makes a difference. It did. Next time I'll just go to the home screen and see if I don't need to kill it.

Good, the test for a count of 1 record passed. Now, I'd like to test for equality. However, I just realized I don't know how to get a value from the select method I've been using. Let's take a look at the current method. Hmm. Not so great. It looks like this:


public List selectByLevelAndNumber(int i, int j) {
List list = new ArrayList();
Cursor cursor = this.myDataBase.query("all_words",
new String[] {"level", "number", "kanji", "hiragana", "english" }, "level = ? and number = ?", new String[] {"1", "1"}, null, null, null);
if (cursor.moveToFirst()) {
do {
list.add(cursor.getString(0));
Log.d(TAG, "cursor.getString(0): " + cursor.getString(0));
} while (cursor.moveToNext());
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return list;
}

As you can see, it's designed to get just one field in the row. It probably would make more sense to return a hash table from this method, so we can just grab and cast the fields as needed. Maybe I should look at the Cursor docs. Strange - it doesn't show getString, but it shows up in the code completion. Ok, it's because I was looking the SQLLite subclass of Cursor. The Cursor interface has all the get methods, so they are implemented somewhere in the hierarchy.

Actually, instead of a hash, I'll just create a pojo class containing the fields I need, and return that.

Here's the revised method to return the pojo in DB Helper:

public ArrayList selectByLevelAndNumber(int i, int j) {
Cursor cursor = this.myDataBase.query("all_words",
new String[] {"level", "number", "kanji", "hiragana", "english" }, "level = ? and number = ?", new String[] {"1", "1"}, null, null, null);
ArrayList rows = new ArrayList();
if (cursor.moveToFirst()) {
do {

int level = cursor.getInt(0);
int number = cursor.getInt(1);
String kanji = cursor.getString(2);
String hiragana = cursor.getString(3);
String english = cursor.getString(4);
rows.add(new AllWordsRow(level, number, kanji, hiragana, english));
} while (cursor.moveToNext());
}
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
return rows;
}


Note for this that I slightly drifted away from test first when coding this, since I didn't have a non-compiling test for it. But, I'm testing it now, before running it in the production code, at least. Speaking of tests, here it is:


public void testGetRowEqualsExpected() {

ArrayList rows = null;

DataBaseHelper myDbHelper = mActivity.getDbHelper();

try {

myDbHelper.openDataBase();
rows = myDbHelper.selectByLevelAndNumber(1, 1);
myDbHelper.close();


} catch (SQLException sqle) {

throw sqle;

}

AllWordsRow row = rows.get(0);


Assert.assertEquals("Ah!, Oh!, Alas!", row.getEnglish());

}

Let's run it - pass! TDD rocks. I've done all this testing without touching the device - well, mostly. That up-front "small" test and the focus on doing the minimum to pass can actually speed things up. Note that I had to refactor the test count method to account for the changed api.

Ok. now let's change the get row method to actually use the level and number parameters. And that passes. Finally, test it with a different row than the first.


public void testGetRowEqualsExpectedToGive() {

ArrayList rows = null;

DataBaseHelper myDbHelper = mActivity.getDbHelper();

try {

myDbHelper.openDataBase();
rows = myDbHelper.selectByLevelAndNumber(1, 1);
myDbHelper.close();


} catch (SQLException sqle) {

throw sqle;

}

AllWordsRow row = rows.get(0);


Assert.assertEquals("to give", row.getEnglish());

}


Hmm...it flunked that test. Oh, that's it - I hadn't changed the parameters. When I do that, it passes.

Ok. We're at the point where we can reasonable expect to use this DB helper class to retrive the data. The big, looming question is the character encoding. I took the default when I set up the database, but it may well be the case that this was incorrect and I'll get junk when I display the Japanese fields. But, for now, let's check in the code and move on.

No comments:

Post a Comment