Much pontification exists when it comes to unit testing. Developers get introduced to the idea of TDD with simple code such as testing a method that adds two numbers etc so that they can learn the mechanics, but such motivators obscure away the very real tradeoffs that arise from the fact that tests are also code.
No one writes code that tests the tests. When faced with complicated, hard to test code and behavior they do not undersand fully, developers tend to fall into the self-deception trap so as to keep tests passing instead of investing time in refactoring. Sometimes, tests are written but are never exercised due to unforeseen reasons and no one notices the fact because everyone assumes a green check next to a commit means things are allright! Sometimes, tests do not actually test code, but whether computers can do arithmetic. And, sometimes, the reason your test suite takes a long time to run is because someone thought it was a good idea to run thousands of tests that will either all fail or all pass. Occasionally, your deployment pipeline grinds to a halt because the tests are overly-complicated.
No one writes perfect code. Software development is change management: As external conditions change, code needs to continue to work and that’s why we benefit from having unit tests: First, they help codify our understanding of how the code should behave. Second, they help us feel confident that our changes will not break behavior on which we depend.