Hacker News new | ask | show | jobs
by randomdata 849 days ago
But more often than not, particularly when interfacing with remote services, you don't want 'FakeSentimentAnalysis' to behave exactly like SentimentAnalysis. You want it to do crazy and unexpected things exactly unlike SentimentAnalysis to ensure that you properly handle the failure conditions that shouldn't, but theoretically could, occur when using SentimentAnalysis. You almost don't even need to worry about the cases where SentimentAnalysis is working as expected. It's the failure states that are of top concern.
1 comments

As we are talking about "code under your control", I don't see a conflict there: you seem to be assuming that I was suggesting the Fake should only implement the happy path, but on the contrary, it's quite easy to have the fake exhibit failing behaviour, but it might be harder to have the same test work against both the fake and real implementation in that case (eg simulating a network connection issue in a fake is easier than with a real implementation).

I generally do that by having a test that works both against a fake and against the actual implementation, a bunch of others that only use fakes, and a few system/e2e tests covering the whole thing.

With not much effort, you get increased trust in the code you write.

But most notably, it makes you write testable code which I think is most maintainable and most readable code to write.

> you seem to be assuming that I was suggesting the Fake should only implement the happy path

There is no such assumption. The assumption is that if you rely on a fake to service all your testing needs it cannot be tested against the real implementation as, in many cases, you do not want it to work the same way.

I don't know if a network connection failure is the greatest example, but let's run with it. Why bother adding simulated network failure into your fake, which, due to the problems you point out, won't be touched by your double-duty test suite anyway, when you can just create an additional mock that does nothing but simulate network failure? Why add needless complexity to the fake? You haven't gained a testability advantage.

That's not to say that a fake isn't also useful, but you haven't made a case for why it has to be the be all and end all. You can use both.

Let's imagine you hard-code a network failure in your fake for a particular domain, say "this-domain-fails.com", but it otherwise "works" for all the other domains. While your double-duty test can't confirm that your real implementation handles the failure properly, it will confirm that your fake otherwise works quite similar to the real implementation for other domains. And you'd test the failure condition with a separate test with the fake set up in exactly the same way as in the double duty test (eg. with a fixture).

And sure, this does not gain any testability advantage compared to a mock, but if your test for the failure uses as much as possible of the same code paths as the real implementation, only substituting the fake in, you increase trustworthiness — if the APIs between a fake and a real implementation diverge (a common problem with mocks as tests continue to pass), it's likely to be caught by the double-duty test, and as you adapt your fake to match the new reality, you'll likely start getting the network-failure test to fail too.

In the above example, the only bits you can't fully trust, since it's not automatically tested for both implementations, is your "emulation" of the failure: you want to be careful about how you implement that so it really happens in comparable circumstances to the real implementation (eg. it's ok to throw an error where you would otherwise be calling out over network and returning data).

A lot of it depends on how you structure the code. In memory database fakes are the easiest examples, because it's clear to most people how you can structure the code to have a facade API that's used everywhere, and only have the final fake/real implementations that either do stuff in-memory or on the actual database. But you can generally do that with anything.

In general, testing is never equivalent to proving code works correctly, but I think this is the closest you can get (with a healthy dose of fuzzing on top).

However I found most software engineers not to believe it to be possible or doable with not much effort to achieve this level of trust in the code. But "showing the code" has managed to convince most — it does require a switch in the mindset, but it's quite similar to accepting that real TDD is possible for anything but toy problems (I don't think it's the most efficient way to develop, but I think it is possible and teaches people to write testable code).