[LRUG] Joined-up unit tests

Tim Diggins tim at red56.uk
Tue Jun 18 06:40:22 PDT 2024


Hi Andrew

Your rules are interesting and *potentially* appealing. Two questions:

> 3) Services should not do work, instead they should ask for work to be
done
What precisely do you mean by the difference between "do work" and "ask for
work to be done". My guess is that this means - a service should at most
instantiate a PORO and call a method on it.

> 4) Services should follow a strict contract where all services return the
same thing (I call this a ServiceResult)

Do you intend this to mean every service returns an instance of one unique
class  ServiceResult and have no behavioural differences between each
other? (so ServiceX's return and ServiceY's return are the same type). Why
does this help? (rather than them being potentially ServiceXResult,
ServiceYResult)?

thanks

Tim


On Mon, 10 Jun 2024 at 15:10, Galibier <apremdas at gmail.com> wrote:

> I have very opinionated views around this topic. Hopefully you'll find
> them interesting.
>
> I think of a service as a broker between two very different parts of your
> application, the web bit, and the business bit ( for want of better terms
> ). The sole purpose of a service is to provide a convenient method for the
> web bit to delegate work to the business bit. It does this by providing
>
> 1) A consistent method for making a call, which takes a parameter that
> contains all the relevant information collected by the web bit (e.g. a
> params hash, or perhaps an assembled model object)
> 2) A consistent method for receiving the result of the call which
> includes; a result boolean, and perhaps a modified thing that was sent.
>
> This interface is somewhat clunky by nature so some strict rules should be
> applied.
>
> 1) The service should follow a naming convention, i.e. have Service in its
> class name.
> 2) A service should NEVER call another service.
> 3) Services should not do work, instead they should ask for work to be
> done (generally by PORO's)
> 4) Services should follow a strict contract where all services return the
> same thing (I call this a ServiceResult)
>
> When you follow this pattern you get some major benefits
>
> a) No need to ever mock a service
> b) You can call services directly from integration tests to allow
> integration tests to bypass the web layer when setting things up
> c) You get to pure OO, or perhaps functional if that's your thing quickly
> and cleanly. Once you are there you are working with things that are much
> easier to Unit test.
> d) You can use the service layer as a testing cut off point. Below
> services you can Unit test. Anything that involves a service by definition
> is an integration test.
>
> This pattern works very well with BDD with each piece of behaviour ending
> up having its own service.
>
> I've dealt with a lot of difficult code where
>
> i) Almost everything ends up being a Service (to the point where PORO's
> were not being used at all)
> ii) You have Service spaghetti were you have complex Service chains, and a
> service has no idea whether its an endpoint or midpoint, and has to cater
> for both uses. Inevitably such services become more complex than they
> should because they have too many responsibilities.
>
> For me a Service calling another Service is a major anti-pattern, and in
> codebases were I have control it is not allowed.
>
> Anyhow I hope you find the above interesting. If you do end up following
> this pattern you will find your test problems around services will just go
> away.
>
> All best :)
>
> On Thu, 6 Jun 2024 at 11:59, Patrick Gleeson <patrick.c.gleeson at gmail.com>
> wrote:
>
>> Hi LRUG,
>>
>> I've been bitten by bad unit tests in the past (mostly ones I've written
>> myself), and lean towards integration tests (a la
>> https://x.com/rauchg/status/807626710350839808) when given the choice.
>> But I want to believe good unit tests are achievable. A problem I often
>> encounter is something like this:
>>
>> Service A calls service B. The unit tests for service A mock service B
>> using statements like "expect(B).to receive(:call).with(x).and_return(y)".
>> But in reality (due to refactors over time or misunderstandings between
>> teammates), if you passed x to service B you'd get a return value of z, or
>> it would raise an ArgumentError. So the unit tests for A are  only passing
>> because they're describing an impossible situation.
>>
>> Are there any good patterns for constraining the arguments and return
>> values stated in mocks somehow? I'd love it if something would check that
>> the inputs and outputs I specify when mocking service B have been "proven"
>> to be accurate in my unit tests for B. That way my unit tests could give me
>> some of the benefits of (slower, unwieldier) integration tests "for free".
>> Or am I being hopelessly naive and misguided here?
>>
>> Patrick
>> *Mediocre developer. Failed composer. Fledgeling novelist
>> (https://bedfordsquarepublishers.co.uk/book/hattie-brings-the-house-down/
>> <https://bedfordsquarepublishers.co.uk/book/hattie-brings-the-house-down/>).*
>>
>> _______________________________________________
>> Chat mailing list
>> Chat at lists.lrug.org
>> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
>> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
>> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>>
>
>
> --
> ------------------------
> Andrew Premdas
> blog.andrew.premdas.org
> _______________________________________________
> Chat mailing list
> Chat at lists.lrug.org
> Archives: http://lists.lrug.org/pipermail/chat-lrug.org
> Manage your subscription: http://lists.lrug.org/options.cgi/chat-lrug.org
> List info: http://lists.lrug.org/listinfo.cgi/chat-lrug.org
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20240618/fe0b7620/attachment.htm>


More information about the Chat mailing list