Integration tests and fuzz testing is where it's at. Anyone can add or improve an integration test within an hour and find several bugs that real users would encounter.
Integration tests and fuzz testing is where it's at. Anyone can add or improve an integration test within an hour and find several bugs that real users would encounter.
I have written tens of thousands of unit tests in my career, and by far the most useful ones are the ones that point you to the exact subsystem in which a bug you've just introduced during refactoring/re-implementing lies. Bad unit tests are brittle "change detectors" and make you constantly adjust them for no real reason. I find mocks and interaction-based tests (rather than simple input/output tests) are this way. I flat-out do not use mocks for this very reason. They are intricate, involved, and take huge amounts of time to load context when they fail. Don't write such tests.
If your unit tests are garbage, it's because they were not thought out very much and commit sins.
This is why I prefer integration tests. Most of my projects interact heavily with a database. Making it an integration test unless I mock it out. Which I don't do, because in my experience, doing so tends to hide lots of bugs.
IMO integration tests are great for making sure that your system actually functions with all its parts together. Nothing but an integration test can do that. But unit tests serve a different role: reducing the scope of the system under test so that you can pinpoint exactly what part failed when it does fail. They help you iterate quickly in refactoring, and when something goes wrong in production (or a bug report from the field), they narrow the cone of uncertainty by eliminating from consideration the parts of the system that are working correctly. In my experience, this rapidly cuts down on the amount of wild goose chases one goes on and overall makes a team more efficient. Then once a bug in the wild is found, the system responsible for the failure gets new regression tests and unit tests. Sometimes, I wrote unit tests for units I am suspicious of as I am debugging, and even if those tests don't find a bug, I might keep them.
I worked for 6+ years on V8 and the parts of the system that were the least unit tested seemed to be home to far more production issues than others.
It also helps a lot to keep your unit tests running super fast. Under 1 second is ideal.
As I say, some people will still think of this as integration testing just because there’s a lot of moving parts working together. But often you’re just testing fairly pure code with no external or IO dependencies so I’m happy thinking of these as unit tests still. Where those types of dependencies exist I do still mock though.
It's fascinating, I agree with the rest of your comment wholeheartedly but am a heavy mock user. Much of the code I interact with has complex logic with well-defined and discrete interacting objects. Without mocks, the combinatorial explosion of input/output behavior means tests are extremely difficult to write, or more likely, are written incompletely, without properly enumerating the invariants expected of the interfaces under test.
I'm currently working on a complex system right that's safety-critical but also SOTA, and full of domain experts who are decisively not engineering experts (among other things, engineering expertise is part of what I bring to the table). Well-defined invariants and narrow unit tests that enforce them are a _god-send_, and I'm literally not sure we could function without them: as we're constantly refactoring the system to work in different ways, automated testing of invariants enables a whole universe of experimental changes that would otherwise carry a great risk of introducing bugs into other parts of the system. Our system is far too complex for integration tests to cover a fraction of the completeness that unit testing enables (though we also have simulation/integration tests, of course).
Well, that might be the problem. Testing "for bugs" isn't a terribly helpful activity. Testing for behavior is much more helpful, and unit tests can definitely be applied to that aspect of the system. Of course, it requires you to properly decompose the system into subsystems (modules) with loose coupling that can be individually (or in small sets) loaded into test harnesses for testing.