Live data from Hacker News

The Myth of Code Coverage

preslav.me

71–80 of 115 posts

Re: The Myth of Code Coverage

#71

Just because code is touched during the test process doesn’t necessarily mean it has been tested. Coverage is more useful for finding chunks of code that aren’t exercised at all by tests. Branches that never get hit that probably deserve extra scrutiny. Coverage is an interesting heuristic but 100% code coverage is not 100% bug free code. Likewise, there’s stuff that’s just not worth it to wrap in a test. The effort…

When “hold people accountable to metrics” is not just your company’s management philosophy but the gospel of its entire surrounding culture, this is not an argument that gets anywhere. It is always better to just cover the damn lines than make your Director explain to the VP why he has worse coverage numbers than a sister org. The same way you should make an expected “no such object” result a 200 status code, so that…

Note that you're now discussing corporate politics, not just creating software, even though creating software often has to deal with corporate politics.

My point is rzimmerman never contextualized this as a "how to navigate the workspace" argument, they are arguing it from a pure technical perspective.

Re: The Myth of Code Coverage

#72

I'm a strident adherent to having 100% code coverage, but this mostly works because my history in infrastructure and a desire to sleep well. The key idea that I have found that if you can't synthetically get your process into a specific state, then life is going to be hard. The problem with a lot of people is that they view testing as a burden rather than a criticism of the code they are testing. If you can't test yo…

You must be trolling.

I don't think so. I once took on the challenge of writing as many unit tests as possible for a project at work (the project did not have unit tests - but was well covered with other types of tests).

The two key takeaways I got from the effort:

First, I had no idea how coupled my code was until I tried writing many unit tests for it. If it's hard for you to instantiate your class without involving N other libraries/objects, your code is very coupled. No one in my team would have looked at the code and said "It's highly coupled". The real proof was "Can you test this in isolation?" If not, you're strongly coupled.

I had to redesign a lot of bits to succeed, and as another commenter pointed out, in my attempts to do so my code really was a lot better. I discovered good principles of design in doing it.

The second thing I learned, which may appear to disagree with the first: There are always easy ways to write code to be unit testable. Most of those easy ways are bad and reduce the "quality" of your code. Forcing yourself to not redesign just "for the sake of writing tests", while still ensuring you have 100% code coverage, will really force you to think heavily about your code, architecture, failure points, etc.

So a 100% code coverage really doesn't tell you if you have good code. But less than 100% does indicate potential problems.

(I personally did not go for 100%, FYI).

Re: The Myth of Code Coverage

#73
Hmm... in my experience, stable code is more likely to be correct than code that changes often. In fact, I would consider this obvious, given that there's a non-zero chance to introduce a bug with every change.

Re: The Myth of Code Coverage

#74
I think code coverage (particularly condition coverage) is mostly useful for one purpose:

You need to get it close to 100% before you can use mutation testing.

Without mutation testing you really shouldn't be particularly confident that your tests are testing much of anything useful.

Re: The Myth of Code Coverage

#75

Earlier quoted context omitted.

I prefer to test everything. The parts you don't test are the parts that break. For example, here's a case where I incremented the wrong metric: https://github.com/jrockway/alertmanager-status/commit/fccae... I noticed the bug when I went to look at a dashboard with that metric on it, and noticed it had the wrong name. If go's Prometheus library had an easy way to run "metric.CurrentValue()", I would have tested it..…

> If go's Prometheus library had an easy way to run "metric.CurrentValue()"... A small tip for the future: https://pkg.go.dev/github.com/prometheus/client_golang/prome... .

Thanks! I've seen that before, but didn't realize that ToFloat64() was what I was looking for.

Re: The Myth of Code Coverage

#76
post #43

I'm a strident adherent to having 100% code coverage, but this mostly works because my history in infrastructure and a desire to sleep well. The key idea that I have found that if you can't synthetically get your process into a specific state, then life is going to be hard. The problem with a lot of people is that they view testing as a burden rather than a criticism of the code they are testing. If you can't test yo…

I wrote a UDF for MySQL. It had just shy of 100% coverage. It used a malloc. I could not figure out how to trigger a malloc failure inside of a imported library running on a MySQL instance. Do I not check for malloc failure (in order to get 100%)? Or do I develop some sort of instrumented version of MySQL which lets me test malloc failure? That seemed far more overkill than warranted for the small project I worked on…

> Could not figure out how to trigger a malloc failure inside of a imported library

Glibc has (or at least has had) special (unsupported) instrumentation that lets you substitute out the libc malloc at runtime with another function.

You can use this to shim in a version that fails after a specific number of executions in order to inject malloc faults.

E.g. https://github.com/xiph/opus/blob/master/tests/test_opus_api...

Re: The Myth of Code Coverage

#77

Just because code is touched during the test process doesn’t necessarily mean it has been tested. Coverage is more useful for finding chunks of code that aren’t exercised at all by tests. Branches that never get hit that probably deserve extra scrutiny. Coverage is an interesting heuristic but 100% code coverage is not 100% bug free code. Likewise, there’s stuff that’s just not worth it to wrap in a test. The effort…

And that’s why you shouldn’t chase after code coverage percentages.

While there are uncovered lines, you have a TODO list that has been generated by the machine, telling you what needs more attention. If you go to that section intending to test it well, you’ll get a lot of lines of test code for a modest increase in coverage. If you’re just trying to boost coverage, you’ll check off the todo without accomplishing much.

The only place these two metrics line up at all is when you can keep the number of conditions in each method close to one, and the functions pure. Otherwise you have a bunch of little functions that accumulate state and you’ve just moved the combinatorics where it’s even harder to reason about.

I think the most you can say with any certainty is that coverage percentage should not go down when adding code. But staying flat doesn’t prevent things from getting worse.

Re: The Myth of Code Coverage

#78
All these discussions in the comments about coverage percentages would be so much more insightful if everyone mentioned the kind of code they work on. Client-side JS? Real-time video processing firmware? A language support library? Automotive middleware? Java for an ATM? An ML library wrapper? Personal website backend in Rust? Android game engine? Cosmic particle simulation?

I don't even care about the implied "what could go wrong" but about the size of the input spaces and the complexity of the algorithms behind them. I certainly don't subscribe to the idea that there is a one-size-fits-all testing strategy for all the above, and I see little gain in arguing for or against a certain approach with someone who has entirely different constraints and code architectures to work with.

Require 100% code coverage on an async task library? Sure, go for it. 100% coverage on a (deep-learning-free) algorithm identifying constellations in pictures of the night sky? Do you really want to have to violate every single assumption one by one in your unit tests? How do you even get that input data (and who will create more nonsense data when you improve the algorithm)?

Or in other words: For any reply in this thread you can probably come up with a scenario where it makes sense to operate as described in it. There is some nice perspective but overall I find it a bit unactionable...

Re: The Myth of Code Coverage

#79
post #26

> 1/3 of the code every software project is irrelevant, buggy, overly complicated, or simply sucks. It has a reason to be where it is, but chances are, one year down the road, it will become a liability. Being dogmatic about tests and covering every line will only make it more difficult to get rid of it. If you have actually tested every line of code with your test suite (not the same as "covering" every line), then…

> Uncovered code is actually a much more useful metric, IMO. You can cover code without testing it, but there is no way you could have tested uncovered code.

Hurrah, finally I see someone with the same viewpoint I've pushed for some time, though for a different reason.

For me, 80% covered isn't the same thing as 20% uncovered. The difference is in the code that has been examined and decided tests for one reason or another aren't worth writing. Could be untestable (elsewhere in these comments is a malloc example), too difficult to reasonably test (API calls abstracted behind a facade; mock the facade in other tests but the facade itself is kept tiny and only tested manually), utilities like python's "__repr__", etc, etc.

Having a way to mark such code as "does not need tests" is the key to making this viewpoint useable though, and I'd also really like it if coverage tools focused on counting uncovered down to 0% rather than covered up to 100%.

Re: The Myth of Code Coverage

#80
Code coverage as a single percent is low value and mostly management fodder.

Code coverage as a percent per module has more value.

Code coverage as highlighted source calling out uncovered code, missed branches, etc is useful during development to help you remember to test stuff.

Post reply on HN