5

I've read some basics about unit tests and TDD, but I find it hard to convince myself why TDD can have an advantage over only selected unittests.

I've read common arguments for TDD, but don't see the improvment compared to simple unittests. Maybe you can help to clarify:

  • TDD allows a loosely coupled, testable design -> I believe I can well imagine if a class is theoretically testable, without having to write tests; Loose coupling is a fundamental requirement that one can judge without refering to TDD?!
  • TDD gives confidence in refactoring -> Isn't it more efficient to add selected unittests as soon as you start refactoring? While you have to think what to cover by test, you still save writing a lot of tests.
  • TDD makes you define the interface first -> To me it seems some minor points are easier to decide while coding (which also agrees with agile?). It's even hard to predict a nested return data structure, but after coding I can often validate that I get what I expected. I made up the nested data while coding (a small unit) and see the behaviour. I'd save a lot of time writing the method first and using the validated result a posteriori to plug into a unittest.
  • from what I've read many (of course not all) sources state that TDD does require more development time even considering the benefit for debugging; merely the code quality improves -> Can it be that some programmers make different kinds of mistakes and for some TDD doesn't cover the type of mistakes they make anyway? So if my typical bugs are not approachable by predictable tests, it's not for me?
  • TDD aids changing teams, and larger interacting teams -> Is this a large issue? I can imagine it is, however as for the moment I program alone I cannot judge. How does TDD help teams in ways that... and here is the important point: other ways don't? It can force some discipline for less experienced team members, but apart from that any advantages?
1
  • I do not understand the close votes stating that this is not a real question. "TDD aids changing teams, and larger interacting teams -> Is this a large issue?", "TDD gives confidence in refactoring -> Isn't it more efficient to add selected unittests as soon as you start refactoring?" These are very concrete questions.
    – Giorgio
    Commented Aug 15, 2012 at 10:22

6 Answers 6

4

TDD allows a loosely coupled, testable design -> I believe I can well imagine if a class is theoretically testable, without having to write tests; Loose coupling is a fundamental requirement that one can judge without refering to TDD?!

You are right TDD helps you learn how to design this way, by forcing you to. But riding a bike with training wheels isn't always necessary, it's possible to design well without TDD.

TDD gives confidence in refactoring Isn't it more efficient to add selected unittests as soon as you start refactoring? While you have to think what to cover by test, you still save writing a lot of tests.

The problem is you may not remember what unit tests to add when you revisit the code days or months later. You may need to refactor just to add unit tests, breaking things unexpectedly. At the same time, unit tests may need to be refactored as well, increasing the overhead. Or unit tests may give you false confidence. It is very situational whether or not test-first or test-after is more effective.

TDD makes you define the interface first - To me it seems some minor points are easier to decide while coding (which also agrees with agile?). It's even hard to predict a nested return data structure, but after coding I can often validate that I get what I expected. I made up the nested data while coding (a small unit) and see the behaviour. I'd save a lot of time writing the method first and using the validated result a posteriori to plug into a unittest.

Thinking test-first takes practice, but has the advantage of ensuring your code is unit-testable without refactoring. BTW don't write an Interface first if that's what you are thinking, you write a interface first (all classes have interfaces).

From what I've read many (of course not all) sources state that TDD does require more development time even considering the benefit for debugging; merely the code quality improves -> Can it be that some programmers make different kinds of mistakes and for some TDD doesn't cover the type of mistakes they make anyway? So if my typical bugs are not approachable by predictable tests, it's not for me?

TDD can take far less time than normal, but you need to practice it enough and learn what tests are most effective. Do pure TDD on a pet project to see what it's like, but don't do pure TDD everywhere - it's a waste of time. Instead apply your TDD skill selectively (mix of hand/unit/integration tests some test-first some after), again this requires experience.

TDD aids changing teams, and larger interacting teams - Is this a large issue? I can imagine it is, however as for the moment I program alone I cannot judge. How does TDD help teams in ways that... and here is the important point: other ways don't? It can force some discipline for less experienced team members, but apart from that any advantages?

TDD takes a long time to learn and will send a project into a nosedive if you apply it to a team that doesn't know it at all.

10

You may think your design is loosely coupled and well usable. You may even be right, if you're really, really good at designing. The trouble is, many people think they're great designers, and some of them, in fact, aren't. TDD forces you to prove that the design has the properties you think it has, much like testing in general forces you to prove (approximately, not formally) that the code is correct, rather than just assuming it is correct.

About refactoring: You could add unit tests if and when you refactor, and in fact that is precisely what is suggested for working with legacy code. However, once you are at the point where you have to refactor a big mess of code, you almost certainly have forgotten details, edge cases, semantic nuances of certain values etc. etc. that should have been properly documented, but weren't. Again, existing unit tests will already capture this detail knowledge, which is why writing them early is better than writing them late. A test written immediately before refactoring is much less likely to actually test what it should test.

Point three seems to be a mixture of other points and some things I don't really get, so: I don't know.

Point four: I don't believe it's possible to have a bias towards "typical bugs" that aren't detectable by tests. What would those be? Sure, there are issues that are much harder to test than other issues, such as problems that can only occur in extreme integration situations (e.g. going live with millions of users simultaneously), but would anyone really have a knack for writing correct code except in such situations? Seems unlikely to me.

Changes in staff are definitely a big issue - the bigger the organisation, the bigger an issue it becomes, but even if you program alone, the person who looks at the code a year from now and wonders "what idiot programmed that??" is very different from the one who actually wrote it - even if they are technically the same person. This happens to me all the time. A unit test gives you a chance to understand why and in what way the system doesn't quite work the way you thought it did.

Overall, then, the really important point (to me) is to write the tests while you still understand the system fully and with as close to 100% coverage as possible. Whether or not you actually write each test a minute before you write a routine or a minute after is actually slightly less important.

4
  • 1
    "Whether or not you actually write each test a minute before you write a routine or a minute after is actually slightly less important." - Except that once you've come this far, there is zero cost to doing it first, and there is the benefit of knowing your test is correct because it will fail when your code is not written. Write the code afterwards and you can never have that reassurance.
    – pdr
    Commented Aug 2, 2012 at 10:26
  • 2
    There is a slight subtlety here. Usually, TDD advocate tell you to write the test before the method it calls even exists, so that it trivially fails. But this only works for scripting languages; if the test doesn't even compile! That gives you no reassurance at all that it will actually catch an error if it ever creeps in. The important thing is that you see the test fail meaningfully, i.e. catching an error in the actual code. If your language makes this difficult, best practice is to deliberately simplify the code (or back out the fix you just wrote) to verify that the test actually fails. Commented Aug 2, 2012 at 10:34
  • 1
    In those languages, you can code the method but still not implement it. That's what NotImplementedExceptions are for. Even in scripted languages, it's only the first test that should fail because the method doesn't exist; each subsequent test should fail because you've only written the simplest code possible to make the previous tests pass.
    – pdr
    Commented Aug 2, 2012 at 11:09
  • @Kilian Foth: I didn't say I think the code is loosely coupled. I said I think the code is testable. And it's possible to imagine or even check automatically if code is testable. If code is a mess is a different problem. TDD might help you have programmers writing bad code but I do not consider this case. Typical bugs could be a special data combination that for example is missing in some look-up database. And documentation for your own code is easier and even better than plain tests. So unless you deal with weak programmers I don't see an argument for TDD.
    – Gere
    Commented Aug 3, 2012 at 9:42
2

Let's address different point first: what do you understand by "simple unit test"? You'll write one test for happy-path only? Only methods you think might break? It's difficult to tell what you have on mind, but for the remainder of this post I'll assume you'll write only some tests where you consider them useful, instead of doing close-to-full coverage like TDD would result in.

Few notes to your points:

  • TDD makes you define the interface first: that's not really an argument for TDD, as you can define interface first without it. What TDD does instead, is make you focus on high-level details of piece you're working on at the given moment, without being disturbed diving deep into dependencies. But then again, this is not TDD specific.
  • TDD requires more time even considering debugging benefits: this is true, simply because in TDD you write more code. Indirect advantage is, that due to the fact you'll have much better coverage than "sporadic unit test" approach, regression will be less painful.
  • "if my typical bugs are not approachable by predictable tests (...)": is it really a bug when you know about it upfront and write code to prevent it before it actually happens?

I think you're comparing wrong approaches. TDD vs "sporadic unit testing", while it should be full testing vs sporadic testing. TDD will result in full testing, but you can achieve that without TDD just as well.

You need to decide how valuable you consider decent code coverage (not the delusive magical-fairy-tale 100%) vs "some" coverage; weight time spent on writing extra test code vs time gained when tests actually help you discover and fix regression related bugs, as this will be their main advantage over no-tests approaches.

6
  • I seems you do not mention any particular advantage of TDD over full testing? This is basically what I think for the moment. Of course the degree of coverage deserves attention. It seems if I add tests only to all classes I modify and on classes that depend on them, I should catch most bugs. So I would conclude selective testing hasn't been disproven (as compared to close to full testing which is essential in TDD)?
    – Gere
    Commented Aug 2, 2012 at 15:16
  • There is no such a thing as "full testing". Testing can never demonstrate that your code is bug-free.
    – CesarGon
    Commented Aug 13, 2012 at 17:50
  • @CesarGon: Nowhere in this post I claimed that (nobody ever claims that, this is prime example of straw man argument). Heck, I even explicitly stated OP should not follow "delusive magical-fairy-tale 100%" coverage.
    – k.m
    Commented Aug 13, 2012 at 19:37
  • @jimmy_keen: Then I must have misunderstood; sorry. In that case, what do you mean by "full testing"? How can you achieve such a thing?
    – CesarGon
    Commented Aug 14, 2012 at 19:59
  • @CesarGon: OP mentioned sporadic testing which, as I understand, is writing unit test when you feel it matters. Instead, one could start with TDD and decide when not to write unit test. After all, it's much easier to determine that "this code has small chances of breaking, so I skip test" (because you wrote similar code many times) rather than "this code will break, so I write test" (it's essentially the same story as is it a bug when you know about it upfront issue I mentioned). That's what I meant by full testing; do selection, but not when to write, but rather when not to write.
    – k.m
    Commented Aug 17, 2012 at 12:11
1

An important thing I've learned last week regarding TDD is that it is something different than writing your tests first only. The main advantage of TDD as a design principle is that it makes you think about what you need when you need it (the YAGNI principle).

Interesting reads:

0

First i started creating Unit tests for where i thought it's necessary. To make it worse mostly i've done it after i wrote the code to be tested.

The fact that i could choose what to test i could (i'm not a strong person you can say) leave out important/risky code parts because i said: yeah that's never gonna broke.

The fact that i wrote the tests after the code made my code hard to test. It was not unit based but instead a collection of hacks (which can simply be because i'm not talented).

Once i've started TDD

  • I could never leave out stuff (or a lot less likely) from testing
  • I wrote only the code that was really needed
  • My code was well (or at least better) structured.

Happy coding!

2
  • OK, but now as you have experience with TDD, can you just do the nice coding and merely imaging you'd unit test? Just pretend you're testing and code such that it would theoretically be testable?!
    – Gere
    Commented Aug 13, 2012 at 20:27
  • For me it is hard (as i said i might not be pragmattic enough) because there is no supporting force or urge. I like to be forced (or encouraged) to write good code. Also to your question, it less likely that my future change would break a business functionality as my tests will fail. It requires some time to alter the tests every now and then. But in my experience still less than when you have to figure out what a variable value was after a production error.
    – dbalakirev
    Commented Aug 15, 2012 at 9:14
0

I often write unit tests but I think that, as any tool, they are not the solution for all problems and one should evaluate from case to case whether they are appropriate or not. Using them systematically for every piece of code I write does not seem the most effective solution to me.

Here are some situations in which unit tests have proved to be very useful for me:

  • Validating methods that perform complex data transformations: unit testing with a lot of possible inputs and outputs allowed me to discover even small bugs very early.
  • Refactoring: unit tests help to ensure that the refactored code has the same functionality as the original code. Newly introduced bugs show up very soon.

On the other hand, I have some doubts regarding TDD and whether this approach is always the best. Here are some points of criticism:

Not all the code can profit from unit tests, e.g. suppose that you want to check that a certain event is dispatched to the correct component in your GUI. The most effective thing to do is to run the program and, say, check that the appropriate button is enabled. The effort needed to unit test the underlying logic would be too high compared to the manual tests that (at least if you have a decent development process) you are going to carry out anyway.

I sometimes develop interrelated classes as clusters and I am not sure which classes and methods I really need until the whole cluster of classes is finished. Such a cycle can last a few days. During such a cycle I can introduce new classes and then, on a second thought, realize that I do not need them and remove them. If I had written unit tests for these classes, I would have had to throw away the unit tests as well. So I find it much more efficient to (1) write unit tests for selected, critical classes and (2) start writing the unit tests towards the end of a development cycle (of a few days) when the class structure has stabilized enough, so that I do not waste my time writing unit tests that I am going to throw away later.

Your Answer

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

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