loading...

What are your thoughts on testing and TDD?

jooforja profile image João Forja 💭 ・1 min read

I discovered testing and TDD when I was in university. I was trying to find something that could improve my programming skills overall and not just my skills in a specific language or stack.

To cut a long story short, I had excellent results as my grades greatly improved across all programming subjects. It wasn't only due to TDD, but it played an important role.

Because of how well things went during university, I naturally used it and still use it in my professional life as a Front-end developer. And given that I'm able to keep my developer experience pretty stress free code-wise, I'd say that things have been going pretty well.

If I had to say why I find testing and testing as used in TDD so amazing is because of 2 things:

  1. It forces me to think before and while I write code. This makes me find holes in the requirements and opportunities to improve the code's design.
  2. _It gives me a development process that lets me stay focused on my task at hand, which makes me more productive and less stressed.

Although it can be frustrating at the beginning, my opinion on testing and TDD is pretty good, and I recommend people try it out.

What's your opinion on testing and TDD?

Posted on by:

jooforja profile

João Forja 💭

@jooforja

Software developer trying to bring order into his craft. I write about my journey, thoughts, and practices. Currently focused on web frontend with React.

Discussion

markdown guide
 

Unsurprisingly, whether TDD and automated testing in general is useful/essential depends greatly on the task at hand. Similarly, riding your bicycle to work has a great ROI value, but using it to get a snack from the fridge, not so much 😆.

 

🤣🤣🤣🤣🤣🤣 Your answer deserves a spot in my "Funniest Software Dev Comments" section.

 

There's a difference between an engineer and a hacker. One isn't better than the other, but have different values.

Applying an engineering approach to software development is impossible without test driven development, but sometimes hacking something together is where the value is.

 

You reminded me of a talk from Kent Beck . He expressed really well that idea that hacking and engineering can have important roles in development depending on which stage a project is at.

 

Hi, I think that TDD is great, not only does it make you think about the code you’re implementing, but it also can highlight issues in pre-existing code if you are writing tests after implementation. I do find that I write my tests after writing my code, but that’s fine it allows me to confirm that the code I’ve written works as I thought. It also helps find bugs in my code.
I do think that there are still a lot of developers who don’t write tests mainly because the projects they have worked on before do not allow time to learn how to write tests and write testable code. So when a project insists that there is 80% test coverage a lot of developers leave writing tests to later in the process, when it’s too late because they don’t know how to write tests and testable code. Which I think they should do, it helps write cleaner code and less bugs.

 

That's a great observation. A lot of developers don't know how to write tests and how to write testable code. I think that a lot of that has to do with the culture of the company they're in. Especially when we're talking about project-based companies, which tend to privilege shipping as soon as possible vs. reducing the cost of development on the medium to long term, it's a kind of environment that privileges hacks over a sustained practice, which makes testing and TDD hard to flourish. So it's unlikely for someone working in a situation like that to be given the time to learn or to have someone to teach them how to test/TDD. At least, that's my experience.

 
  1. The clients don't pay for writing tests, the pay for code that works. That established, how much (percentage-wise) are you planning to spend on writing tests?
  2. Brenan Keller @brenankeller wrote: "A QA engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 99999999999 beers. Orders a lizard. Orders -1 beers. Orders a ueicbksjdhd. First real customer walks in and asks where the bathroom is. The bar bursts into flames, killing everyone." (and that's my personal experience, writing gazillion tests and still failing at runtime)
  3. TDDS are just as important as making small git commits or saving your work every 2 minutes; They are sort of insurance that you're not messing up, yet the unexpected will always surprise you.

Yes, TDD is great, but only when it makes sense (i.e. don't make it into religion);

 

People certainly can take it too far and make it into a religion. Like it is some silver bullet that will solve all development problems when it won't. It's a technique that has its advantages and limitations, although I'd say it is one of the more useful ones I've come across. But I'm probably totally biased.

About point 2, I think it covers one of the limitations of TDD. TDD can never find an unexpected new bug in a system for a simple reason. We are the ones writing the tests, and that means that they are limited by our imagination. And, speaking for myself, we aren't very good at imagining all the ways our programs can break. We could try to stretch our imagination and write tons of tests, but then we end up with a codebase that's much harder to maintain than it needed to be. The way I found to deal with this issue is just to write enough tests to force me to implement a feature, and after the feature is "done" use exploratory testing to find bugs I didn't imagine could exist. And then write regression tests for those new-found bugs. It's been working great so far :)

 

Regarding no. 1: I find this a very simplistic reply to TDD. The client doesn't pay for code that works, they pay for code that gives them ROI. Working code is just one aspect, among many.

If you write the right tests the right way, they always increase ROI, not diminish it.

 

How much time do you spend on writing tests?
Let's say the client asked you for a feature that will take you 1 hour to write and 2 hours to include tests. Will you charge him for 2 hours? What if the client cannot afford two hours? Will you be willing to reduce the amount of tests? (Imagine it's a question you're asked in a job interview - how critical is it for you to meet the deadline?)

There is a fundamental flaw in your (your client's) view. If you do TDD, testing IS developing.
Developing software without tests is like developing cars without tests. This way or that way your product has to be tested. I'd rather write tests before I write code and let my implementation be guided by TDD, rather than making tests an afterthought.
One way or another, the client will have to pay for testing. My argument is that TDD (testing beforehand) has a higher ROI than doing it afterwards.

Besides, if a client begins micromanaging those he pays for developing (and should know best), that's the point when such client should probably take his business elsewhere.

Simply put: If the client cannot afford solid development (and this includes tests), then he cannot afford any development. No car manufacturer would sell a car to a client that cannot afford the entire process and asks them to skip tests and testing.

I get you. Writing tests is indeed important, I'm happy for you that all your clients can not only afford your work but also understand and value the quality of your outputs. I also appreciate the fact you clearly know how to efficiently write concise tests that give you and your client great certainty that the code won't fail. I wish all the clients would be like your clients.

We have some clients who cannot or do not want to afford individual development (with everything that is included), those usually opt for standard solutions (products already on the market).

Bottom line, we have never had a client who did not want workable code. And how do we know code is workable? We test it.

Of course, that does not mean 100% code coverage, which just doesn't make sense.

 

I think item 1 does not consider the time spent on executing tests. When we work with software that keeps evolving during the time, writing tests makes you spend less time with tests then executing them manually every time.

 

Pros

For me it's the best way to implement a feature, you simply implements slowly and with a lot of confidence.

And the mais reason that i like it, is that you tend to create the feature in the most readable way.

 

Completely agree!

I liked how you mentioned that it lets you implement something slowly and with lots of confidence. I think it clearly expresses the feeling of being in control of the code that TDD can give.

 

TDD: absolutely fantastic and valuable if you have the skills and discipline to use it. One of the most elusive phenomena in software engineering, something of a holy grail, many people say they want it, comparatively very few actually do it (or do it well, at least).

 

I'm frankly still struggling to grasp how to implement tests/TDD effectively in a large, real-world application.

(More specifically, I have trouble with the idea of unit tests; functional tests fit my thinking and understanding better).

I've had a taste of how useful it can be in small side projects, but when I look at the large-scale fintech loan pricing product I develop for a client, while I can INSTANTLY see the value in having a suite of tests that will guarantee adding a new variable doesn't break the pricing algorithm, I just can't picture how to build up the suite of tests around it.

I'm also still "catching up" on general thinking around pure functions and testable code, so I'm sure that's where a lot of the problem lies for me. Poorly written code will never be effectively testable.

So all of that said, I'm working hard towards learning and using TDD.

P.S. - Don't get "comfortable enough" with what you know and stop learning and keeping up with best practices and paradigms, especially not for years "while you focus on the business side." Playing catch up after 15 years in the industry is just. no. fun.

 

I agree that in real-world applications, it's harder to do TDD. In my opinion, it's due to the increase in the complexity of the domain. So my strategy has been to break features down into degenerate cases that are simple enough for me to know how to write a test for them. After implementing the degenerative case, I then proceed to iterate into the next degenerate case that brings me closer to the full-fledged feature.

This example is a bit too simple, but if I have to develop a table that fetches data and has tons of sort options, my tests will probably go something like this "Shows loading while fetching" -> "Can show an error message when fetching data fails" -> "Show's message to the user if there's no data to show" -> "Can show one row" -> "Can show multiple rows" -> "Can filter rows by 'Property1' " -> "Can filter rows by 'Property2' " -> "Can filter rows by 'Property1' and 'Property2' " -> etc. Some tests might be redundant at the end of the implementation, and I'll probably delete them if that's the case. But going through this process gives me more confidence in what I'm doing and tend to lead to better tests and design.

Small steps help a lot.

 

Testability comes from good design and architecture, I agree.
But starting somewhere is really important. Maybe you can start with a high level test without edge cases like:

Given this input I expect this output. Add a few of them covering different code paths (delta coverage per test) and try to move forward.

 

The Pragmatic Programmer (book) nails it:

Thinking about writing a test for our method made us look at it from the outside, as if we were a client of the code, and not its author.

And:

...most of the benefit of testing comes from thinking about the tests and their impact on the code. And, after doing it for so long [30 years], I could do that thinking without actually writing tests. My code was still testable, it just wasn't tested.

 

That nails is. Absolutely. TDD is less about tests themselves. Rather, it is a way of exploring, designing, implementing, securing and validating the solution domain.

All of these things are done driven by testing, but tests are never the real deal themselves. They are always means to an end.

 
 

Gotta love that book. It aged really well!

 

Indeed. But you really don't need to be doing TDD for 30 years (or at all) to get there

 

As with most ideas X in software dev: You have four types of developers along a continuum of:

(1) Has significant experience in X to know the pros/cons? yes/no
(2) Finds X to have any merit? yes/no

Make sure to eliminate all those that have a NO on (1), then compare the remaining Yes/Nos on (2).

Part (2) is easy. Part (1) is hard and will, if not done properly, likely bias your summary of total yes/nos.

 

I use TDD semi-regularly in my hobby projects. I've also spent the last month getting my team's main tool set up with a proper test suite. It's more for regression tests than TDD, but I'm excited about it, all the same.

I would say my experience with TDD is fairly similar - my code is better, and I think about my designs in more detail before implementing. I don't usually implement all the tests up-front, but I'll write down a large number of test synopses, and then implement the most important ones (and come back after to fill in the edge cases, just to validate my work).

 

I use it all the time, based on the same two points you mentioned.

 
  • Testing is mandatory.
  • Manual testing takes time and is painful, because you need to test everything again after changing anything, so it should be avoided. Many thinks that it's faster than automated testing, but it's not true.
  • Coding is not only typing code. It needs to make sense. Automated testing is not only testing. It needs to make sense. If it doesn't, it's not because of the test, but of the person who types.
  • If you spend too much time on your test, you're not efficient enough. You can fix that. The problem is not the tests.
  • If tests are not written early in the project:
    • Nobody will tests because "nobody tested before".
    • It's way, way more difficult to introduce tests to code which is not designed to have tests.
  • TDD is not a religion, and it has many flavors. Not only the red-green-refactor one.
  • TDD is not important. Writing good tests is. What is good tests? Doesn't break each time you change something, test what's important for your precise project.
  • Refactoring is the most important part.
  • TDD doesn't (shouldn't) drive a design. TDD doesn't know anything about what your company wants to do with the software, what the business constraints, what the invariant (stuff which doesn't change) you need to respect. Think, before using easy recipes.
  • Use whatever you need in your precise context. If you have mostly data from outside, TDD won't really help you (good luck to unit test external APIs...), for example.
  • It's not because your app still fail in production that your tests are useless. You don't see every bug you avoided, and every bug you'll avoid each time you change something / refactor something.
 

Great answers. I agree on all of these points.

Only on one I'd like some explanation:
"TDD doesn't (shouldn't) drive a design."

Design meaning what the software does (business logic), then I agree. But TDD can perfectly be used to drive the implementation process (design in a different reading of the word: how the business logic is implemented).

 

I kindly disagree: The tests should not affect the implementation.

I use TDD all the time (on my job and 90% of my hobby projects) and the most I think I like about it:
the structure of the test can be completely decoupled from the structure of the implementation:
Maybe the test has 2 classes, but the implementation has 1, 2 or even 15. I always test the outside behavior of the module, never private details.

I think we actually agree, and there is just a misunderstanding. I absolutely agree with what you say there.

Let me rephrase: The test should be used to guide you during your implementation phase (exploring the solution domain, especially the refactoring part).

Of course, tests and implementation should be decoupled.

 

I think sometimes TDD gets confused with ensuring good unit test coverage. True TDD means writing the tests before the actual code, something I admit I've never been a fan of or managed to get into the practice of doing. However, I am a fan of good (meaningful) test coverage, I just write tests after writing the code.

I think after a while of writing tests, you end up learning how to write code that's more testable as a matter of course.

 

Having test cases is important and it's useful in a DevOps perspective due to reliance on automating the small stuff which includes creating testing pipelines to test your code without you running it yourself.

As for TDD, it depends on your company or organisation engineering practices. They might require you to do it and you can do it to affect yourself. Ideally, I would go for TDD and as others have mentioned it's really subjective. Since there will be a need to write code that requires you to get it out as soon as possible. Especially you are creating a prototype or MVP who has a very short timeline for it to be completed.

 

I have used TDD once and find it really useful and fun to learn. I was able to write my codes more organized and easier to understand. But the thing is were not using TDD, clients wants to get things done asap.

 

I'm of the opinion of DHH, that there's a lot of test-induced damage caused by TDD.

These days I almost never write unit tests anymore, I've almost completely replaced them with integration tests, as suggested by Kent C. Dodds. As I work on the backend with Kotlin, I use test-containers to spin-up the DB, RabbitMQ or whatever, and then test the app as if it was a function (hitting an endpoint and asserting the response, for example).

One of the biggest advantages to doing this is that the tests are completely decoupled from the code (which you typically get when you use mocks), so you can refactor freely and all you have to do is run the tests to see you haven't broken anything.

When you do these sort of tests, TDD doesn't apply much anymore because you often need to connect a few components before you can actually see a result, so the "write a failing test, fix it" iterative process doesn't fit.

One question I often get when someone sees this approach for the first time is, what do you do when the system under test has a lot of variations? For those cases, I've found that a table-driven approach has always worked well. I've found that the amount of permutations you'd write if you were unit testing would pretty much be the same, if not more as some tests from other layers might overlap.

Another good article on the topic that describes well the philosophy: blog.twitter.com/engineering/en_us...

 

If you are a very lean team trying to move quickly and progress is more important than stability then it makes no sense to spend time writing tests for code you know will radically change.

It's so frustrating to see teams trying to hit coverage numbers with useless tests. Put a value in a prop and see if the value was rendered. Lol, we've got 90% coverage with mostly useless tests that add no value, but we've got high coverage.

Projects move through different phases and high-performance teams adjust to ensure the business is supported in a way that makes the most sense for each phase.

 

I personally never found TDD all that helpful. It can be a great tool for providing feedback during development but it's not the only one.

With typescript + hot reloading, TDD doesn't really bring any value for me.

I also rarely write unit tests for the same reason.

 

Hi, João. I'm a code newbie and I'm still on that frustrating stage, trying to understand and implement tests in React. The concept of testing and TDD is great, but right now I don't know exactly how to get started. Do you have any recommendation for me?
For instance, do you use react-testing-library? Do you recommend a good resource for learning it (or other testing libraries)? Any hint will be really appreciated. :D

 

Hi Marina :)

When dealing with React applications, I tend to use Cypress, cypress-testing-library, react-testing-library, and Jest.

While knowing how to use test-related tools is a good thing, after you've learned the basics (running tests, assertions, and mocks), learning more about the tools probably won't do much to alleviate your testing/TDD frustrations, as most of them come from a lack of understanding of the goals and processes of testing/Test-driving an application.

Do you have a specific example of something that you would like to test but don't know how? I think I can give more accurate and useful hints if we go over a specific example that's causing you frustration, rather than talking about what I think is important in general when testing an application :)

 

Hey João, thanks for replying.
I think you are right, I'll first learn the basics of testing before diving deeper in the tools. That's where the frustration comes from, for sure.
No specific examples right now, but you answer already helped me. And I'll keep following your other articles on this subject. Thanks!

Glad I could help. Feel free to reach out if you have any particular question you think I could answer :)

 

I love it when people say about TDD. I also love to use it. But I'm too lazy to do that😅

 

I don't follow TDD exactly, but for some things I do write some tests. It might not be a full on Unit Test that we are familiar with, but I usually do something to prove that what I wrote works.

 

It really depends on a task, if I write something really easy, I will avoid testing at all, because I see no point at all, but if it is not the case, then It what gives me carefree attitude.

 

Breaking things and medium / long term maintenance are real risks. If I plan to maintain it, testing is a must.

I wouldn't do UI testing, though. I am just not convinced.

 

I haven't used testing in any of my projects in a 26 year professional career

 

That's interesting! How do you deal with regression issues when working in codebases with other people?