6 ms·
Pretty good advice, except the preference for interfaces to support mocking. You should avoid mocking when possible in favor of real objects or fakes. Interface
by sdhgaiojfsa 9y ago
Pretty good advice, except the preference for interfaces to support mocking. You should avoid mocking when possible in favor of real objects or fakes. Interfaces are still cool, though.
- spraak 9y agoCan you explain this further, or offer a link that does?
- sdhgaiojfsa 9y agoThe general theory is that, although you're focused on the code under test, if the underlying dependency is not behaving as expected, you want to know that. Mocks create an opportunity for your code's expectations about your dependencies' behavior to diverge from reality. So if at all possible, they should be avoided. There are a few general ways to do this. The best way is to use the real underlying dependency. This should be the default. If the dependency is too slow to use in the test (e.g. it has a long startup time, or is hosted remotely), the next best thing is to use a fake -- a local implementation of the dep that has the same behavior, but operates in memory on ephemeral data. The best libraries will provide these, and if they don't, you might be able to implement one and contribute it back. Even if you have to maintain it yourself, it often takes less code than describing mock behavior. The downside of fakes vs. the real implementation is that their behavior too can diverge. But this is not a net downside compared to mocks. If you can't use a fake, the next best thing after that is a stub. See below. Only if none of these options are viable should you use mocks. (The position I'm describing is the one referred to as "classicist" in this post: https://martinfowler.com/articles/mocksArentStubs.html https://martinfowler.com/articles/mocksArentStubs.html. I'm not by any means its originator.)
- sagichmal 9y agoThis is straight-up bad advice. You don't test the http.Client as part of your business logic, even (especially) if your business logic makes HTTP requests. Instead, you trust that package http is already well-tested, mock out http.Client as an abstract HTTP Request Do-er, and you downscope your tests to your business logic exclusively.
- sdhgaiojfsa 9y agoLots of people have different opinions on this. You're entitled to yours, but I think it's hasty to call it bad advice. I'm not so worried about http.Client behaving incorrectly, but I am worried about the server I'm targeting behaving in a way that's different than my expectations. I'm also worried about getting cookie, post-data, url, etc. formats wrong. Even if they match what I put in my mock, they won't necessarily match what's out there in production. I'm worried that I'll handle download resumption wrong, for example. If I have the capability to bring up the target server locally, that's what I will do. If I can't, but it's straightforward to create a work-alike, I'll bring that up in process. Only in the most dire of circumstances will I use mocks to test my code. Think about it this way. What harm is done by using the real object, under the circumstances I described? Sure, if the dependency code breaks, my tests will too. Is that bad? Is there any point in my tests passing when my code won't actually work in production? OK, so maybe the tests will run slower? This matters in the limit, but often times you can use the production dependency without materially increasing test time. OK but what about the CI system using more resources? That will only be a problem of significant economic import if you're running at a scale like Google or Facebook. My opinion is there are a few main valid objections to my approach. One is an argument about the size of the test code. In some cases, bringing up the production dependency will require more code than a mock. Sometimes a lot more code. My response is that we should make it easier to bring up the production dependency! But if we can't, I can see the case for a mock or a fake. The other counterargument is for when we really are interested in testing the sequence of interactions the code under test has with the production system. In that event, I guess I'm ok with using a mock, but I think most of the time we're more interested in testing state. We can also use things like testvalue injection to test behavior sequences, which is normally a lighter-weight approach, and in some cases can allow you to reproduce paths that you'd otherwise have trouble hitting, even with a mock.
- sifoo 9y agoThis is how I do it as well these days, testing as much of the final product as possible in the normal test run. It's not that I didn't try unit testing and mocking, or don't understand; my experience tells me that it's not a constructive approach. This means that I always test mostly the same code that other people run, which prevents all sorts of mistakes; and I don't waste precious time writing and maintaining mocks. It will creep up on you, but if you keep mocking everything, sooner or later the effort required to change anything will start hurting the project. It's easy to loose track of the goal; which should be preventing as many production errors as possible as effectively as possible, not checking all the boxes of whatever religion.
- dkarl 9y agoAfter reading the Martin Fowler article you linked, your comment makes more sense, but I think the terminological distinctions Fowler makes are never going to be widely adopted. People are going to keep using terms like "mock" and "stub" to broadly refer to any kind of test double, and it's disingenuous to assume otherwise.
- sdhgaiojfsa 9y ago> "People are going to keep using terms ... and it's disingenuous to assume otherwise." I'm sorry, I'm making an argument about how people should write tests, not what terms they'll use to describe it. :) I promise you that there is no disingenuity going on in my use of certain terminology to describe how I think it's best to write tests.