Today, I want to change gears a little bit and talk about our TDD development process, which has always been very dear to our hearts. Just as a disclaimer, this is going to be a very general overview and if you are an experienced TDD developer, you may not get a lot out of it. However, continuing along the mentorship/educational theme, I hope this will be useful for the people out there who are just starting out, and are trying to figure out what is a good style/process for developing.
So here is what I did for a small webapp that I created (after I used Ansible to automatically configure my new PythonAnywhere account of course) .
- Go through the PythonAnywhere web interface to create an initial webapp, and double check that you are getting a hello world page at
And here is my programming/iteration process:
- Start off by picking a feature to implement and write a story for it. That is, create a functional test that details what sort of behavior one would expect to see in the browser (from a specific user's standpoint) that is related to that particular feature. It is also conceptually useful to subdivide the test into "given/when/then" chunks.
- Next, run the ft with your favorite test framework (eg: py-test with selenium). Depending on what other features are already implemented, the test may not get really far before failing. This is okay- the point is to use the story to drive further development, so you can stay on track.
- Look at the first failing line of the ft and figure out what is needed to get it passing. Start writing some unittests for exactly that and then start implementing the feature itself to get the unittest passing.
Now the plan is to just work through the story, iteratively adding new unittests and writing code to get it to pass, saving your progress into your version control system of choice, and work your way down the functional test until that whole story passes!
While you code, there will probably be a bunch of other use cases/edge cases/different branching routes that pop into your mind that should be tested as additional stories or unittests. I tend to put in a bunch of placeholders with just the name (description of the scenario) and flesh them out one by one afterwards.
But wait, there's more!
- coverage.py is an exceptionally useful tool for measuring if all of your code is "covered" (tested) by your unittests. This is especially useful for projects which you picked up in the middle and had no control over the previous code quality. Nowadays, It can also do branch coverage and generate pretty html reports!
- I also set up test runs that go through all the tests (unittests, functional tests etc) every night to check for regressions. In my setup, I roll my own test runner using PythonAnywhere scheduled tasks, because I find it is easier, more flexible, and more private.
- Of course, there are a lot of available solutions out there (eg: Travis CI) which look super pretty, and is really cool (eg: it spins up a new server from scratch to make sure your code doesn't have some hidden dependency on your current machine).
However it does have a lot of limitations: it doesn't work with mercurial; it doesn't work with bitbucket; it's at least USD 129/month if you are not open sourcing your code; it's also a bit slow...
- And since I am lazy, I also really hate clicking through multiple emails. Apart from the test run results, I have tasks that do daily backups, look at production site statistics (eg: how many customers clicked xyz today), scrape stuff and keep track of how it changed etc, and it all gets bundled up into a single email each day so I can read a single report on all that with high level stuff summaries (plus detailed attachments).
- Another important thing I do quite soon after starting a new webapp is to separate out production from development. Coding and making changes on the live site is a recipe for disaster. PythonAnywhere is really great for this because your production environment can be exactly the same as your dev environment! Just setup a second webapp and point it to a cloned code base, and voila!