One benefit I find with code coverage as a goal is that it highlights unused or unnecessary branches, and can encourage me to think about how my code is organized such that code is more easily accessible.
Put another way, those branches that are rarely touched or hard to get to can become a surprise when they are actually reached in some unique situation.
I guess in this case it isn't really the test itself that is useful but instead the requirement to at least hit each branch can cause me to design and organize the code better.
Code coverage reports are a visualization tool that should be used to quickly get a lay of the land. Code coverage itself should not be the goal; the goal should be careful coverage of behavior that is composed of discrete units amenable to unit tests.
I once made the mistake, as a new lead, of implementing a 100% code coverage policy; I thought that I was expressing the intent of covering all possible behavior. What ended up happening is that the team focused on the metric and lost sight of the goal of unit testing, which is to test behavior. We ended up with people submitting PRs containing unit tests for object getters and setters but not testing that trying to set a null value is properly handled.
That experience taught me is that code coverage is only a tool, and a tool is only useful if used correctly.
To drive the point home: having 100% coverage of all branches means that all written code is tested, but it does not mean that all code that needs to be written has been written. Unit tests should verify behavior, not just execute whatever code has been written.
Yeah I agree, once a metric itself becomes the goal then you end up with some unmaintainable tests just to hit a block. I guess I would prefer to see un-hit blocks eliminated, not obtuse tests written to try and hit them. But like you said, I can see how if test coverage is the only end goal, you could end up with some pretty useless tests just to hit some random block.
Put another way, those branches that are rarely touched or hard to get to can become a surprise when they are actually reached in some unique situation.
I guess in this case it isn't really the test itself that is useful but instead the requirement to at least hit each branch can cause me to design and organize the code better.