[LRUG] Joined-up unit tests

Galibier apremdas at gmail.com
Fri Aug 2 04:46:35 PDT 2024


Apologies for late reply.


On Tue, 18 Jun 2024 at 14:49, Tim Diggins <tim at red56.uk> wrote:

> 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.
>

Yes pretty much this but ...

-  if there is some cross cutting concern like auditing service calls, then
the service could do that as well.
- if multiple PORO's need to be called and results collated, then perhaps
this could be done in the service, or perhaps delegated to another PORO.


>
> > 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)?
>
>
Yes, generally this interface is a boolean to indicate success and failure,
and then something else. The something else could be relatively specific
e.g. a model object, or something vaguer depending on application design
and what your approach to typing is. If you need different types of
ServiceResult you can always subclass.

thanks
>
> Tim
>

Your welcome

All best

Andrew


------------------------

>
>
> 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
>>
> _______________________________________________
> 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.lrug.org/pipermail/chat-lrug.org/attachments/20240802/f6122241/attachment.htm>


More information about the Chat mailing list