Tuesday, May 17, 2011

Initialize - setting up the word table

In our last post, we set up a full selection of the words at a the level desired and tested to make sure it was retrieved. The next step is to incorporate the following two parts:


/* TODO figure out what to do about non-kanji */
if (question.kanji! = question.hiragana) {
Utils.storeWordBySuffix(appState, question);
} else {
Utils.storeKana(appState, question);

}


/* store question in the AppState question hash */
appState.questionHash.put(new Integer(question.number),
question);


This poses a couple of interesting questions. The first part is needed to group potential answers by word pattern. The second loads into internal memory the questions. We've already replaced the logic to access the the questionHash in QuestionActivity. But, should we go back to the internal memory for speed purposes? Also, are there other places where the question hash is accessed?

Also, what about the test? I guess if I just put the first part in, I can figure out how to access where it's stored, and make sure the correct question was put in. I could comment out the storage of word in the XML initialize routine we're replacing. I'm just curious how many places the initialize routine is called from.

Yeah, it's called from two places, which I'm kind of unhappy about. The second one was a kind of restore for AnswerActivity which I created to satisfy the kill/restart test, but why did I have to do it that way? Actually, that's a very good reason to go with the database on the question access - I won't have to re-initialize the question hash when the app is restarted. Although I will still have to reinitialize the word table.

So, for now, I'm going to just duplicate the call to initialize in both places. like so:

initializeQuestions(a, appState);
initializeQuestionsSQL(a, appState);

First, let's change the input parameter to Activity from a specific activity and check that that works.

Ok, here's where my use of a member in a specific activity to pass a test comes back to bite me. A generic activity doesn't have that member. Ok, let's put that return paramater into AppState

Good. It passes that test.

Now, let's find out where the words are stored...

if (question.kanji != question.hiragana) {
Utils.storeWordBySuffix(appState, question);
} else {
Utils.storeKana(appState, question);
}

So, it's a little complicated to test because the words are stored in a list array, which is in turn stored in a hash table whose key is the word's suffix. To properly test this, we'll need to get the trailing hiragana, and then access the table by that suffix, then search through the the ensuing list to find the word.

This also leads to the question of test dependencies. If the previous test passes, all the data will be in the word table. But what if the dependent test runs first?

I just read something about how tests shouldn't be dependent. But - how do I know the word table has or hasn't been cleared? Maybe I should just clear it at the start of the test. Or is the whole app restarted for each test? The display does blink on and off when it's running.

Also, I'm wondering if there's a way for duplicate entries to slip in there when they restart the app. The suffix won't be, but it's just adding to a list. If the initialize runs more then once, there could be dups. I should add a test for that.

Well, first things first. Let's code the test to see if it gets there in the first place.

Ok, well, I need to test the suffix first. I'll add that to the Utils class:

public void testGetTrailingHiragana() {

String suffix = Utils.getTrailingHiragana("言う");
Assert.assertEquals("う", suffix);
}

Ok, that passed - but a couple of others failed!

testDatabaseMatchesView and testBlankKanjiIsHandled, on (what else is new) - null pointer exception.

The trace shows they are going into the word grouping. So, clearly my new code somehow broke something.

Let's see what happens if we back out the call to the SQL method, and re-implement the word setup in the original initialize.

Right - they all pass.


Ok, I see the problem. My code for the for the words needs to iterate through the returned list array. Previously, it had been the sql loop. Now it's getting the whole list in one go and needs to loop through them.


for (Question question : rows)
if (question.kanji != question.hiragana) {
Utils.storeWordBySuffix(appState, question);
} else {
Utils.storeKana(appState, question);
}


But the same tests still fail.

Well - what are my options? Debug or plow forward with the word test?

Let's plow forward with the test. It will isolate the code we need to look at.

Let's first check that the word table gets initialized...

public void testInitializeWordHashIntialized() {

AppState appState = new AppState();

setApplication(appState);

Intent intent = new Intent(getInstrumentation().getTargetContext(),
JlptQuizStartActivity.class);


JlptQuizStartActivity a = startActivity(intent, null, null);

InitUtils.initializeQuestionsSQL(a, appState);

Assert.assertNotNull(appState.wordGroupingsBySuffix);
}

Well, of course it doesn't pass.

Although the is supposed to allow me to mock the Application object, it's very possible that the running app is using a different app state. I'll have to track that down.

Well, the trace certainly shows plenty of calls to the store methods.

Try this:

appState = (AppState) a.getApplication();
Assert.assertNotNull(appState.wordGroupingsBySuffix);

Nope. Ok, I see the problem. The words are only initialized for a particular level. I'm testing at two different level. No - wait - why was it passing before?

Well, I think I need to call the initializeSQL from the other tests. No, that should be being called.

Just in case, I'll substitute in level 5, number 16 あそこ on "testBlankKanjiIsHandled".

Run it - nope. It never works until you understand the problem.

Ok, I see - those two tests actually run the initialize questions. When I checked for where they were called, I only called the checked the target - not the tests!

Ok, they pass now. Onward ho!

No comments:

Post a Comment