DEV Community

Cover image for Unit Tests: The Greatest Lie We Tell Ourselves?
Varshith V Hegde
Varshith V Hegde Subscriber

Posted on

Unit Tests: The Greatest Lie We Tell Ourselves?

The Setup: A Developer's Hubris

So there I was, Friday night, 11:47 PM. My test suite? Green as grass. Coverage? A beautiful 94.7%. My code? Love it!! Proud of myself...

I had done everything right:

  • Unit tests - Checked
  • Integration tests - Checked
  • Edge cases covered - Checked
  • That weird bug from last sprint? Fixed and tested - Checked
  • My confidence? Through the roof - Checked

I pushed to production, closed my laptop with that satisfying thunk, and packed my bags. This weekend, I was conquering Kaiwara Betta, a modest 4,200-foot trek in Karnataka. Because I'm the kind of person who carries their laptop on mountain treks (don't judge me, we're all broken in our own ways), I figured if anything went wrong, I'd have cell service at the peak.


The Fall: When Reality Hits (Literally)

Saturday morning, 4 AM. I'm at the base of Kaiwara Betta, breathing in that crisp mountain air, feeling like a protagonist in a coming-of-age developer documentary. My phone buzzes.

Teams notification: "Production is down. URGENT."

But hey, I have my laptop! I'll just... checks phone signal... I'll just climb faster to get better reception!

By the time I reached a spot with decent 4G (around 3,000 feet, sweating like a deployment pipeline on Black Friday), I opened my laptop on a rock. Picture this: Developers trekking past me, living their best unplugged lives, while I'm crouched over my MacBook like Gollum with his precious.

The logs were... creative:

NoneType object has no attribute 'execute'
KeyError: 'user_preferences'
Connection timeout: Database not responding
Enter fullscreen mode Exit fullscreen mode

confused nick young meme

My 94.7% coverage didn't cover THIS. My beautiful unit tests didn't catch THAT. And my Friday night confidence? Currently rolling down the mountain like that boulder in Indiana Jones.

Boulder in Indiana Jones

The Uncomfortable Truth: Unit Tests Are Necessary BUT...

Here's the controversial take that'll get me ratio'd on Twitter: Unit tests are absolutely necessary, but they're not a silver bullet. They're more like a bulletproof vest that only covers your torso.

Let me explain before you close this tab in disgust.

What Went Wrong? (A Post-Mortem from 4,200 Feet)

My tests were perfect. My code was perfect. The problem?

  1. Environment Drift: My production environment had a different Python version (3.9 vs 3.11) that handled dictionary merging differently
  2. Race Conditions: My async database connections had a race condition that only appeared under load
  3. Configuration Hell: An environment variable was set differently in prod

None of these were caught by unit tests. Because unit tests test units. Isolated, mocked, perfect little universes where everything behaves exactly as you tell it to.

Real production? That's a lawless wasteland where Murphy's Law rides a motorcycle through your carefully constructed sand castles.


So... Are Unit Tests Useless?

NO. ABSOLUTELY NOT. PUT DOWN THE PITCHFORKS.

Here's the thing: Unit tests are like going to the gym. Just because you can still trip and fall down the stairs doesn't mean leg day was pointless.

Unit tests catch:

  • Logic errors in your functions
  • Edge cases you forgot about at 2 AM
  • Regressions when you refactor
  • That time you accidentally deleted a critical if-statement
  • Your coworker's "quick fix" that broke three other things

What they DON'T catch:

  • The chaos of the real world
  • Systems integration issues
  • Infrastructure problems
  • That one server that's possessed by demons
  • The inevitable heat death of the universe

How to Actually Write Tests That Matter (Lessons from a Rock)

Sitting on that mountain, debugging production code with 12% battery left, I had an epiphany. Here's how to test properly:

1. The Testing Pyramid is Your Friend

Testing phases

Don't just live in the unit test basement. Venture upstairs occasionally.

2. Test the Contract, Not the Implementation

Bad:

def test_user_service_calls_database_exactly_once():
    # This test knows too much about HOW things work
    mock_db.assert_called_once()
Enter fullscreen mode Exit fullscreen mode

Good:

def test_user_service_returns_user_data():
    # This test cares about WHAT happens
    user = service.get_user(123)
    assert user.id == 123
    assert user.email is not None
Enter fullscreen mode Exit fullscreen mode

3. Integration Tests Are Not Optional

def test_the_actual_freaking_database():
    """Yes, spin up a test database. Yes, it's slower. 
       Yes, it would've caught my production bug."""
    db = create_test_database()
    result = service.fetch_user_preferences(user_id=1)
    assert result is not None  # THIS WOULD'VE FAILED
Enter fullscreen mode Exit fullscreen mode

4. Test Your Assumptions

My code assumed the database connection would always exist. LOL.

def test_graceful_degradation_when_everything_is_on_fire():
    with mock.patch('db.connect', side_effect=TimeoutError):
        # Does your app explode or handle it gracefully?
        result = service.get_user(123)
        assert result == None or isinstance(result, DefaultUser)
Enter fullscreen mode Exit fullscreen mode

5. Use Contract Tests for External APIs

def test_third_party_api_still_returns_what_we_expect():
    """Because apparently they can just CHANGE THINGS"""
    response = external_api.get_data()
    assert 'user_id' in response  # Would've caught the API change
    assert isinstance(response['user_id'], int)
Enter fullscreen mode Exit fullscreen mode

The Mountain's Wisdom: A Balanced Approach

As I finally fixed the bug (at 3,800 feet with 4% battery), I realized something:

Testing is like trekking. You need preparation, but you also need to expect the unexpected.

Your testing strategy should include:

  1. Unit Tests (60%): Fast, focused, fearless
  2. Integration Tests (30%): Slower, broader, realistic
  3. E2E Tests (10%): Slowest, widest, most painful but most valuable
  4. Manual Testing: Sometimes you just gotta click around and see what breaks
  5. Monitoring: Because tests can't catch everything, but dashboards can alert you when everything's on fire

The Real Controversial Take

Here it is, the spiciest take that'll get me cancelled in dev circles:

Stop chasing 100% code coverage. Start chasing confidence coverage.

Would you rather have:

  • 100% unit test coverage with no integration tests?
  • 70% coverage with a good mix of unit, integration, and E2E tests?

I'll take option B and sleep better at night (when I'm not debugging on mountains).


Practical Tips for Tests That Actually Help

1. Write Tests That Tell a Story

def test_user_cannot_access_deleted_account():
    """Given: A user with a deleted account
       When: They try to log in
       Then: They should get a meaningful error"""
    # Your test here
Enter fullscreen mode Exit fullscreen mode

2. Test the Sad Paths

Everyone tests the happy path. Be different. Be sad.

def test_what_happens_when_literally_everything_fails():
    # Database down? Check.
    # Cache unavailable? Check.  
    # API returning 500s? Check.
    # Does your app at least log the error? Let's find out!
Enter fullscreen mode Exit fullscreen mode

3. Use Realistic Test Data

# Bad
test_email = "test@test.com"

# Good  
test_email = "user+test@company.co.in"  # Tests plus signs, subdomains, etc.
Enter fullscreen mode Exit fullscreen mode

4. CI/CD is Not Optional

If your tests only run on your machine, they're basically vibes.

5. Fail Fast, Learn Faster

Don't spend 3 hours debugging a test. If it's that complex, maybe your code is too complex.


The Epilogue: Making Peace with Imperfection

I eventually fixed the bug from the mountain (thank you, sketchy 4G). The trek was beautiful, even if I spent half of it debugging. And I learned something important:
Top view

Perfect is the enemy of good, but good tests are better than perfect tests you never write.

Unit tests are necessary. They're not sufficient. They're one tool in your toolbox, not the entire toolbox. They'll catch your typos, your logic errors, your "oh god what was I thinking" moments. But they won't catch everything.

And that's okay.

Because at the end of the day, development is about:

  • Writing code that works (mostly)
  • Testing what you can test
  • Monitoring what you can't test
  • Being ready to hotfix from a mountain at 4,200 feet with 2% battery
  • Learning from every production fire

TL;DR (Because Nobody Reads Long Posts Anymore)

Write unit tests. Write integration tests. Write E2E tests. Don't obsess over coverage percentages. Test the sad paths. Monitor production. Maybe don't deploy on Friday nights before a trek. Definitely carry a power bank.

Unit tests are like airbags. Absolutely necessary, but you still need to drive carefully.

Now if you'll excuse me, I have a meeting with my DevOps team to discuss "why deploying on Friday nights is a crime against humanity."


Have your own testing horror stories? Drop them in the comments. I need to feel less alone in my suffering.

I made it to the peak. The view was worth it. The production crash? Not so much.

Me at Top

Top comments (28)

Collapse
 
pascal_cescato_692b7a8a20 profile image
Pascal CESCATO

why deploying on Friday nights is a crime against humanity

I've always refused deployments starting Thursday afternoon, because if a serious bug comes up, rolling back to a stable state would require dedicating a part of the weekend to it.

It's better to wait until Monday to start the week calmly and have time to fix things without unnecessary pressure.

Collapse
 
xwero profile image
david duymelinck

So there I was, Friday night, 11:47 PM.

This is your first problem. What are you doing so late developing?

I pushed to production, closed my laptop with that satisfying thunk, and packed my bags.

That is your second problem. A push to production should be followed by at least a few hours of monitoring.
Going to production at night is a bad idea because the chance people are hitting the changed/new code is going to be smaller. Early releases are better for monitoring.

Saturday morning, 4 AM.

Sleep is overrated?

While the message in the post is good. The storytelling feels forced. Who is dragging a computer with them on a mountain hike? The main goal of a hike is to get away from the screens you seeing every day.

Collapse
 
vulinh64 profile image
Linh Nguyen

It looks like a story crafted (or assisted) by A.I

Collapse
 
williamwizmax profile image
wizmax8

Sharp points, I like it

Collapse
 
varshithvhegde profile image
Varshith V Hegde

Yeahh I have exaggerated the storyy may be a little just to make it interesting but yeahh , I work on different shift and trek was unplanned by my friends.
Thanks for the comments though !!!!!

Collapse
 
xwero profile image
david duymelinck

My developer pattern detection alarm went off reading the story. As a hiker and bike traveler myself I detected some things that felt off to me.
Of course if you are still young you are a little more resilient and you are taking more riskily decisions. But that is how you learn.

Thread Thread
 
varshithvhegde profile image
Varshith V Hegde

Actually this a story like from Jan 2025 I wanted to share this , and I was still junior dev and there were rumors of layoffs didn't want to take any risk

Collapse
 
coderallan profile image
Allan Simonsen

Love that you put the TL;DR last. That made me laugh! How many mindless vibe-coders missed that...
But good points about unit testing.
Serious mistake to forget to never put anything into production on a Friday. NEVER!

Collapse
 
nejckorasa profile image
Nejc Korasa

Great article. I couldn't agree more, especially with your point that integration tests are "not optional."

Your argument that unit tests fail to catch real-world issues like environment drift and integration failures is especially true for microservices. In that world, focusing on implementation-detail unit tests can be actively harmful, as it makes refactoring harder without giving any real confidence.

I recently wrote an article that builds on this idea, discussing how to test a microservice as an isolated component "from the edges" and why contract testing is so crucial. It's a very similar philosophy to what you're advocating for.

You can find it here if you're interested: nejckorasa.github.io/posts/microse...

Cheers!

Collapse
 
varshithvhegde profile image
Varshith V Hegde

Greatt!!! Thanks...
I would really love to read the article...

Collapse
 
leeca profile image
Lee Carver

If anyone is still trying to sell the idea that coverage is import, they need to read this paper from the University of Waterloo:

Coverage Is Not Strongly Correlated with Test Suite Effectiveness
cs.ubc.ca/~rtholmes/papers/icse_20...

Testing the fraction of the code that actually matters is way more valuable then testing the rendering behavior of a UX where the spec will change tomorrow.

Collapse
 
varshithvhegde profile image
Varshith V Hegde

Exactly!! Sending this to my manager

Collapse
 
devsa profile image
Sanjeev Sarkar

Was there any mock production server to deploy before the production deployment on Fridays to prevent such cases? Once the team is set for pre prod setup to run smoothly then after a day of test we can go to prod with confidence. Yes, that's it, that's my take on this report.

Collapse
 
richardevcom profile image
richardevcom • Edited

Whenever I hear from devs about importance of using unit tests, I just nod, knowing that I didn't write them then and won't write them now. Devs write unit tests only after they've f*d up and it's too late. 😅

Collapse
 
varshithvhegde profile image
Varshith V Hegde

This is soo true 😂💯. I write test cases bcz my non technical manager is obsessed with coverage

Collapse
 
joeaxcorp1 profile image
Joe Agster

I remember back in my first CS class, the professor emphasized defensive programming. Don't assume perfect inputs, she would say, and she backed it up with chaotic test inputs for all our coding assignments. Years later I get into the industry and suddenly it's code fast/ship fast and unit tests later with weak edge case coverage. But that defensive mindset stuck with me. If I'm reading a table row from the database, I assumed it got deleted before I update it. Every time I hear "that should never happen in production" I assume it will happen. Unit tests won't catch everything but you should have good enough breath and depth coverage to be able to sleep at night.

Collapse
 
vpospichal profile image
Vlastimil Pospíchal

UPDATE is an atomic operation.

Collapse
 
sylwia-lask profile image
Sylwia Laskowska

Exactly! That’s why I’m really glad that my product is released only once every few weeks and gets tested by the QA team beforehand. I’m a frontend developer, and I remember having to explain in one of my previous companies why it’s important to have someone in that role - sure, we have unit tests, automation, and all that, but an automated test won’t tell you “something isn’t working in the test environment” or “this feels unintuitive.”

Collapse
 
mike_hasarms profile image
Mike Healy

I think deploying more frequently, not less is a better solution.

When it's less frequent the changes are bigger, there's more surface area for problems and the process itself might be more manual and complicated, because it's only done every few weeks.

If you can deploy small changes often you'll have a much smaller window to narrow down targets, and it's a forcing function to incentive good rollback and deployment procedures so the whole thing is less risky and less scary.

Collapse
 
sylwia-lask profile image
Sylwia Laskowska

You're absolutely right - in general, deploying more frequently is the safer approach. In my case, though, it's not really feasible because the frontend is hosted separately in multiple locations, so coordinating frequent releases becomes very difficult.

Collapse
 
varshithvhegde profile image
Varshith V Hegde

Yeahh!!

Collapse
 
htho profile image
Hauke T. • Edited

On Input data: From time to time I unit-test functions that operate on x/y-Coordinates.

I once wrote a test that completed fine. In Prod we found that somehow the x-coordinate is used for all operations, and y is ignored. At some point I accidentally copied x to y.

Ever since, I use different values for x/y for all operations and I use prime-numbers. This way errors accumulate faster.