Re: [rails.excellence] Unit Tests Hitting the DB, Obese Models, and Service Objects (oh my!)
- From:
- Pat Maddox
- Date:
- 2010-01-04 @ 06:14
My AR::Base subclass unit tests hit the database. I haven't found it
worthwhile to isolate those for the most part. Speed is the main concern.
Most of the time I can write tests in such a way that I don't actually hit
the db, but without introducing an explicit don't-hit-the-db layer. I
write a method that performs the business logic but doesn't save. In my
controller / service / whatever I call the method and then do an explicit
save. This is a big change in style from what I was doing a year ago,
when I would use callbacks for that sort of thing. Benefits I see:
* business logic is explicit. You don't have complicated stuff going on
magically. The code does precisely what it says
* tests run faster. A lot of the logic you write doesn't need to be
persisted, at least not in a unit test. So just make a normal Ruby method
that does what you want and make sure that things are good
* refactoring is easier because you've decoupled the business logic from
persistence
One thing that I struggled with for a while was the inherent coupling of
business & persistence logic in ActiveRecord...or rather I fell into this
mindset that they were inherently coupled. They are to some extent, but
with careful thought you can write an AR::Base subclass in such a way that
the coupling is minimal, and that converting to say DataMapper or
something else entirely wouldn't be particularly painful. It's been this
shift in mindset that's allowed me to keep taking advantage of AR's
simplicity and power but without driving myself crazy in the long run.
With respect to service and controller unit tests, my preference is to use
mocks there. Service objects in particular are amenable to mocking due to
the fact that they just coordinate logic among other business objects.
I've not tried moving all/most of the logic out of AR and into service
objects. I don't think that's something I'd ever be inclined to do,
really.
One thing I think could drastically simplify larger models is explicit
validators. Often you'll find that some validation needs to be performed
in a certain context, or in conjunction with another validation, and that
leads to ugly or confusing code with flags being passed in, funky
switches, etc. I wrote a plugin a while back that let me do contextual
validations but it required some serious monkey-patching and I doubt it
works anymore. More recently I'll just have my business methods attach
error messages right onto the record's errors object.
Pat
On Dec 25, 2009, at 10:48 AM, James Golick wrote:
> Hey all. I'm a developer from Vancouver who does a fair bit of rails,
among other things. I try my best to keep pushing the envelope in terms of
practices, so I hope this list will be a great place to discuss ideas with
other thoughtful rails hackers. </intro>
>
> When I read Jay Fields' article about not touching the db in unit tests,
I, like so many others, thought he had gone a bit overboard. As I've
learned more about better testing practices, though, I have started to see
the value in this approach. On all of my new ruby projects, my unit tests
are fully isolated with mocks and stubs fulfilling dependencies.
>
> However, in the rails world, this is not a very widely used practice.
Tests are nearly always inter-dependent and usually hit the database (i.e.
not unit tests).
>
> This is at least partly a result of the "best practice" of stuffing all
of the app's business logic in to its persistence objects. The "fat model"
pattern tends to introduce a high degree of coupling between model
objects, which makes proper stubbing and mocking practices ugly.
>
> I know Pat mentioned service objects in his aloha on rails talk.
Although I don't have any experience implementing this pattern in rails,
it strikes me as the right approach.
>
> Service objects can be decoupled from model objects, and using mocks and
stubs in unit tests becomes straightforward. It also segregates
responsibilities in a more appropriate way.
>
> Persistence objects are for persisting, and service objects are
responsible for implementing the business logic.
>
> What do you think? Have you used this pattern in a sizeable app? Do your
unit tests hit the database?
Re: [rails.excellence] Unit Tests Hitting the DB, Obese Models, and Service Objects (oh my!)
- From:
- Seth Ladd
- Date:
- 2010-01-04 @ 13:16
> required some serious monkey-patching and I doubt it works anymore. More
> recently I'll just have my business methods attach error messages right onto
> the record's errors object.
Thinking along those lines, why isn't an error thrown immediately if I
try to call a setter method with an invalid valid? Why do the
validators only fire when trying to save into the database?
Sure, this implies a lot, but the more general question is, if it's a
business rule to ensure a password is at least 4 characters, then why
am I allowed to set a password of 2 characters all the way up to the
point of saving to a DB?
Seth