So I posted the code as is a couple days ago and it’s so bad it makes me itch. This morning I added a noninteractive option to add an item to the todo list if you just called it like so:
todo a “New Item”
But when I grabbed the list to test it that item was displayed as “w Item”. Now, the error is obvious but the solution could go a couple ways.
So I’ve reached the end of flying by the seat of my pants. In order to progress any farther on this I’m going to have to do what I arguably should’ve done from the beginning…
It’s time to start adding unit tests.
There are all kinds of python unit testing frameworks, modules, libraries and such. But I’m a fan of the unittest module. It’s a nice small scale xUnit style library. Works great.
I’ve got 7 functions here and my intention is to instrument all of them. I suspect this little Yak Shaving exercise is going quite some time, and add a lot of code to accommodate the necessary refactoring. You’ll see what I mean as we go along.
First, a minimal unittest based test script, and a toy of my own:
#!python # test_todo.py import unittest class TestTodo(unittest.TestCase): def test_hello_world(self): self.assertEqual('foo','foo') if __name__ == '__main__': unittest.main()
That’s about the simplest “useful” unittest script you could write. I start with that exact script every time I’m starting a tight test cycle development project. I put the hello world test just to be sure everything’s all set up right.
But because programmers are lazy and I’m an exemplar of the form (lazy, not programmer) that’s not good enough. I have two little scripts I use to run this thing for me. Warning: These instances are windows batch files. If you need bash scripts and can’t translate them, seek help on ye olde innertoobz
First, the outer script, testloop.bat:
REM testloop.bat - a test running script
It’s a goofy script that loops forever and calls something; in this case “runtests.bat”, below:
@echo running tests...
@echo ...tests complete
REM Ping timeout trick to sleep.
@ping 127.0.0.1 -n 3 >nul
Okay, I know what you should be thinking: Why the actual crap is that separated into 2 scripts and what’s with the ping?
The ping trick is a simple way to implement an N second timer. The command line ping pings a network host once every second, pass it a numeric parameter (-n 3) to give it the number of iterations to perform. So this is a cheap easy way to create a 3 second timer.
Now, why two scripts? To answer that I’ve got to walk you through my development cycle a bit.
Once I have this setup I kick off testloop.bat in a console window. It scrolls test results, which don’t dump a lot to stdout unless one of the tests fails. When a test fails (which, when I’m doing something close to TDD, is all the time) it’s obvious what’s going on. Sometimes a change will break a crapton of tests. Plus, once a project starts growing I’ll have lots of test scripts. But I’m REALLY lazy and don’t want to bother stopping and starting the test runner every time I have a structural change (or if I need to increase the delay because the stack trace from unittest is flying by a little quick.)
By keeping the guts of “what test scripts are REALLY being run and how long should it wait between executions” in an INNER script, I can just edit that at will and the outer script will pick up the changes without me having to EVER CHANGE FOCUS OUT OF MY EDITOR.
It sounds like a bullshit optimization. But in fact anything that takes me out of the editor (emacs or sometimes Visual Studio Code or PyCharm) slows me down.
So in practice I’m staring at my editor screen while this thing is scrolling out in my periphery. I have the editor split vertically with the test script I’m working on in one pane and the code I’m testing in the other pane. My eyes don’t have to move. I don’t have to reach for the mouse.
If an error shows up I glance over at it, then start coding the correction (be it to the test or to the code.)
So in those three little scripts, the python test script and the two batch files, my workflow is pretty much as streamlined as it can possibly be.
An advanced side-effect of this is that if my unit tests get too big (i.e. if I’m dumb enough to add connections to external resources like a database or a network resource to my tests, making them something absolutely NOT unit tests) it becomes obvious FAST because I’ll notice the dramatic speed reduction.
I was going to start adding the actual tests in this post, but I think this sidebar into my testing workflow stands well enough on its own.
Who knows, maybe my next post won’t be a straight stream of consciousness, won’t THAT be a treat!