3 ms·
This is part of the difference between the classicist and the mocking styles of testing. The argument for the latter goes that if (big if!) your collaborators a
by Tainnor 6y ago
This is part of the difference between the classicist and the mocking styles of testing. The argument for the latter goes that if (big if!) your collaborators are well designed and have good interfaces, then asserting that something is called is a meaningful assertion in the language of your domain (e.g. "check that an email is sent with exactly this content" is a meaningful assertion, even when you mock the mailer).
I agree that in many cases, APIs are badly designed and expose implementation details that you shouldn't couple your tests to, but many still do that. Mockist proponents would claim that this is not the right way to do it (for example, they argue you should wrap bad APIs in better ones and write your assertions against the latter, see also "don't mock what you don't own").
I can understand the point of view that even then you wouldn't like these kinds of tests, but for me they do provide some assurance that "the right stuff happens" (of course, you need integration tests to test the wiring etc.) at a still lower cost than "you have to extremely strictly separate pure from impure code" (which has other benefits, yes, but is also really hard to enforce especially in a team).
- UK-Al05 6y agoI mean I am a functional programmer so i naturally make code pure until the edges anyway. In a functional language I will just replace the edge function call with a fake version. But the problem with mocking, is you couple your objects to one another. You expect certain methods to be called. With certain parameters. What if you want to add in extra layer, or remove a layer, or add a helper. All your tests have now been broken yet the overall behaviour has not changed. You also have issues around mocks not acutally setup to behave like the real object. If your object under test sends a null it may pass but the real thing may fail. Instead create an architectural boundry with a clear public interface. Exercise the interfaces in tests and then check for results on the otherside of the boundary. You might do that with a mock or a fake. You are now free to refactor from the start to finish of that architectural boundry without breaking tests. This is just fancy talk for only testing your public API. But defining those boundries and public interfaces is where the skill is. If your building a web app using hexagonal architecture, I might say drive your primary ports from your tests, mock and fake your secondary ports. If you expect some group of objects to used in multiple places like a library I will test those as if they're a boundry to. For example If I've built money exchange rate module.
- Huggernaut 6y ago> Instead create an architectural boundry with a clear public interface. Exercise the interfaces in tests and then check for results on the otherside of the boundary. You might do that with a mock or a fake. I think mockist (test-doublist really) TDDers would suggest that the architectural boundaries derived from the design pressure are the "correct" public interfaces you're describing here, they just often happen to coincide with class (or your favourite languages equivalent) boundaries. The pattern often ends up with many one-function role interfaces, orchestrated by collaborators down the dependency tree until you hit value structures, pure functions or external integrations at the edge of the system. In many ways, mockist TDD is a gateway to functional programming.
- UK-Al05 6y agoI agree this is what it is. But a lot people people don't understand it that way. So I get rid of the existing already loaded terms, and just teach it directly without referencing it.
- Tainnor 6y agoHm, yes and no. I do feel that mockist TDD is still heavily emphasising the OOP idea (more in the original sense than in the Java sense) of having separate "collaborators" with their own internal state and side effects that exchange messages (in a way similar to the Actor pattern). A functional approach (at least in a pure functional language) wouldn't necessarily emphasise this sort of interaction pattern between independent components and try to isolate state and side effects much more.
- ptx 6y ago> What if you want to add in extra layer, or remove a layer, or add a helper. You make the extra layer implement the same interface and delegate to the original object. Classes don't depend directly on other implementation classes in this style.
- UK-Al05 6y agoThe extra layer will transform the objects somewhat, so you would have to change what the original object would accept. You have now broken the tests against the original object.