DEV Community

Cover image for Are unit tests a waste of your time?
Valentin Radu
Valentin Radu

Posted on • Updated on • Originally published at techpilot.dev

Are unit tests a waste of your time?

First published on techpilot.dev

TL;DR In a perfect world, we would have automated tests for everything, but in reality, there's always a compromise between code quality and costs.

I've been lucky enough to start a fair amount of greenfield projects in the past years. Looking back, I feel like writing tests had always saved time in the long run but were rather cumbersome when working on UI and smaller projects.

It goes without saying that for some apps (i.e. life-critical, mission-critical) an unit test coverage of 80%+ should be required. But, despite what most of the literature says, in some cases, unit tests are actually slowing us down. In which case, how do we decide if it's worth the trouble and keep a balance between quality and costs?

Here are a couple of things to consider.

  • Debugging process Remember when you had that one awkward bug, and it took days only to reliably reproduce it? Unit tests allow you to control the state of your components in great detail, making it easier to find and fix such issues.
  • Reusability and life span If you wrapped up some code in a reusable library, chances are you'll improve it frequently in the beginning. Unit tests can help you ensure no breaking changes sneak through each iteration.
  • Complexity When you can't reason about a system in the wild, it helps to frame it in isolation. Unit tests do just that.
  • Third-party dependencies Software requires the perfect alignment of many moving parts, some of them not under your control. When third parties act unexpectedly, it becomes almost impossible to debug your system. Mocking dependencies in your unit tests solves this.
  • Costs If you think you don't have enough time to write unit tests it automatically means you're ready to trade quality for it. This might work in some scenarios (e. g. proof of concepts), but you must be aware of the implications.
  • Team size You'll find that as you make your code testable, you also increase its intrinsic quality. Also, unit tests are the closest thing to written specs, and while you can manage without any formal requirements while working on a part-time project by yourself, larger, heterogeneous teams would have a hard time doing so.

Unit testing is time-consuming, especially if you have a poorly designed codebase. You can decide to skip it, and it might be the right thing to do, depending on the context, but you'll have to trade code quality in return.

Discussion (18)

Collapse
rossdrew profile image
Ross • Edited

If things are hard to reason about in isolation, they are impossible to reason about as a system. Unit tests tell us at an atomic level what works and under what assumptions it works given a large number of fast running assumptions. System tests tell us that something doesn't work, somewhere based off a very narrow, slow running set of assumptions.
Anyone still arguing against unit tests is either a complete genius programmer or a dangerous developer. I've met very few genius level developers in my time...and they all prefer unit tests.

Collapse
srleyva profile image
Stephen Leyva (He/Him) • Edited

I’ve found it’s two fold: shorten feed back loop and it encourages good design practice to make your code testable. I’ve also found, however, they aren’t extremely valuable when your code has quite a few external dependencies (api client wrappers, AWS, GCP). Testing for regressions and business logic makes sense, but not everything needs to be unit-tested and shouldn't be. There should be deliberate value from that test resulting in saved developer time. 100% test coverage is a constraint on time, tightly couples the implementation to the test and usually results in tests that don’t tell you anything valuable. Integration tests are more intensive and do require a bit of time to run, but provide a bit more feedback. Like everything in tech, I’ve started to view them as tools. Right one for the right case to ensure I’m confident in the code I’m shipping.

Collapse
rad_val_ profile image
Valentin Radu Author • Edited

I usually aim for 80% coverage from unit tests alone. 100% is definitely not achievable without wasting time for real. Problem with integration tests is that, before you know it, people start using it to test business logic, just because in a way, is more convenient (not easier or less verbose, just more convenient: no mocks/stubs, flows that translate 100% to user flows, tools that are more familiar, blackboxed etc)
In any case, I liked your comment because you're pointing out that in the end all of them are tools and each project is different.

Collapse
xavierbrinonecs profile image
Xavier Brinon

And yet so many (I mean all of them) tutorials articles have 0 test in it. Devs hear one thing but see another. This is a sad state of affairs.

Collapse
rad_val_ profile image
Valentin Radu Author

I think this is an important aspect. It applies to schools as well, at least in my case. You learn how to write code 3+ years, but learn how to test it one semester, and even then, very little hands on practice, only, theory. At the end of it, no wonder the benefits are not clear and you fail to form a healthy habit of testing.

Collapse
florianweissdev profile image
Forian Weiß

Unit Testing actually helps me writing my code. It's like a rubberduck in a certain way. And there is no need for TDD do get this advantage out of it. On top of it there is a lot of code in the backend that is no fun to test manually at all. Therefore I can see why people skip it on the frontend if everything seems to work after the hot reload.
And high maintenance is a myth in most cases.

Collapse
rad_val_ profile image
Valentin Radu Author

Couldn't agree more. Strict TDD is a myth as well, at least the way it's presented by the book: fully writing test cases before everything.
I'm inclined to think nobody does that. Instead, you get a synergy between the test and the code and they sort of drive each other. When the code is immature, the tests are as well. They beautifully grow together.

Collapse
michi profile image
Michael Z

I usually start with integration tests and only break down to the unit level if necessary.

Collapse
rad_val_ profile image
Valentin Radu Author

Integration tests are clearly important. However, unit tests are easier and faster to write. You don't have to reason about other subsystems, you don't have to manage artefacts (e.g. clear the database) and so on. With enough unit tests, you can have less integration tests, testing only what they're suppose to test: the integration (not business logic)

Collapse
michi profile image
Michael Z

I think it depends on the tech stack. Working with frameworks like Laravel or Adonis.js writing integration tests is a joy.

Thread Thread
ryands17 profile image
Ryan Dsouza

Integration test with JS are also easy to write both in frontend and backend. I find writing unit tests a bit more effort as you have to mock a lot of components. My personal opinion is aiming for more of Integration and E2E tests in frontend/backend both. Inspired by this article.

Collapse
adnanhz profile image
Adnan

It took me a really long time to grasp how I should do unit tests. I finally understood them as a mean of testing that I'm writing the correct code, and not testing that the code is returning the correct result. The latter is done by integration tests.

I learned it the hard way: I worked at a startup that had 0 automated tests and saw its products fail on production. Then I started looking for how I should do automated tests. The easiest ones seemed integration tests, as they test that the result of the code is correct, plus I didn't have to worry about this weird thing called Mocking. Then, my integration tests grew bigger and bigger, and started taking more and more minutes to finish running. And they failed randomly.
Today, I am going through a refactoring process and writing unit tests now that I have experienced all this first-hand - and for the last few days, I have already caught a lot of hidden semantic bugs here and there (even typos!). It all really became easier when I understood how mocks work.

To recap:

  1. Unit tests are to assert that I've written the correct code. These run very fast as all potentially slow external dependencies are mocked.
  2. Integration tests are to assert that this correct code, glued together with the external dependencies, is returning the correct outcome to the requester.

That's my opinion!

Collapse
vinceramces profile image
Vince Ramces Oliveros

One issue where people don't write test is "How do you know if a test works in a unit test?" and "I don't get paid to write test". I write tests when the client doesn't want a major breaking changes in the UI before going to the next phase. If 1 functionality gets added/modified, it affects that test cases. I'd simply wait for his/her final decision not to change any feature or test will not be written.

I'd advice for starters that when writing a test, always assume the language specifications will go wrong(even if you're writing typescript, it will always go wrong without type validation first in production).

Collapse
rajdeepc profile image
Rajdeep Chandra

Yes Only if it's a necessity in the functional flow. When there is a too much of a complex DOM structure, I always do the snapshot testing and while using redux, I always make sure to test the action creators and reducers in isolation.

Collapse
mikenikles profile image
Mike Nikles

In my experience, unit tests are crucial for pieces of code that can be tested as an independent unit. I believe as soon as you start to mock all sorts of dependencies in order to write a unit test, you are not testing the truth anymore.

With modern, a lot more reliable testing tools, I've seen huge benefits in writing more end-to-end tests and focus on unit tests only where it makes sense.

A single unit test covers a small area of the code whereas a single end-to-end test covers a larger area of the code and with that, has the potential to catch more bugs.

It takes time to set up a fully automated end-to-end test environment, but I've seen it worthwhile many times over the past few years.

To visualize my thoughts:
Test Pyramids

Diagram source: excalidraw.com/#json=5186401523990...

Collapse
rad_val_ profile image
Valentin Radu Author

Problem with end-to-end tests is that you can't easily test outliners because often you don't control all the ends. Covering all cases is many times hard or even impossible (you want to test what happens with your (micro)service if another service it depends on fails in a certain way)
Also, they are much more expensive.
In the end, both unit, end-to-end and integration testing need to coexist, but in my experience, having a solid unit tested base, can save you lots of trouble/code higher in the pyramid.
Also, you're right, when unit testing, you're not testing the whole system, you're testing the unit, the function or class and assume that if all units work, the system works as well. This assumption can be wrong, that's why integration, end-to-end and manual testing exists parallel.
Ultimately, for me, cost and time are the main reasons I fancy the unit tests first approach, although I agree it's also a matter of team/organization/project/tech stack etc

Collapse
andrewharpin profile image
Andrew Harpin

The challenge with unit testing, is having effective unit testing.

Many groups use it as a box checking exercise. Basically using the code as a design to implement the testing, this results in significant effort for zero benefit.

The challenge is educating developers to implement from the use case perspective over the code perspective.

Collapse
sebbdk profile image
Sebastian Vargr

Unit testing <3

Unit-testing everything? -.-*

Accidentally doing integration testing instead of unit testing . </3