32

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:

  1. 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?

  2. 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.

7 Answers 7

25

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
  • 5
    "and will certainly make angry people working on the same project if" - someone is living in SVN world use GIT and you will not make anyone angry
    – Mateusz
    Jun 16, 2013 at 19:48
  • 4
    I think committing is fine after writing a test, just don't push it till you're done.
    – Matsemann
    Jun 16, 2013 at 19:51
  • 4
    @radarbob Does this apply even for a DVCS where there is a distinction between committing and pushing? I can imagine a situation where I make several commits to my local git repo where at the final commit the build isn't broken but at any of the interim commits it might be.
    – Code-Guru
    Jun 16, 2013 at 20:41
  • 8
    No, do not commit failing test. But one of the points of TDD is precisely to make a failing test before coding. So commiting a failing test makes sense.
    – mouviciel
    Jun 16, 2013 at 21:33
  • 5
    @Code-Guru: For a DVCS, that rules should be: "Don't commit broken code to a branch that others regularly pull from". If others don't pull from your local repo, then that can be in any state you can live with. Jun 17, 2013 at 8:05
9

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:

  1. 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.
  2. 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.

7

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.

6

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
  • 2
    Be careful about recommending git reset --hard. It is one of the few commands in git that will cause you to loose work.
    – gnash117
    Jun 18, 2013 at 23:44
  • @gnash117 The example use was "I wish to discard this work"
    – Caleth
    Dec 1, 2021 at 10:20
5

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
  • I agree with you. I prefer to use a different branch to follow the rule "don't break the build" and merge the changes in the trunk only when the test passes.
    – Fil
    Jun 3, 2015 at 15:18
2

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.)

2

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:

  1. the test actually failed
  2. 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
  • 1
    Thanks for the answer to my old question. I have a lot more experience with git and pull requests since I posted this. But I still don't regularly do TDD. The companies I work for don't emphasize it. One thing that comes to mind is if you pair while doing TDD, then this is less needed since the review is happening as you write the code.
    – Code-Guru
    Dec 1, 2021 at 17:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.