loading...
Cover image for Are unit tests a waste of your time?

Are unit tests a waste of your time?

rad_val_ profile image Valentin Radu Updated on ・3 min read

Unit tests are just a waste of time: you'll always end up spending more effort maintaining them than writing code
someone, at some point in your career, probably

I've heard it dozens of times, in more places than I can remember. Usually frontend devs are more inclined to think so than their backend counterparts, and for a good reason: there's still room for improvement when it comes to frontend unit testing.

I've been lucky enough to start a fair amount of greenfield projects in the past years, both large and small, and looking back, I feel like writing tests had (or would have) always saved time. But, it took me quite a while to truly see the benefits and change my headfirst habits. I imagine there are other skeptics out there, so, here's what I'd tell my younger self.

  • There's no such thing as "not enough time to write unit tests"
  • You'll never write them "later", you need discipline to do so, and if you don't have it as you code, you'll certainly won't have it as the project gets larger
  • No project is small enough not to unit test
  • In retrospect, for anything more than 5 lines of code, it will always be more expensive to manually test your code
  • You'll find that as you make your code testable, you also increase its intrinsic quality, to the point that testable code will sort of become a synonym for clean code.
  • Remember when you had that one awkward bug and it took days only to reproduce it? Software systems are complex and require the perfect alignment of many moving parts, some of them not under your control. If something goes wrong only in a rare scenario when a third party (like, Google or Stripe API) returns something unexpected, it becomes almost impossible to debug. Because unit tests allow you to control the state of your application in great detail, reproducing such an issue becomes much easier.

Unit testing is not some extra or nice-to-have thing, it's a mandatory step in the software development process. As an engineering team, you can decide to skip it, but it will bite you back and waste many good hours.

In the end, what are your other options?

  1. You don't test at all. Needless to say, the resulting product will be an unusable crap. You'll have to fix it solely based on user feedback, and this will lead to unfathomable costs. You will fail, your team will fail, you'll have to find another job.

  2. You let others do the manual testing. Soon enough you'll discover that issues that you'd have otherwise caught fairly quickly, will keep bouncing back and forward between you and testers.
    This is an improvement, at least your users don't do the testing anymore, still, the constant ping-pong is time-consuming, plus with each iteration you'll have to restart the whole process. More $$ down the drain.

  3. You manually test your code throughout before passing it down the line. This is probably the most common scenario out there, or at least I hope it is.
    You'd be inclined to think it's enough, as a developer, you'd know where to look and fix things quickly. However, considering how complex state can become and how tendinous triggering all the required conditions to replicate an issue is: you'll end up spending a serious amount of time, not coding per se, but clicking and following user paths.

Back to out initial statement, it's true, either way you'd take it, unit testing is time-consuming, especially if you have a poorly designed codebase. But, you'll have to test your code anyhow, the only choice is, waste your time doing it manually or invest some effort in learning how to properly write unit tests, and profit later.

Posted on by:

rad_val_ profile

Valentin Radu

@rad_val_

Tech consultant, software engineer and aspiring mathematician. Founder at techpiIot.dev

Discussion

pic
Editor guide
 

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.

 

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.

 

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.

 

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.

 

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.

 

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.

 

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.

 

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

 

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)

 

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

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.

 

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!

 

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).

 

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.

 

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...

 

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

 

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.

 

Unit testing <3

Unit-testing everything? -.-*

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