Live data from Hacker News

The day I started believing in unit tests

mental-reverb.com

261–269 of 269 posts

Re: The day I started believing in unit tests

#261

Earlier quoted context omitted.

Floating point values are definitely perfectly representable as strings - worst case, you just output the binary as a string - but what you may refer to is that many exact fractions can't be represented exactly as floats and/or that floating-point arithmetic doesn't obey "normal" rules of arithmetic (e.g. addition isn't associative).

Sometimes a framework will do weird things, like convert it to scientific notation, or round it, or add a ton of zeroes. Like BigDecimal in Java and Decimal in C#. They can sneak up on you.

BigDecimals aren't floats, though. They're arbitrary precision rational numbers.

Re: The day I started believing in unit tests

#262
post #43

Earlier quoted context omitted.

> My "unit tests" do hit the database and file system, and I have found and fixed many many problems during testing by doing so. I have found many other problems with those calls in production when I didn't do so. No-one said that integration tests can't also be very valuable. From the little context I get that you write integration tests, and that is fine. They are useful, valuable! But they are not unit-tests. edit…

>No-one said that integration tests can't also be very valuable. Integration tests are a better kind of default test because they bring value under pretty much all circumstances. Nobody said that unit tests cant also be valuable and under just the right circumstances i.e. - complex stateless code behind a stable API. Unit tests shine in that environment - theyre not impeded by their crippling lack of realism because…

> Integration tests are a better kind of default test because they bring value under pretty much all circumstances.

I respectfully disagree. Not with the last part, that is true: they do bring value under pretty much all circumstances. But the first. Because integration tests come with (extremely) high costs.

They are expensive to run. They are much harder (costlier) to write. They are even harder (costlier) to maintain. The common pushback against tests -but they slow down our team a lot- applies to integration tests much more than to unit tests - factors more. And so on.

As with everything software-engineering, choosing what tests to write is a tradeoff. And taking all into consideration, e2e or integration tests are often not worth their investment¹. The testing pyramid fixes this, because testing always (well- it depends) is worth the investment. But when you skew the testing pyramid, or worse, make it an testing-ice-cream-cone, that ROI can and will often quickly become negative.

¹Edit: I meant to say that many of these e2e tests are not worth their investment. Testing edge-cases for example: if you need man-hours to write a regression test e2e style and then man-weeks to maintain and run that over coming years, it's often better ROI to just let that regression re-appear and have customers report it. Whereas a unit-test that captures this edge-case costs maybe an hour to write, milliseconds to run and hardly any time to maintain.

Re: The day I started believing in unit tests

#263
post #262

Earlier quoted context omitted.

>No-one said that integration tests can't also be very valuable. Integration tests are a better kind of default test because they bring value under pretty much all circumstances. Nobody said that unit tests cant also be valuable and under just the right circumstances i.e. - complex stateless code behind a stable API. Unit tests shine in that environment - theyre not impeded by their crippling lack of realism because…

> Integration tests are a better kind of default test because they bring value under pretty much all circumstances. I respectfully disagree. Not with the last part, that is true: they do bring value under pretty much all circumstances. But the first. Because integration tests come with (extremely) high costs. They are expensive to run. They are much harder (costlier) to write. They are even harder (costlier) to maint…

>Because integration tests come with (extremely) high costs.

Unit tests usually have lower capex and higher opex. It often takes less time and effort to write a single lower level unit test but that test will require more frequent maintenance as the code around it evolves due to refactoring.

Integration often tests have higher capex because they rely upon a few complex integration points - e.g. to set up a test to talk to a faux message queue takes time. Getting playwright set up takes quite a chunk of up front time. Building an integration with a faux SMTP endpoint takes time. What is different is that these tools are a lot more generic so it's easier to stand on the shoulders of others and they are more reusable and it's easier to leverage past integrations to write future scenarios. E.g. you don't have to write your own playwright somebody already did that and once you have playwright integrated into your framework any web-related steps on future scenarios suddenly become much easier to write.

Whereas with unit tests the reusability of code and fixtures written in previous tests is generally not as high.

You have to also take into account the % of false negatives and false positives.

I find unit tests often raise more false positives because ordinary legitimate refactoring that introduced no bugs is more likely to break them. This reduces the payoff because you will have more ongoing test failures requiring investigation and maintenance work to mitigate this.

I also find that the % of false negatives is lower. This is harder to appreciate because you wouldn't ever expect, for instance, a unit test to catch that somebody tweaked some CSS that broke a screen or broke email compatibility with outlook, but these are still bugs and they are bugs that integration tests at a high level can catch with appropriate tooling but unit tests will never, ever, ever catch.

>But when you skew the testing pyramid, or worse, make it an testing-ice-cream-cone, that ROI can and will often quickly become negative.

The pyramid is an arbitrary shape that assumes a one size fits all approach works for all software. I think it is one of the worst ideas to ever grace the testing community. What was particularly bad was Google's idea that flakiness should be avoided by avoiding writing tests and applying good engineering practices to root out the flakiness. It was an open advertisement that they were being hampered by their own engineering capabilities.

I do agree that this is a cost/benefit calculation and if you shift some variable (e.g. E2E test tooling is super flaky and you've got good, stable abstractions to write your unit tests against, you've got a lot of complex calculations in your code), then that changes the test level payoff matrix, but I find that the costs and benefits work out pretty consistently to favor integration tests these days.

Re: The day I started believing in unit tests

#264
post #30
post #2

I still like one of the defining characteristics of Unit Tests (paraphrasing Michael Feathers from memory): they are fast and cheap to run. Sure, they might not perfectly simulate production like integration tests, but they also don’t take hours burning cash in cloud infrastructure while risking failure from unrelated races dealing with those dependencies. You can use Unit Tests to get to a place where you’re fairly…

From Working Effectively With Legacy Code by Feathers, p. 14[0]: Unit tests run fast. If they don’t run fast, they aren’t unit tests. Other kinds of tests often masquerade as unit tests. A test is not a unit test if: 1. It talks to a database. 2. It communicates across a network. 3. It touches the file system. 4. You have to do special things to your environment (such as editing configuration files) to run it. Tests…

I've dropped my archaic thinking on what constitutes a unit test or an integration test. I now seldom write what most people consider unit tests, in the "one class, one test" sense.

Instead, I classify units of logical coherence and write unit tests for those. I write financial trading systems - not hft, but still latency sensitive. I will test an order through our pipeline as a unit of work. This will necessarily touch multiple classes. So as an example, a test will cover orders which are accepted and then filled, orders which are rejected, and so on.

Many people would classify these as integration tests, and to be fair, I don't really care what you name them. To me these are much more valuable than the traditional "one class, one test" mechanism because it means I am free to refactor the internals of our pipeline as much as I want with very low impact on the test code.

One of the whole points of test code, that I think has been lost, is that it should be there to give you confidence in the correctness of your application under change. Writing "one class, one test" is a bad way to achieve this.

Re: The day I started believing in unit tests

#265
post #248

Earlier quoted context omitted.

Floating point values are definitely perfectly representable as strings - worst case, you just output the binary as a string - but what you may refer to is that many exact fractions can't be represented exactly as floats and/or that floating-point arithmetic doesn't obey "normal" rules of arithmetic (e.g. addition isn't associative).

I'll clarify, as yes it's possible, but not using the built in conversions from strings to floats. That is, the following is not true for all x: Number(String(x)) == x

NaNs also cause problems here, since it is not equal to itself.

Re: The day I started believing in unit tests

#266
post #77

Earlier quoted context omitted.

I bumped into so many corner case and dumb bugs on a recent python project that I'm even more of a unit testing enthusiast than before. Past a certain level of complexity they are definitely a net benefit.

You mentioned Python. I struggle with the weak(er) typing. It is a bottomless well of bugs. Did your unit tests find type issues or (business) logic / state issues?

It was more business logic issues relating to conditional logic which needed to factor in a lot of edge cases. I don't think strong typing would have helped much in this case.

Re: The day I started believing in unit tests

#267
post #262

Earlier quoted context omitted.

> Integration tests are a better kind of default test because they bring value under pretty much all circumstances. I respectfully disagree. Not with the last part, that is true: they do bring value under pretty much all circumstances. But the first. Because integration tests come with (extremely) high costs. They are expensive to run. They are much harder (costlier) to write. They are even harder (costlier) to maint…

>Because integration tests come with (extremely) high costs. Unit tests usually have lower capex and higher opex. It often takes less time and effort to write a single lower level unit test but that test will require more frequent maintenance as the code around it evolves due to refactoring. Integration often tests have higher capex because they rely upon a few complex integration points - e.g. to set up a test to ta…

> single lower level unit test but that test will require more frequent maintenance as the code around it evolves due to refactoring.

"more frequent" is not the same as "high maintenance costs" though.

Unit tests should only change when the unit-under-test (sut) changes. Which, for many units is "never". And for some with high churn, indeed, a lot.

Actual and pure e2e tests should never have to change except when the functionality changes.

But all other integration tests most often change whenever one of the components changes. I've had situations where whenever we changed some relation, or added a required-field in our database, we had to manually change hundreds of integration tests and their helpers. "Adding a required field" then became a chore of days of wading through integration tests¹.

With the unit-tests, only one, extremely simple test changed in that case. With the end-to-end-tests, also, hundreds needed manual changes. But that was because they weren't actual end-to-end tests, and did all sorts of poking around in the database. Worse: that poking-around wasn't abstracted even.

What I'm trying to convey with this example, is that in reality, unit-tests change often if the SUT has a high churn, but that those changes are very local and isolated and simple. Yet, in practice, with integration-tests, the smallest unrelated change to a "unit" has a domino-effect on whole sections of these tests. (And also that in this example, our E2E were badly designed and terribly executed)

¹Edit: one can imagine the pressure of management to just stop testing.

Re: The day I started believing in unit tests

#268
post #96

Earlier quoted context omitted.

Especially true if you get emergent side-effects from non-obvious shared state dependencies in large projects. Nightmares... =)

Yes sir :) And pragmatically - this always happens at some point. Something something about deadlines and need to get this out yesterday.

[deleted]

Re: The day I started believing in unit tests

#269
post #184

Earlier quoted context omitted.

>"this combination of preconditions yield a business logic edge case that wasn't accounted for". Static typing doesn't really help more than dynamic typing in these cases. Depends on the language and the business logic. Types are a way of specifying preconditions and postconditions; a more expressive type system lets you rule out more edge cases by making illegal states unrepresentable. In particular, I'm pretty sure…

It’s also not possible to write a non-trivial program in Rust.

> It’s also not possible to write a non-trivial program in Rust.

You should probably clarify that it’s not possible for you to write non-trivial programs in Rust.

And that’s okay! No one comes into this world knowing how to do any thing, but we can all learn if we choose to.

If you have a particular sticking point, I’d be glad to give advice.

Post reply on HN