DEV Community

loading...

TDD Practicality

stevekaufman profile image Steven Kaufman ・2 min read

Background

As a young person getting into programming, it's hard not to notice the wildly varying cultural perspectives around the industry.
From just watching YouTube tutorials, I think I was completely oblivious to the very existence of unit testing for at least a full year after learning the fundamentals of Javascript.

Shortly after, however, I stumbled upon Robert C. Martin (Uncle Bob)'s talks on YouTube.
If you haven't already seen his six publicly available hour-and-a-half long talks, I highly recommend watching ALL of them.
I've personally rewatched and listened to them like podcasts in the car at least three times over.

Youtube: Clean Code - Uncle Bob


Uncle Bob and TDD

To summarize, he's one of the authors of the Agile Manifesto, a 50+ year software professional, and an extreme advocate for Test Driven Development.

This advocacy for TDD sets him apart from even some of the other people who worked on the Agile Manifesto (which was designed to be a great compromise between wildly different programming philosophies).


My Question

After playing around with TDD in personal projects, I can see its value, and I'm inclined to believe what Uncle Bob says. However, I want to be careful and make sure that I'm not blindly indoctrinating myself into some idealistic nonsense before I have much in the way of real world experience.

I've seen the debates against TDD online, and Uncle Bob himself warns against trying to adopt things like TDD and Agile too quickly, as they're intended to be skills and disciplines that one has to practice to become effective at.

My question is simply this, if anyone would like to share their opinion:

Why might TDD ever be a bad idea?

or:

Why is TDD always a good idea?

Discussion (20)

pic
Editor guide
Collapse
cariehl profile image
Cooper Riehl • Edited

Why is TDD always a good idea?

Only a Sith deals in absolutes...

I strongly believe in TDD as an effective way to design code that can be easily tested and verified. I think TDD is an important concept to keep in mind when starting a new project. Sadly, we can't apply TDD to every single project we work on, primarily due to time constraints.

Why might TDD ever be a bad idea?

The biggest "issue" with TDD is the additional overhead that gets added to the project as a result. It takes time to write and document tests up-front. It takes time to train our fellow developers on our tests, and the TDD process in general.

I will almost always argue that this overhead is worthwhile, and that the up-front complexity of TDD will pay itself off in spades when the project must be modified in the future. Unfortunately, there are real-world scenarios where we must meet a tight deadline, and we simply don't have time to follow all of the best practices.

Sometimes, we need to write a new component in the middle of several layers of existing services, none of which are being tested properly. In order to apply TDD here, we may need to write additional tests for components other than the one we are modifying, or we may need to create an excess of "fake" components in order to test our new one effectively.

Sometimes, we have to create a "proof-of-concept" project on a tight schedule. If we spend the time to focus on TDD, our product may not make it to market in time to beat the competition.

I believe it is most common for TDD to be ignored when the priorities of the project become "functionality > maintainability". Working on these projects sucks, because as developers, we know that we're just creating a mountain of technical debt that will have to be addressed later. However, sometimes we really do just have to "bite the bullet" and get something working, and pray that we have the time later to come back and improve it.

Collapse
stevekaufman profile image
Steven Kaufman Author

Sadly, we can't apply TDD to every single project we work on, primarily due to time constraints.

Is that so?

If we spend the time to focus on TDD, our product may not make it to market in time to beat the competition.

This is another subject Bob Martin touches on. The myth (according to Bob) that being 'first to market' is of the utmost importance. "Was Facebook first to market?" he asks. And, of course, neither was Google. But you don't see people walking around using 'Yahoo' as a synonym for internet search or even mentioning MySpace.

It's really a misunderstanding of the Lean startup philosophy, which says that startups should quickly produce a minimum viable product and rely heavily on consumer feedback so that they can quickly adapt. This ability to quickly adapt is key, and is actually enhanced by TDD.

The word viable is also important here. A minimum viable product (in software), I would argue is not a mess of code that barely works, but a well-done piece of software that is perhaps missing some of the features that would be necessary to call it 'done' or 'ready' from a business perspective.

The only way to go fast is to go well - Bob Martin

Collapse
cariehl profile image
Cooper Riehl • Edited

The myth (according to Bob) that being 'first to market' is of the utmost importance.

You have good insight. I agree with you, and Uncle Bob, that "first to market" should not be a company's primary motivation.

startups should quickly produce a minimum viable product and rely heavily on consumer feedback so that they can quickly adapt. This ability to quickly adapt is key, and is actually enhanced by TDD.

100% agree. If I ever create a new product, I plan to approach it with this in mind. Prototypes and minimum viable products have their benefits, but they should never be considered the "end goal". They should always be a single step on the journey towards a well-maintained, easily-extensible, clean and functional system. It sounds like you understand this very well.

I think your interpretation of TDD theory is spot on. I'll end with a single piece of advice, for you and anyone else reading.

As developers, we love to think about the "ideal scenario". Full test coverage, beautifully clean interfaces, proper documentation, etc. There's a reason we love these things - it's because they're proven to be effective, when they exist. Unfortunately, we rarely have time to implement all of our ideals.

When we're faced with deadlines (whether it's from management, another team, ourselves, or other circumstances) we have to pick and choose which best practices we want to follow, and we even have to limit how much time we spend researching all the options. In these scenarios, it's important to understand what each practice brings to the table, and which ones will be the most effective for our project.

And if we are forced to ignore best practices too often, it's important to remember that we can always find another job ;)

Collapse
theowlsden profile image
Shaquil Maria

Totally agree with this comment. Good explanation!👌🏾

Collapse
cariehl profile image
Cooper Riehl

Thank you, I appreciate it! 😊

Collapse
sargalias profile image
Spyros Argalias • Edited

Other responses have already touched on the value of testing, prototypes and time to market. I agree with those sentiments. In more detail, it may make more business sense to ignore the overhead of tests to be first to market. However, in a large project, in my experience, tests provide a lot of long-term benefits, both in development speed and robustness of the software.

However, you don't need TDD to write good tests.

So I'll give my opinion on whether the technique / workflow is worth it compared to writing tests afterwards.

The benefits, of TDD, in my experience, are that:

  • it forces you to design the public API of what you're about to code ahead of time (because that's what your tests will call). This can result in better designed code, that's easier to use.
  • it ensures your code will be tested
  • it encourages your code to be simple and decoupled (clean) (it needs to be, otherwise it would be hard to test)
  • it forces you into a very small development loop. You code a tiny part of a feature (which is very easy to do) and test it, ensuring it's stable. This almost completely eliminates debugging compared to programming for 30 minutes, then testing your work and having to debug multiple little mistakes to get it working
  • it forces you to write a failing test first. Failing tests are good because they ensure your test isn't passing when it shouldn't. It's more efficient to write it first, rather than write a normal test, then deliberately make it fail and then fix it again.

But you don't actually need TDD to do any of those things. For example, you could design the public API of your code ahead of time, even without TDD, or refactor into a good public API. You can also write the same clean code with TDD or without. And so on.

TDD is just a nice and efficient process for doing those things. Also, TDD forces you to do them, even if you normally wouldn't.

As a final note. I normally can only do TDD if I have some idea of what the solution / public API is going to be. If I have no idea of what to do, I either:

  • create a small prototype (try some stuff out in code) without tests. Then re-write it properly with TDD.
  • code until something works. Then refactor my code and add tests. The resulting code should be equivalent to what I would have written with TDD.

What are your thoughts (or anyone else's) on this?

Collapse
cariehl profile image
Cooper Riehl

Great response! I really appreciate that you made it clear you're not "against" TDD, while still explaining some situations where it's not a silver bullet.

I'm curious to hear about your experience with writing code first and then adding tests, versus using TDD from the start. How often do you find one approach is more useful than the other? Are there certain types of projects that are easier to work on with one of those two approaches? Let me know!

Collapse
sargalias profile image
Spyros Argalias

Thank you :). In general I do TDD whenever I can (assuming I'm not working on something where I wouldn't write tests). I think it's a very efficient workflow, for many reasons.

But as I mentioned, you can get the benefits of TDD by doing them manually. TDD is slightly more efficient, for example, as mentioned above, for writing a failing test.

So for me personally. I feel a very small difference when using TDD or not using it.

However, I imagine that there are many people who wouldn't do anything that TDD helps you with unless they used TDD. For them, TDD would be a much more efficient process and result in much cleaner code.

When I don't use TDD

If I write tests and if I have a general idea of what I need to do, I use TDD. If I'm not sure what to do, I mess around with code a bit first, until I have a general idea of what to do.

Unfortunately, I can't think of a good example at the moment. Let's say, for the sake of example, that you have no idea what your implementation should be. You then mess with some code and discover that promises may be good to use. At this point, you have some idea of what kind of tests you can write. So you can either re-write the code with TDD, or just refactor what you've got so far and add tests.

When I don't write tests at all

This is a separate topic to TDD, but in general I don't write tests if I'm creating a quick prototype for something. That's because I don't need the code to be robust, and I'm not going to be working on the code for months to come. I just need something quick and dirty to see if I've got the right idea in what I'm creating. Spending time writing tests will probably just be wasted time.

Another example is some hobby game development I've been doing. I've learned that, during initial development, I change how things work a lot. In other words, things stay in the trial or prototype stage for a long time. For these kinds of projects, I'm avoiding writing tests more and more, until I feel like the requirements are more stable. (Or until I feel like I'm spending too much time debugging and I'd rather automate the bug-finding process by writing tests.)

The thing with tests is that they aren't free. They take time to create. Further, if you change the public API of the code, you also need to modify the tests. So they make it harder to make large changes. So, for prototype code where things may change a lot, tests are too costly. They are worth it in enterprise projects because their benefits outweigh their costs. They reduce bugs in your code (critical for real applications but not important for prototypes), they reduce time spent debugging and reduce time to do manual testing (which would be significant in large applications, but low in small applications or prototypes).

Okay, that was an essay... I hope it's somewhat useful. If you have any questions, comments or even different opinions, let me know :).

Thread Thread
cariehl profile image
Cooper Riehl • Edited

I love and appreciate this "essay"! I read the whole thing, and I think you and I have similar thoughts on TDD in general. I especially agree with this point:

I've learned that, during initial development, I change how things work a lot. In other words, things stay in the trial or prototype stage for a long time. For these kinds of projects, I'm avoiding writing tests more and more, until I feel like the requirements are more stable.

I am the same way. When I work on my personal projects, I tend to spend way more time reorganizing systems, just because I find it enjoyable. If I forced myself to write tests for every change, I would get way less enjoyment out of my projects, which would defeat the purpose! I program as a hobby because I find it fun, not because I feel the need to create a useful product. That's what programming professionally is for ;)

I do try to force myself to remember TDD principles whenever starting a new project, and I'll often start by writing a few tests for a component. But similar to you, as soon as the tests get in the way of my enjoyment/productivity, I stop forcing myself to write them. In a workplace setting, I would be more hesitant to abandon my TDD approach; but in a hobby setting, the only person I'm trying to please is myself.

Thanks for your response :) if you have a response to this one, I'll happily keep the conversation going!

Thread Thread
sargalias profile image
Spyros Argalias

Nice yeah, seems like we have similar thoughts indeed :).

Thread Thread
bertilmuth profile image
Bertil Muth

One situation where I don't use TDD is when I need to call some framework/library methods, but don't know how to use it yet. I would rather "play around" with the library a bit, until I know how to use it, without writing tests, and then start my own development based on it.

Thread Thread
sargalias profile image
Spyros Argalias

Good example, thanks :)

Collapse
theowlsden profile image
Shaquil Maria

Uncle Bob himself warns against trying to adopt things like TDD and Agile too quickly, as they're intended to be skills and disciplines that one has to practice to become effective at.

I totally agre with this. TDD or even testing is not something you just learn one day and the next you can easily implement it. It's a discipline you need to become good at by practicing. This means that if you are trying to adoot it into every project and you are just getting started it will be a hassle and you will be spending more time worrying about that than working on the actual project. (Personal experience 😂😅)

Why might TDD ever be a bad idea?

Well it would be a bad idea if you are getting started and your are trying to enforce TDD on every project. Depending on the complexity of the project it would be worth it to do that or not. One main issue with TDD is that by implementing it you are creating a lot of overhead, and this can result in delays in your project.

Why is TDD always a good idea?

You will be building a robust project. By implementing TDD, with all it's overhead, you will be setting up a playground to code the best logic you can in a clear way. By using TDD your code will be (potentially) smaller, easier to read, straight to the point and reusable.

Collapse
cariehl profile image
Cooper Riehl

By implementing TDD, with all it's overhead, you will be setting up a playground to code the best logic you can in a clear way.

I love this comment, it's so true! When we have a well-tested project, it becomes so much easier to mess around with new features, and find the best way to add them to the project.

Writing tests first really forces us to break the logic down into the simplest possible operations, which naturally leads to cleaner interfaces and more self-documenting code. And clean code is a lot of fun to interact with, just like a playground!

Collapse
theowlsden profile image
Shaquil Maria

Writing tests first really forces us to break the logic down into the simplest possible operations, which naturally leads to cleaner interfaces and more self-documenting code.

Yes! that self-documenting part is very important too. If you are working on a piece of code today and have to get back to it in a month, or another team member needs to interact with it, it really makes it easy to understand and continue to work on.

And clean code is a lot of fun to interact with, just like a playground!

It certainly is. I'm still a noob at testing, but I will for sure try to implement it in every project that I think is suitable.

Collapse
stevekaufman profile image
Steven Kaufman Author

Darn, nobody wants to play devil's advocate.

Thread Thread
theowlsden profile image
Shaquil Maria

Hahaha, I guess not😅

Collapse
eljayadobe profile image
Eljay-Adobe

TDD is a forcing factor to drive a good chunk of SOLID principles.

TDD shores up OOP for languages that do not provide design by contract in the language. Languages that have contracts still can use unit tests, but the number of unit tests needed is greatly reduced.

TDD differs from other automated testing in that TDD-style unit tests are written by the developers, for development. Run in debug mode, as a design tool — not as a testing tool. Integration tests, acceptance tests, systems tests, performance tests, stress tests are written by software development quality engineers, which are run against the optimized release build. (Assuming a project big enough to have roles fulfilled by different people.)

TDD unit tests provide several values: 10,000 quatloos of primary value for guiding design. 100 quatloos of secondary value for enabling aggressive refactoring. And 1 quatloo of tertiary value as an artifact that can be used to detect regressions and continue to ensure basic correctness.

Collapse
cariehl profile image
Cooper Riehl

Just want to say, I love your usage of "primary value", "secondary value" and "tertiary value". Everything provides value in a variety of ways, and I applaud you for pointing that out.

Collapse
adnanhz profile image
Adnan

I wish this question was split between the backend and the front-end. As a fullstack developer with a focus on backend, I've seen how TDD has greatly improved the quality of our backend API.

But on the front-end (vuejs) we're still developing the interfaces with only manual testing by the developers and by testers. Granted, most of our interface is composed out of simple tables, forms and buttons.
Isn't it much more time consuming to mock the response of the API to test the front-end? We just develop the front-end (no TDD) and the backend (TDD), then hook them up together in a short meeting between the backend and front-end devs.