DEV Community

João Forja 💭
João Forja 💭

Posted on

What made you interested in learning how to write tests or how to TDD?

I got interested in learning how to TDD and how to write tests because I wanted to improve my overall skills as a developer.

I had this idea that there are too many technologies, and they change so fast that I can't learn all of them in-depth, so my countermeasure was to improve my testing skills. Tests will tell me if what I'm building is working or not. They will also give me feedback on the design of what I'm building. And all of this independently of the frameworks/tools I'm using. So even when I have to dig in a codebase that uses technologies that I'm not so familiar with, I'm always reasonably confident that by writing tests and by reading the technologies' documentation, that I can get productive pretty quickly.

What about you? What made you interested in learning how to write or learning how to TDD?

Discussion (16)

Collapse
panditapan profile image
Pandita

I've fixed so many bugs in one single app that I was wondering how to avoid them as much as I could. Talking about this to a QA engineer (who's also a friend) she told me everything about unit testing and let me know that most of the bugs I fixed were preventable with it (and probably integration testing but I haven't reached that point yet 🐌).

It took me a while (like, 2 years) to actually start including unit tests in our development flow... convincing is hard but, hey we're now doing it! \o/ I'm still super newbie with it, I'm terrible at inventing test scenarios, but I'll get there! There's always areas of improvement c:

Collapse
imforja profile image
João Forja 💭 Author

Congrats on convincing people to start doing unit tests! That's probably the toughest part of making the team adopt unit testing.

Regarding coming up with test scenarios, I find out that it becomes easier if I build my modules as units that have simple inputs and outputs. If I have a good grasp of the inputs, I can fiddle around with the possible values and define what the output should be. Of course, this approach implies that you'll need to organize the code to make it easy to test, which is good because code that's easy to test is code that's easy to understand. Hope this helps :)

Collapse
panditapan profile image
Pandita

aah thanks for the advice! much appreciated 💖 I'm also facing the problem that I don't really know exactly what to expect as an output because there aren't any requirements (sad I know), but maybe making the code much smaller than what we currently have might help. Thankies!! 😄

Thread Thread
imforja profile image
João Forja 💭 Author

Unfortunately, that's common, and it tends to be one of the core issues behind not knowing what to test.

Making the code the code base as small as it can be without compromising readability is an excellent approach to keep code maintainable. I'd say that it's one of those practices that we should follow almost always :) But this approach won't fix the situation :/

Usually, when we don't know what to implement, we should ask management or someone who should know that. The real problem comes when management doesn't know what needs to be implemented because the client also doesn't know what needs to be implemented! It might seem that the client is at fault here, but actually, it's just the way these things work, and the sooner we accept that, the faster we can start working on a solution.

Most often, the client has a general idea of what the app needs to do for it to benefit the business, and it seems quite well thought out when he talks about it. But as we start to develop three issues become apparent:

  1. The client kind of knows what needs to happen, but he doesn't know HOW the app should make it happen.
  2. We find out that we didn't entirely understand what the client meant.
  3. The idea that the client had of what the app needs to do keeps changing. The longer it gets to release something that real users can use, the worse the three issues above will get, and ironically, the harder it will be to release something that real users can use. And this is because we lack feedback/guidance from the real world, so everyone involved in making the app (from clients to devs) will get stuck in their heads trying to guess what the app needs to do to be a success. But we are awful at guessing, and it's easy for this process to keep going on indefinitely and burn through all the project's budget.

One solution to this problem is to simplify an application to the point where we can sit down with the client and easily map what's on his mind into inputs and outputs. Or in other words, we can write acceptance tests for the app. So if a client wants an app with 50 features, we might start with a single feature, or with degenerates of 5 the 50 features that we can figure out with the client how they'll work. Which approach to take will depend on which one leads to the shortest route to deploy something for real users because the goal is to get stuff out there as soon as possible. The faster we deploy, the sooner the client gets money, the quicker we get out of our heads and get guidance from the real world, and the more likely the project is to succeed. Also, there isn't a better way that I know of, to make sure that we're on the same page as the client than to show him/her a working application. After we have something working, it doesn't matter how minimal it is, the conversations start to become a lot more specific and a lot more productive.

Well, I ended up writing a bit more than I was expecting. The essence of what I wanted to say is that not knowing what to expect as an output of a test, can be a sign that there's a problem with how the project is being managed and that it is urgent to reduce the scope of the next release to the point that we can clearly define the requirements. Otherwise, the project will likely fail.

I hope that my mumbling is useful in one way or another xD

Thread Thread
panditapan profile image
Pandita

aaah thankies!! I can't go into much details regarding the project but I'll definitely try to use your suggestions in order to help ease the unit testing process. I appreciate your input a lot \o/ (though I guess you could use this reply as a blog post wink wink)

Thread Thread
imforja profile image
João Forja 💭 Author

haha thanks a lot for the suggestion. I think I'll follow it :D

Collapse
steelwolf180 profile image
Max Ong Zong Bao • Edited on

Save time in debugging as I had to search for the problem one by one in my source code. Showing to people that what you wrote meets the quality of being fit to be in production. Without pulling your hairs out when it fails in production with a higher stake if it fails.

Collapse
alchermd profile image
John Alcher

I started with TDD by the usual reasons: improve code quality, expand my horizons, try out new methodologies etc. But a huge thing for me that I had just realized is that TDD takes a lot of time to implement. And those minutes that I'm using writing tests are minutes I probably would've used to procrastinate if I'm not doing TDD.

TDD keeps me in check. It gives me structure and flow, which is invaluable for my productivity.

Collapse
marinafroes profile image
Marina Costa

In my case, as I am new to programming, I started to contribute to an existing project and I realized that some of the changes I made were causing side effects and they had no tests to show that my contributions were working as expected and that the existing features continued to function properly. So, I realized that I needed to implement tests to be more confident, reduce the amount of bugs and at the same time have clear documentation of what the existing code should be doing.

Collapse
imforja profile image
João Forja 💭 Author

It's awesome that you've reached that conclusion. Many people don't have those concerns and just go on and break things thinking that it's part of the job. I also believe that we can do better than that :)

Since you said that you're new to programming, I don't know if you're aware of the term legacy code or not, but what you're describing is part of the problems that we face when we work with legacy code. And in my opinion, working with legacy code is one of the hardest challenges of software development because we need to reverse engineer code into a design that allows us to test it. It's an entirely more complex scenario than writing tests from the get-go. So don't feel frustrated if you get to the point where you can consistently write tests for an app that's not yet written, but still find it hard to introduce tests to already existing projects :)

Collapse
marinafroes profile image
Marina Costa

Yes, from what I have seen so far (which is not much), working with legacy code is more challenging but it is also much more likely than writing something from scratch or that already has well-established tests. But it’s good to know that even more experienced programmers also find this difficult, I’ll keep that in mind.

Collapse
patarapolw profile image
Pacharapol Withayasakpunt • Edited on

In a complex enough library, testing with edge cases is just inevitable, and has to be done every time you change the library code. Sometimes there are collaborators, and sometimes you just forgot how it works. This is just needed if you maintain the code long enough.

As you might forgot to test before publish or commit, automated testing, as well as Git hooks and CI are needed.

As UI / JavaScript is getting more complex. This is inevitable for the frontend too.

Pure functions (purely functional programming) and dependency inversions are also moving in this direction, testing for cases you cannot guess they will occur, in production; where you have to be responsible for everything, even wild cases.

I might not go as far as religiously test from day one with null hypothesis; but I don't really think it is that bad either.

Collapse
enzechua profile image
enzechua

Hi, I am new to Software Engineering and was thinking of using TDD in my projects. However, I found it quite mind blocking to start on it. Most of the tutorials just talk about how to do TDD, Red Green Refactor, repeat. But what I am looking for is more towards the thought process when doing TDD.

Some questions I have.

  1. When it comes to TDD, do I think of writing tests for a feature, or do I design the functions and write tests for it? The difference is that for the first one, my functions are derived from my test which could be further broken down into smaller units. Whereas the second one is more towards knowing what functions(no implementation yet) I will need for the feature and creating a test for each function.

  2. What does it mean that TDD is more of a design than testing?

Thanks, looking forward to your answers!

Collapse
imforja profile image
João Forja 💭 Author

Hi, those are some great questions!

Depending on who you ask, you'll get different answers, as different people find different ways to integrate the general ideas of TDD into their workflow.

Therefore, in my opinion, the right mindset when learning TDD is not to try to find the one right way to do it, as if we were a computer executing a program, but to specialize the general ideas to our needs and context.

In other words, use TDD in the way that you find it makes you more productive, given the environment you're programming in. And by productive I mean reduces the number of bugs you produce and makes you go faster in the mid to long term.

With the above said, I'll answer the questions given my understanding and what has been working for me.

"What I am looking for is more towards the thought process when doing TDD."

In essence, I see TDD as a technique to bring clarity. Human language is fuzzy by nature. So when we're discussing requirements, there's a lot of room for misinterpretations. TDD gets mostly rid of misunderstandings by introducing verifiable hypotheses (tests). By turning requirements into tests, usually through examples of how something will work, we have a way of knowing if we're confirming to what's being asked of us.

NOTE: Depending on where you use the logic above (code level or client level), people will give them different names. When we're using tests to clarify what a client wants, people tend to call it BDD or ATDD. When using them to clarify what a code module should do, people call it TDD.

What's first on my mind when I'm using TDD is what do I need to clarify about this module that I'm building. I mainly look at the inputs of a module (parameters, dependencies, etc.), fiddle with them, and see if I can find a combination that I don't know what output it should produce. If that happens, I'll discuss with the team and look at the system's goals as a whole to determine what that module should do.

Eventually, I'll get to a point where the tests leave no room for doubts about what the module should do, and all that's left is to implement what's missing to make the tests pass.

"When it comes to TDD, do I think of writing tests for a feature or do I design the functions and write tests for it? The difference is that for the first one, my functions are derived from my test which could be further broken down into smaller units. Whereas the second one is more towards knowing what functions(no implementation yet) I will need for the feature and creating a test for each function."

We start at the client level (ATDD/BDD). At that level, we clarify what the client wants the feature to do through tests. These are usually called acceptance level tests since we're testing what we'll be building against what the client intends the feature to do.

After we have acceptance tests, we start to design what modules our system needs to have and what they should do to fulfill the acceptance tests.

NOTE: A module is not necessarily one function. Could be many, could be one, could be one with many inside. It can not even be a function. It could be a class, for example. It's better to think of a module as a code-unit with a job.

After we have an idea of the system's modules, we start to TDD against the interfaces of the module we came up with through the acceptance tests.

So to answer the question more directly, we start with acceptance tests. From there, we come up with a general idea of the structure we'll need to make the tests pass. By structure, I mean modules and how they interact. At this point, you'll probably already have an idea for the signature of the modules' interfaces. Now you write tests against the modules' interfaces to verify they work as expected.

"What does it mean that TDD is more of a design than testing?"

When you're using TDD, you're forced to be a client of your own modules. If the modules are hard to use, you'll start to experience pain when writing tests. That pain will force you to take a step back and look at the program's design as a whole and change it so that testing becomes less painful.

The general idea is that painful testing equates to a failure in some characteristics of what good design should be. For example, modules that are hard to test tend to have interfaces that are complicated to use. That complication will then propagate to other modules that use the complicated module. This gives rise to complicated interactions all over the program. And in a good design, it should be simple to understand how the parts interact.

NOTE: Although TDD can help with design, please don't get the idea that by using it, design considerations take care of themselves. We're the ones in the driver's seat, not TDD. If we don't study design, we won't know how to interpret the queues given by TDD or even if we should listen o them given the project's broader goals.

I hope this helps, and let me know if you have more questions :)

Collapse
vdedodev profile image
Vincent Dedo

I was interesting in TDD as it was becoming as buzzword. After learning how it worked, I lost interest as it really wasn't the magic solution some people claimed it was. I barely use it now.