Taskelot Journal: Unit Testing Drama
21 May 2015
Taskelot dev journal and notes for Thursday, 21 May, 2015.
12:07 PM – Focus
Really digging in my heels to focus on my current side project: Taskelot. So far I’ve gotten the basic testing infrastructure set up for Taskelot. Trying to work out the best way to test an implementation of the interface class I created. I need to also get linters hooked into the testing workflow.
Other things on my agenda for Taskelot today include:
- Create an implementation of TaskelotInterface for storing tasks in a Python pickle file.
- With a working persistence API and implementation of that API in place, I can finish evaluating the use of Flask, BottlePy, and Aspen.io for Taskelot, and settle on one for this project.
- Consider which front end application framework to focus on for Taskelot; Backbone appeals to me for it’s minimalist and ultra-flexible design, but something such as EmberJS or AngularJS may be more expedient.
12:38 PM – Vim, Tmux, & Testing
Yesterday night I spent some time re-figuring out how to integrate Vim
and tmux in a simple way
to make it dead simple to run my test suite from within Vim. I can now
<Leader>s from within normal mode and that sends
make test to
the appropriate pane in tmux so that the command runs and outputs
there. I’m not using a plugin, just a simple mapping (which I can
remap from within Vim anytime I need). Works great for my needs right
05:46 PM – Testing Classes; Challenges Keeping Implementation Private
What am I even trying to do here? I’m attempting to write tests for things as I go, as much as possible writing the test first. I have an object model concept something like this:
- TaskelotInterface: abstract base class with method stubs which serve as
a sort of interface to be implemented by modules implementing a
storage back end; the four core methods which must be implemented by
a subclass are thusly:
- save(): save a new task to persistent storage
- all(): get a list of all tasks (which will be instances of Task, see next class)
- get(id): get a single task (instance of Task) based on its id
- delete(id): delete a single task based on its id
- Task: the task model itself, which somehow gets associated (through configuration or at runtime during testing) with a backend which inherits from and implements the TaskInterface class
- TaskelotInMemory: a reference implementation of TaskelotInterface, pretty useless in production, but useful to flesh out precisely how one might go about implementing the interface methods for creating a back end– data will be stored in memory and thus when the program stops the data isn’t really persisted
So I have written some tests making sure the core methods of TaskelotInterface return a NotImplementedError; that means if one of the methods get called when they should have been overridden by a subclass: BOOM! So far so good (though the tests feel pretty rote and meaningless, I stick with it to try to get at least some progress, even if I end up needing to redo it, assuming I find a better way).
But then I try to write a test for TaskelotInMemory. The first test I think to write is testing that it can be instantiated. This is pretty much like a hello world program sort of test– we’re not really testing anything except that we have a class definition. But then what? Do I write a test for the implemented save() method? The unit test should be testing the behaviour of the externally accessible methods right? If so, how do I verify save() actually does anything? I can’t using only external aspects of the API without broadening the scope of my test, making it less a unit test. The other option is to introspect the actual implementation of the instance of the class to make sure the save() method did what I expect, but then I’m not testing the external interface but rather the internal implementation details. I want the internal implementation details to be loosely coupled with the external interface such that the implementation could change without breaking the interface. So that’s my current conundrum.
Another issue confronting me is that Task will be associated to some TaskelotPersistenceDriver (which will implement TaskelotInterface), which itself will import Task to create instances of it in response to at least the all() and get() methods. That smells funny to me. Funny like a circular dependency or at least reference. This is likely a sign that my design is wrong and in need of serious revision. If that turns out to be the case it’s alight; that’s the point of trying, I might discover I have the wrong approach and be able to try something else which ends up far better– that’s the goal.
Taking a break for today. If I get any more done on Taskelot tonight worth sharing I’ll update this post, otherwise stay tuned for future updates in an upcoming post.