I am currently learning about TDD and trying to put it into practice in my personal projects. I have also used version control extensively on many of these projects. I am interested in the interplay of these two tools in a typical work flow, especially when it comes to the maxim to keep commits small. Here are some examples that come to mind:
-
I start a new project and write a simple test to create an as-yet-nonexistent class. Should I commit the test before writing the class even though the test doesn’t even compile? Or should I stub out the minimum amount of code that is needed to get the test to compile before committing?
-
I find a bug and write a test to recreate it. Should I commit the failing test or implement the bug fix and then commit?
These are the two examples that come immediately to mind. Feel free to provide additional examples in your answer.
Edit:
I made an assumption in both examples that immediately after writing the test I will write code to make the test pass. Another situation might also arise: I work on a project using TDD for several hours without committing. When I finally make commits, I want to break up my work into small chunks. (Git makes this relatively easy even if you want to want to commit only some of the changes in a single file.)
This means that my question is as much about as what to commit as it is about when to commit.
Should I commit the test before writing the class even though the test doesn’t even compile? Or should I stub out the minimum amount of code that is needed to get the test to compile before committing?
Of course not. You should finish both the test and the class. Committing something1 that doesn’t even compile makes no sense, and will certainly make people working on the same project angry if you do it regularly.
I find a bug and write a test to recreate it. Should I commit the failing test or implement the bug fix and then commit?
No, do not commit a failing test. LeBlanc’s Law states :
Later equals never.
and your test might fail for a long time. It is better to fix the problem as soon as it is detected.
Also, TDD development style tells :
Test-driven development constantly repeats the steps of adding test cases that fail, passing them, and refactoring.
If you check in a failed test, that means you didn’t complete the cycle.
1 When I said commit, I meant really commit to the trunk (for git users, push your changes, so other developers would get them).
19
Should I commit the test before writing the class even though the test doesn’t even compile?
No.
Should I commit the failing test
No.
You are talking about two paradigms here:
- test driven development – which says nothing about committing code. Indeed, it tells you about how to write code and when you are done. So I’d consider every ‘done’ as a candidate for a commit.
- agile development, specifically: “commit early and often” (which does not require TDD). The idea behind it is to have an early integration with other components in the system and thus get early feedback. If you commit in a DVCS locally, and don’t push, it’s worthless in that meaning. Local commits only help developers to structure their work.
My recommendation is: follow the circle of TDD until your code compiles, your tests are green and you got something to contribute to the system. Therefore you should cut your features vertically, e.g. for a new UI mask don’t create the whole form and commit without the business logic, but rather implement one tiny aspect but in the frontend as well as the business logic as well in the persistence layer.
For a large bugfix, commit after each improvement (e.g. refactoring), even if the bug is not fixed yet. Tests should be green, and code must compile, though.
Certainly you start with using a healthy source control like git.
Then you can work the way you like, and commit at each corner — any step or substep is a fair game.
Then before pushing the stuff you squash the whole work into a single commit. Or a couple, at points where everything is green and the composition makes sense. And push those sensible commits. For the multiple case, make it a branch that you merge with –no-ff.
The source control is not a work-tracking system or a historian. The commits shall present a sensible coherent delta, while the checkout state shall be compiling at least. Intermediates may be preserved for a while for review purposes, but once everything is considered okay, a single commit per feature is fair.
Should I commit the test before writing the class even though the test doesn’t even compile?
With a branching SCM (I saw you use Git) you should commit whenever you want a backup point (“I screwed up something; I will reset the working dir to the last backup point”) or when you have a stable version. When you have a stable version (all tests pass), you should also consider merging the current feature branch into your main development branch.
Or should I stub out the minimum amount of code that is needed to get the test to compile before committing?
Up to you (git gives you the flexibility to commit whenever you like without affecting other members of your team, or your ability to work on different features). Just make sure you do not have multiple incomplete (non-working) features in the same branch at the same time (then they will block each other).
I find a bug and write a test to recreate it. Should I commit the failing test or implement the bug fix and then commit?
I usually make two commits for that, unless the test code is really small / trivial to write.
These are the two examples that come immediately to mind. Feel free to provide additional examples in your answer.
Edit:
I made an assumption in both examples that I immediately after writing the test I will write code to make the test pass.
That could be a wrong assumption to make. If you work alone (personal project) nothing stops you from always doing that. In one of my most successful projects (with regard to maintaining high code quality and TDD throughout development of the project) we defined tests sometimes weeks before implementing them (i.e. we would say “the “test_FOO_with_null_first_parameter” test is now defined as an empty function and commit it like that). Then we would take a sprint (or half a sprint) sometimes a month or so later, for just increasing the test coverage for the module. Since we had the tests already declared it was easy to estimate.
Another situation might also arise: I work on a project using TDD for several hours without committing. When I finally make commits, I want to break up my work into small chunks. (Git makes this relatively easy even if you want to want to commit only some of the changes in a single file.) This means that my question is as much about as what to commit as it is about when to commit.
I would say definitely commit to create backup points. This works very well for exploratory testing (“I will just add some prints throughout the code base, run and git reset --hard
to remove them when I’m done) and for prototyping.
2
It is my understanding of the world that one commits to mark a point that it may be desirable to return to. The point a which a test fails (but compiles) is definitely one such point. If I were to wander off in the wrong direction trying to make a test pass, I would want to be able to revert the code back to the starting point and try again; I can’t do this if I haven’t committed.
1
In my work flow, whenever possible I do uncertain work on a personal source-control branch. So I can try, fail, try again if necessary until it works, and only commit to the larger project when I have actual working code.
From a TDD perspective, the question of “do you check-in the test first?” is entirely dependent upon the code that you’re working on. If it’s new code, you don’t check in anything until you have something worth checking-in. But if it’s a bug found in already compiled or shipped code, checking in a test to reproduce the bug, BY ITSELF, is worth checking in. Especially if it’s the end of a work-day, and you will leave the office before you fix the code.
(Of course, if your shop has an automated build process that dies if any unit-tests fail, you may not want to check in a failing test until you fix the bug. But that seems like an odd way to work, since “find and document bugs” and “fix bugs” can be done by two entirely different teams.)
For code reviews, especially regarding bug fixes, I like to see that a test failed before the fix was implemented. So I’d actually encourage to commit and push your branch so that I can be assured that:
- the test actually failed
- the problem is actually fixed with the next commit
If both happens in only one commit I won’t really be able to see if either test or code just do nothing.
Of course, for that to work you should have automatic multi branch continuous integration.
After code review though, I agree with the other answers and it might be sensible to squash commits together.
1