3 ms·
My view on unit testing is if there are no dependencies, there is no real reason not to write tests for all behaviours. While you may have a wonderful integrati
by osigurdson 3y ago
My view on unit testing is if there are no dependencies, there is no real reason not to write tests for all behaviours. While you may have a wonderful integration testing suite, it is still great to know that building blocks work as intended.
The problems arise with dependencies as now you need to decide to mock them or use concrete implementations. The concrete implementation might be hard to set up , slow to run in a test - or both. Using a mock, on the other hand, is essentially an alternate implementation. So now your code has the real implementation + one implementation per test (in the limit) which is plainly absurd.
My current thinking (after writing a lot of mocks) is to try to shape code so that more of it can be tested without hard to setup dependencies. When this can't be done, think hard about the right approach. Try to put yourself in the shoes of a future maintainer. For example, instead of creating a bespoke mock for just your particular test, consider creating a common test utility that mocks a commonly used dependency in accordance with common testing patterns. This is just one example. Annoyingly, a lot of creativity is required once dependencies of this nature are involved which is why it is great to shape code to avoid it where possible.
- patrick451 3y agoThe tests in the codebase I currently work in is a mocking nightmare. It feels like somebody learned about c++ interface classes and gmock for the first time when the codebase was first being put together and went completely bananas. There are almost no classes which don't inherit from a pure interface. Two of the main drawbacks to this are - Classes which have only a single implementation inherit from an interface just so they can be mocked. We often only need polymorphism for testing, but not at runtime. This not only makes the code slower (minor concern, often) but more importantly much more difficult to follow. - The tests rely heavily on implementation details. The typically assertion is NOT on the input/output behavior of some method. Rather, it's on asserting that various mocked dependencies got called at certain times within the method under test. This heavily couples the tests to the implementation and makes refactoring a pain. - We have no tests which tests multiple classes together that aren't at the scale of of system wide, end-to-end tests. So when we DI class Bar into class Foo, we use a mock and don't actually test that Bar and Foo work well together. Personally, I think the code base would be in much better shape if we completely banned gmock.
- troupo 3y ago> it is still great to know that building blocks work as intended. The only way to know that the building blocks work as intended is through integration testing. The multiple "all unit tests passed, no integration tests" memes show that really well.
- osigurdson 3y agoI suspect you are not finding many bugs in common built-in libraries via integration tests however. The same concept can extend to code that our own teams write.
- tomnipotent 3y ago> finding many bugs in common built-in libraries via integration tests I'm not convinced they're finding them with unit tests, either. No shortage of public repos with great test coverage that still have pages of Pull Requests and Issues dealing with bugs. In many of those instances, you could argue that it's the act of integration testing (the team trying to use the project) that ends up catching the issues.
- osigurdson 3y agoI think for low level functionality, unit tests and integration tests are the same thing. As an absurd example, consider a function that’s adds two numbers together producing a result.
- adrianN 3y agoIf your whole Program is one unit there is no integration between units that warrants an integration test.
- michaelteter 3y ago> try to shape code so that more of it can be tested without hard to setup dependencies Yes! In general, functional core + imperative shell goes a long way toward this goal. With that approach should also be minimal coupling and complex structured types outside with simple standard types passed to core functions. These things make unit testing so much easier and faster (dev time and test run time).
- deleted 3y ago[deleted]