Hunger Tracker needs to persist data past the closing of the app. Knowing nothing about Android I googled “android write to file” and used the first reasonable looking thing that came up. This was sufficient to let me write to and read a file, which was good enough for a first try and powerful enough for me to release version 0.1. But as I planned the next step I ran into problems. The read function I found required me to specify the number of chars I wanted in advance. I couldn’t spot the end of the file so I grabbed a fixed number of entries every time. I could extract the data as strings but couldn’t figure out how to make a proper scrolling list (even though I’d done one in the Udacity tutorial). Attempts at fixes felt muddled and high friction, which is usually a sign I’m afraid of losing data, either to a hard drive failure or to a introducing a bug and failing to detect it.
Step 1 in fixing this was setting up my github account so I had proper version control. Step 2 was testing. I spent a long while figuring how to properly test the kind of android class I was using (“android unit testing” being a surprisingly unhelpful search term), and then some basic tests of “The text fields that should be there, are they there?”.
The next step was to test the data storage. But I couldn’t figure out how to unit test that. If I tested whether the app was writing to the correct file I needed to look for the exact same file. But that means updating the test every time I change the name of the file, which I didn’t want to do. Plus seeing if the correct thing is written to it is ugly. Tests should be atomic (meaning it doesn’t matter what order you run them in), but the file is persistent, meaning I either need to clear it every time or factor in what previous tests have done. Plus I would have to shape the test around the exact storage format the app was using, but again that means making the test dependent on an implementation detail. I could trigger writing and then reading and make sure the display element was correct, but that’s testing two different things and a unit test should only test one.
What I finally worked out was that the handling of persistent data was not actually a core function of HungerTracker’s MainActivity. What I needed to do was separate out those functions into a separate class, and then use mock objects to make sure the expected calls were made. E.g. instead of the app writing to a file and the test looking at the exact file and verifying the writing, the app calls the HungerTrackerWriter object. The test swaps out the real HungerTrackerWriter with a fake one, and monitors that the expected call is made. This leaves the HungerWriter proper tests blissfully unaware of the implementation details while still verifying that the app is doing what was expected.
[Technical details: somewhere I read that the android junit framework handled mocks easily. This was something of an exaggeration. It has built in mocking for a lot of Android specific classes, but nothing for user created classes. There are many well regarded Java mocking libraries, none of which provided comprehensive instructions that worked for me. Apparently they integrate weirdly with Android? My first round of mocking was hand-written, just so I could work on test code. I never did get the best regarded library, Mockito, working, but I eventually cobbled together a set of build instructions that made EasyMock work]
You might think that making that big a change in order to make something more testable is the tail wagging the dog, but as I was doing it something magic happened. Those problems with reading exactly as many entries as there were and putting them in a list (as opposed to reading exactly 10 and dumping them in a string) were suddenly much easier to conceptualize. What seemed so muddled when it was part of HungerTracker was suddenly easy to think about when it was part of HungerTrackerWriter.
If you are super curious, here is the code before the refactor, here is the code after, and here is the test code with mocks.
Have you considered putting this data into a SQLite DB? Android has decent support for that.
Not at this stage, but another nice thing about factoring this out is it will be easy to swap that in if I want to later.