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
commits (without 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
example,
mvn verify
or 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.
Why?
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.
Bootstrapping
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
Adding files
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
might toss $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.
Fine-grained commits
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 $PATH
:
#!/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.
Updated:
An editing error left out the Why? section when
initially posted.
Remember to autostash.
A follow up post: Push early, push
often, push on green.