Pair posting with guest Sarah Krueger!
A source control pattern for TDD
At work we recently revisited our commit practices. One issue spotted: we didn't commit often enough. To address we adopted the source control pattern in this post. There are lots of benefits; the one that mattered to me most: No more throwing the baby out with the bathwater, that is, no more two hour coding sessions only to start again and lose the good with the bad.
So we worked out this command-line pattern using single unit-of-work
git rebase -i!):
# TDD cycle: edit code, run tests, rather-rinse-repeat until green $ git pull --rebase --autostash && run tests && git commit -a --amend --no-edit
# Simple unit-of-work commit, push, begin TDD cycle again $ git commit --amend && git push && git commit --allow-empty -m WIP
What is this?
- Start with a pull. This ensures you are always current, and find conflicts as soon as possible.
- Run your full tests. This depends on your project, for
rake. If some tests are slow, split them out, and add a full test run before pushing.
- Amend your work to the current commit. This gives you a safe fallback known to pass tests. Worst case you might lose some recent work, but not hours worth. (Hint: run tests often.)
- When ready to push, update the commit message to the final message for public push.
- Push. Share. Make the world better.
- Restart the TDD cycle with an empty commit using a message that makes sense to you, for example "WIP" (work in progress); the message should be obvious not to push. Key: the TDD cycle command line only amends commits, so you need a first, empty commit to amend against.
They key feature of this source control pattern is: Always commit after
reaching green on tests; never commit without testing. When tests
fail, the commit fails (the
&& is short-circuit logical and).
In the math sense, this pattern makes testing and committing one-to-one and onto. Since TDD requires frequent running of tests, this means frequent commits when those tests pass. To avoid a long series of tiny commits when pushing, amend to collect a unit of work.
The TDD cycle depends on an initial, empty commit. The first time using this source control pattern:
# Do this after the most recent commit, before any edits $ git commit --allow-empty -m WIP
This pattern, though very useful, does not address new files. You do
need to run
git add with new files to include them in the
commit. Automatically adding new files can be dangerous if
gitignore isn't set up right.
It depends on your style
The exact command line depends on your style. You could include a script
to run before tests, or before commit (though the latter might be
better done with a git pre-commit hook). You might prefer merge pulls
instead of rebase pulls. If your editor runs from the command line you
$EDITOR at the front of the TDD cycle.
The command lines assume git, but this source control pattern works with any system that supports similar functionality.
An example of style choice. If you prefer fine-grained commits to unit-of-work single commits (depending on your taste or project; they're both good practice):
# TDD cycle: edit code, run tests, rather-rinse-repeat until green $ git pull --rebase --autostash && run tests && git commit -a
# Fine-grained commits, push, begin TDD cycle again $ git push
Improving your life
No matter your exact command line, it can be made friendlier for you.
Yes, shell history can story your long chain of commands. What if they
vary slightly between programmers sharing a project, or what if there
is a common standard approach? Extend git. Let's call
our example subcommand "tdd". Save this in a file named
git-tdd in your
#!/bin/sh set -e case $1 in test ) git pull --rebase --autostash && run tests && git commit -a --amend --no-edit ;; accept ) git commit --amend && git push && git commit --allow-empty -m WIP ;; esac
Now your command line becomes:
$ git tdd test # Repeat until unit of work is ready $ git tdd accept
The source is in GitHub.
An editing error left out the Why? section when initially posted.
Remember to autostash.