Unit Testing Core Data
When last we spoke, I was looking for a method of applying test-first development to Core Data. After some very Core Data specific initial code, heavy refactoring, and many adventures along the way, I've finally built the solution I need. As you'll see, it has used far beyond Core Data. If you're interested in more information on my solution, let me know. Here's the story of how I ended up where I did.
First, I wrote some hard-coded tests to load the root object of the Core Data Model (NSManagedObjectModel). I then found the child NSEntityDescription objects and their NSAttributeDescription objects. This allowed me to write a hard-coded test to make sure the model looked like I wanted it to look. Fine for phase one, but it was a real pain to add any new tests. It was several lines of code to add a single new Attribute, and very ugly to start testing the second entity.
What I needed was a way to describe how the Entity and it's Attributes should look. Some kind of hierarchical way of describing the expected state of the model, preferably in a human-readable form. This was just screaming for XML. Time for the first refactoring. I began to turn the hard-coded check for Attribute values into checks against XML data values.
How, though, do I know what accessors to call on the NSEntityDescriptions to check for it's member values? How do I know that "isAbstract" is the method to call, and the type is BOOL? That's the next step in the puzzle. So I decided to pick a single level of the Core Data Model, the accessors of the "NSAttributeDescription" node, and see if I can create a general way of defining a mapping from the "expected value" in XML to the "actual value" in the model, retrieved by calling the accessor.
This obviously required some sort of table. Back to the refactoring board. The old hard-coded methods were replaced with a table that mapped XML names to the correct data accessor. For example, the XML:
Attribute Name="title" Optional="NO" Transient="NO" Type="NSString"
required me to call four accessors on the Entity names "title". These are "name", "isOptional", "isTransient" and "className". Then the values needed to be checked (and checked against the correct type). The type checking turned out to be one of the thornier issues, since the NSXML functions don't have any support for translating string values back to basic types. So custom parsing of strings based on types is the order of the day.
Once I had the Attribute working, it was time to move up the tree to the NSEntityDescription. Here, however, it became obvious I'd need another table for this level of the hierarchy. And if I was ever going to add any more levels, I need a new, hand-coded table for each type of object I was going to be inspecting. This would be a lot of error-prone work, defining nested tables all pointing at each other. If only there were a way to define a hierarchical set of data that I could read in at run time to generate those tables.
Back to XML once more, and back to refactoring. I re-wrote my table mapping the specifics of an NSAttributeNode into an XML format. I then wrote the routines to read in that XML and generate the old tables, verifying that I hadn't lost anything. Once that was done, I scrapped the tables entirely in favor of a cleaner arrangement, which I'll get to shortly.
At this point, I began working on the NSEntityDescription node. This node has data accessors, much like NSAttributeDescription, but it also has child nodes (those same Attributes). This lead to the obvious conclusion that we needed some kind of recursive descent through the Core Data Model Tree. At each level, I'd need to verify I had the right object. Then I'd need to check the data accessors. Then I'd need to find the child containers and iterate on each of those until I reached the bottom of the tree. I could specify the entire thing in XML. And so I did.
At this point, I noticed that the only references to Core Data were in my schema XML. The code itself didn't care at all what kind of object I was using, it just read the schema and began checking the nodes. I had a way to call methods on an object to return values, and a way to iterate the children. I no longer had a Core Daa Unit Test tool. I had in fact created a framework for walking any object tree where the nodes derive from NSObject, and comparing it to a textual description of that tree.
This wasn't my original intent, but it's a very cool place to get to. By working on a specific problem and refactoring it relentlessly, I managed to create a generic version of the same tool that is far more flexible. This is the kind of experience that most Agile Methods proponents talk about, but one which is often not realized in a business situation due to time or other pressures. A bit of extra time up front now, building this tool, has provided me with a method of testing arbitrary object trees in the future, and that can be nothing but a win for software quality if used to its full potential.
I still have a few refactorings to do on the code before I call it finished. I'm considering making the code available. If you'd like to see it, or think this is a useful tool, let me know. I'm still working out PlayTank's official stance on shared source, so this is a good time for any input. I'm sure the topic will come up again soon.