DEV Community

Cover image for “How do you think when writing tests?” – It’s simpler than you may think
⛩ Caio Zullo for ⛩ Essential Developer

Posted on • Originally published at essentialdeveloper.com on

“How do you think when writing tests?” – It’s simpler than you may think

Watch on YouTube

In this episode, we reply to an important question raised by a member of our community.

Question

“I'm trying to understand how you think when writing tests. Do you just try to cover everything that comes to mind or you have some kind of list with priorities? Or you just try to reach max possible coverage?”

Answer

We follow the basics. Our main process is to write the test first, see the test fail (red), then write the minimum amount of code to make the test pass (green), then refactor the code/test *if needed.* By following this process, we end up with pretty high coverage (not always 100% but very close to it). The coverage is not a direct goal. The coverage is more like a positive side effect of following the process.

With that said, it requires discipline and skill to *always* write the test first. You may get stuck in the beginning. We've been doing this for many years, and we still get stuck sometimes! However, the goal is never to be stuck for long. It's wasteful. At the end of the day, shipping is more important than testing first.

When you're stuck, spike some ideas. Go free, without tests (or anything else) in your way. Play with the code (Xcode Playgrounds, Swift REPL, Debugging...) until you're unstuck and found the solution you are happy with. Then think, "now that I know what I want to build, how could I have written the test first?"

When you have the answer, you can discard the spiked solution (commit the spike solution into a separate spike branch), and start fresh, by writing the test first. Next time you have to solve a similar problem, you know how to do it test-first without spiking.

We practice, learn, and get a little bit better every day. The daily practice compounds!

Subscribe now to our Youtube channel and catch free new episodes every week.


We’ve been helping dedicated developers to get from low paying jobs to high tier roles – sometimes in a matter of weeks! To do so, we continuously run and share free market researches on how to improve your skills with Empathy, Integrity, and Economics in mind. If you want to step up in your career, access now our latest research for free.

Top comments (3)

Collapse
 
sandordargo profile image
Sandor Dargo

If you want robust code, according to Uncle Bob, you might want to write the degenerate test cases first. So think about anything "silly" that doesn't respect the contract between your API and the callers, anything that doesn't really make sense. Once you covered those cases, you can move forward with the normal test cases.

Collapse
 
silverhusky profile image
SilverHusky

Seems like the idea of TDD is to implement the minimum to make the tests pass and repeat the process until the whole functionality is implemented.

Collapse
 
kaelscion profile image
kaelscion

Yes, that is true. But the idea behind TDD, at least in my experience, is to get very granular with the tests. Test everything. Some developers (myself included) will write a test for every function/method in every class they create. We champion the concept of "each class should do exactly one thing, and each property/attribute/object within that class must do only one thing to support or enhance that singular function.

Want a class that handles basic math? Good. Then you need a function that handles addition, subtraction, multiplication, division and a test or series of tests that account for expected use cases of each function, and wacky use cases of each function. Write the test, see it fail. Write a function just robust enough to make it pass. No more, no less. This accomplishes a couple things:

  1. The code is readable as each function makes it pretty clear what it's meant to do to support the sole function of it's parent class.

  2. The code is predictable. Expected input is obvious, expected output is obvious, and how it goes about it is obvious.

  3. The code is efficient, simple, and most of all, is verified to accomplish exactly what it sets out to do. No muss, no fuss. No frills or window dressing.

This approach means the code works as expected. If there is a problem or bug, it's easy to trace, just look at the object who's sole job it is to handle what is not being handled. Once that bug is found, write a test for it, see it fail, implement a solution that does just what it needs to pass, merge the new test and bug fix so that all future changes can properly test for this problem.

At first, TDD seems kind of asinine and irritating. But it will help keep you focused on one solution at a time, and give you peace of mind that your code works as expected and if anything happens, tracing and fixing the bug will be stupid easy compared to the copy pasta we all tend to write when we're in a hurry (you should see some of my personal projects...ugh).

While it isn't everyone's cup of tea, if you are like myself and many others and tend to get sidetracked and follow a bug down the rabbit hole for 2 hours before realizing you forgot what you were originally doing, TDD just introduces some structure and peace to the restless, hyper-curious, multithreaded minds we developers tend to have. Especially when we are excited and/or passionate about the project.