I decided to experiment a bit with my development practices, specifically on how I mix application code-writing with test-writing. A test-first approach is quite common nowadays; you write a test and then hammer your code until it passes the test, then refactor. But is this test driven development (TDD)? Supposedly yes, but why would you call that test driven development and not just test first development? I decided to try out an approach that combines tests first and outside-in wishful programming that I believe adds the driven to TDD, if you would like to know how to do this and what I concluded from my experience then read on.
You start from the acceptance test level, deciding which is the next test you’ll tackle, writing only that test. Write the test wishfully, assuming you have all that you need, classes, methods, routes, tables, etc. Presume everything works the way it should work. Run the test; it will fail. The error will tell you what is missing or working differently than you hoped. Fix the issue. Run the test again and repeat until you get a passing test. Move on to the next test.
Every time you add a function or modify some code to fix the test error, do it with confidence, assuming all dependencies are met. The idea is that every bit of code you write is directly related to a test error. You don’t create a button until the test says the button could not be found, you don’t create a model class until you get a class not found exception, you don’t create the database table until you don’t get a table not found exception, you don’t add a route until you get a 404 Not Found or invalid method exception, etc. This will make you dig in from the outside, but every step inwards is only taken when the test requires it, and hence driven.
If at any point you need to add or modify business logic, first write a unit test, then using this same method as described previously get this test to pass and run your acceptance test again.
The best part is that you get really good test coverage, you are not writing code unless the test tells you to, so it’s less likely you’ll end up with untested code or unused code. Using this approach it’s easy to know what to do next as the test tells you, meaning less time spent deciding on which path to take. Finding it hard to write the “as a user I can edit my profile” feature for the 20th time in your career? No problem, as this approach is like having somebody poking you and telling you what to do until you are done.
The wishful aspect can involve a lot of heavy lifting when you are working on the first test for a feature or project. You’ll find as you dig in that so many things you wish you had are missing. In particular, if you are trying this with a legacy codebase, it can become a rolling snowball. You can compensate for this by carefully choosing which feature and which test to start with. Alternatively you could be less strict on the wishful aspect of this approach and allow yourself in some cases to use what you currently have and come back to it in a reiteration. It can also be annoying when code fails silently, like functions not complaining when you add in a new parameter which is ignored, as the test will move on to the next step and fail in the wrong place.
It’s a good approach if you’re not too strict. It can be hard to not look forwards and implement something you will need but hasn’t yet been requested by the test. If you do give it a try you could also play the “Guess what’s the next error” game, it lets you know how much you understand of what’s going on and you get a treat each time you’re right.