Last week I tried test && commit || revert - a.k.a “TCR” - for the first time. I went to Mödling (near Vienna) to spend a whole day with Peter Kofler to learn this technique.

We did three sessions: “FizzBuzz”, then a “Snakes & Ladders” game and again the “Snakes & Ladders” game. Before I’ll describe what happened there, TCS means (as defined by Kent Beck):

  • Add test and pass. The goal here is to shorten the time between idea and some kind of test passing in some kind of way. Even writing part of the test is fine. Cheating is encouraged, as long as you don’t stop there.
  • Better passing. Once you have a test passing, replace the fake implementation with a real implementation, a little at a time if necessary.
  • Make hard changes easy. Rather than change four places in the code, introduce a helper function (a little at a time, natch) so you can change one place.

We first read about it in “Limbo on the Cheap” by Kent Beck.

Try 1: FizzBuzz

We wanted to try something simple, so we implemented FizzBuzz in Java. We used the Limited WIP plugin for IntelliJ IDEA which has a TCR mode, where it commits all your changes when the tests are green.

The plugin worked (although it does not add new files) and we implemented FizzBuzz.

But we were not sure if we were learning something. Was the example too easy? Were we doing it wrong?

Try 2: Snakes & Ladders

We decided to increase the difficulty in two dimensions: A more difficult Kata, done in JavaScript so the Compiler does not prevent us from running wrong code.

We now ran the “TCR” script on the command line via npm:

{
  "name": "ladders.js",
  "scripts": {
    "tcr": "jest && bash ./git_commit || bash ./git_revert"
  }
}

Wher git_commit is:

#!/bin/sh

git stage -A
git commit -m "working"

exit 0

and git_revert is:

#!/bin/sh

git checkout -b broken_`date +%s`
git stage -A
git commit -m "broken tests"
git checkout try_1

So, the idea of the revert script was to commit the progress on a new, “broken” branch and then go back to the “try_1” branch. This did not work yet, because it created too many branches, but the “TCR” cycle worked.

We implemented Snakes and Ladders. Did we learn something this time?

We still thought that TCR didn’t force us to take small steps. And I, personally, did not see the usefulness of the technique yet.

But we learned that even a few seconds to run the tests is a very long time when you do TCR. Jest was almost too slow, because running the tests took ~5 seconds. You want really fast feedback when you do TCR.

We decided to not keep the tests when reverting. Some people on the internet suggest to keep the failing test, but we thought that this would even lower the pressure for taking smaller steps.

Try 3: Snakes and Ladders.

Were we still doing it strong? TCR does not force us to take small steps, but maybe it works better doing really small steps?

In try 1 and 2, we still always finished writing the test before starting the implementation, like you would do in TDD. We just tried to write even smaller tests. What if we switched between test code and production code even more often? After editing a single line, as Peter suggested?

We tried that, and now it started to make sense. Suddenly the technique worked. When you take small steps, commit every few seconds, fake it until you make it, then this technique allows you to move forward fast.

We also improved our test scripts a little bit…

run_tests writes the test results to a file. In the “broken”-branches, we keep the test results:

#!/bin/sh

./node_modules/.bin/jest --no-color 2> test_results.txt
error=$?
cat test_results.txt

exit $error

The commit script deletes the test results (not interesting in a “working” commit) and always exits with “0” so we do not create reduntant “broken” branches:

#!/bin/sh

rm test_results.txt
git stage -A
git commit -m "working"

exit 0

Conclusion

TCR worked really well once we found out how to do it. It did not force us to take smaller steps, but it started to make sense when we learned to take smaller steps.

In “the real world”, you would probably squash those commits before pushing. Or you would try “Limbo”, where you would also push and pull each commit. This is also what we want to try next.

Did you alredy try TCR? Or do you want to try it, but do not know how to start? Please tell me via E-Mail or on Twitter.