Testing has earned its place in the software development world. Most developers write software tests in the course of their work, and many put them front-and-center by practicing Test Driven Development (TDD). In TDD, you always write a failing test first. Then, you write the code to make it pass, and finally, you refactor with the confidence that your tests will catch any errors.
I really like having good test coverage for my code, and I even enjoy writing tests, but I’ve never been able to stick with the TDD flow.
It’s possible to do testing well without practicing TDD, but you have to be careful. The obvious concern is coverage: TDD forces you to write a test for every behavior you implement, and without it you’ll probably write fewer tests. You could spend weeks trying to digest all the opinions and resources about how and how much to test, but right now I’m concerned about another pitfall of testing without TDD: Not seeing tests fail.
Your software tests should detect broken code so you know they work.
Why is it important for tests to fail? If you never see a test fail, you don’t know for sure that it does what you intended, and therefore it doesn’t give you any real certainty about the correctness of your code. The code and the test could both be wrong, and you’d never know it. If you don’t believe this can happen, here are a few things I’ve seen in real life:
- Tests that don’t run at all because of a configuration issue or filename typo.
- Assertions that fire in asynchronous code… after the test method has already returned.
- Tests that accidentally exercise a totally different piece of functionality because of a copy-paste error.
- Regular old bugs in test code. We need tests because code inevitably has bugs, but tests are code, too! (mind-blown-emoji)
So let’s assume you’re convinced that it’s important to see your software tests fail, but you don’t do TDD. Now you have another problem: How can you get the test to fail if you’ve already written the code?
If the changes are super small and local, you can comment them out. But, you only get so much mileage out of that. For anything larger, my trick is to use git.
Stash the Code….
Most git users think of git stash as a way to put your edits on a shelf so you can work on something else for awhile. Once that’s done, you pop your stash and pick up where you left off. That’s a great thing compared to a VCS that requires you to make a phony commit every time, but stashing everything won’t help us with our testing problem.
…but Keep the Tests
It turns out git stash takes a –patch (or -p) option that lets you choose the individual pieces of code to stash. Those edits not chosen for the stash will remain unchanged in your working tree. The interactive stash may take a moment if you have a lot of changes, but it’s worth it: it takes you back to an idealized past where you wrote your tests but hadn’t yet started the implementation.
Using this trick opens up the following workflow:
- Forget to write tests first.
- Write the code.
- Write tests after the code.
- Stash your code changes but not the tests with git stash -p.
- Run the tests to make sure they fail.
- Pop the stash with git stash pop.
- Run the tests again to make sure they pass.
In step 4, your main options are y to stash the change in question and n to keep it in your working tree. So you’ll press y for implementation changes and n for test code. There are some other cool options, so don’t be afraid to read the manual. The same interactive patch interface can be invoked with -p for add, reset, and checkout as well..
I’ve been focusing a lot on improving my testing skills lately, and this trick really helps to give me some added confidence in my tests. After using it for awhile, getting my software tests to fail feels almost as essential as making them pass.
I’m not aware of any GUI tools or editor integrations that support git stash -p, but please leave a note in the comments below if you know of one.
Finally, if you find the interactive stash too cumbersome for large changes, take heart: As of version 2.13, git stash (with and without -p) will accept a pathspec so you don’t need to interactively go through every change in your repo when stashing!