Preamble
This is my first time attempting to build an app with SRP in mind and really trying to use tests to drive the code for the site, rather than starting with my data architecture (ActiveRecord) and then building the app to fit.
But I'm running into problems. I've subscribed to and watched a lot of Destroy All Software screencasts and in theory I like what he preaches but I'm having trouble making it work in practice.
Problem at hand
I know the main feature of my app will be to search profiles based on location. So I write a simple Cucumber feature for that (purposely leaving off routes/controllers/etc for the time being to simplify the task at hand).
search.feature:
Feature: Search for profiles
Scenario: By zipcode
When I search for 90210
Then I will see profiles near 90210
search_steps.rb:
When /^I search for 90210$/ do
@profiles = ProfileSearch.new.near_location(90210).all
end
Then /^I will see profiles near 90210$/ do
pending # express the regexp above with the code you wish you had
end
No problems, now on to specs:
profile_search_spec.rb:
require_relative '../../app/services/profile_search'
describe ProfileSearch do
it "finds nearby profiles"
it "does not find far away profiles"
end
profile_search.rb:
class ProfileSearch
end
I've used a ProfileSearch class for a few reasons.
I'm not really sure what to do next. ProfileSearch
obviously depends on the Profile
model, and I'm quite certain that this will be ActiveRecord.
So the question is do I start spec'ing out and building Profile
and just start loading Rails in my tests? This seems like the easiest option but something about it seems wrong. I feel like I would be desiging and building behaviour that my app hasn't specifically called for yet. I would have to think about fields and relations and storage, etc., all of which my app currently shouldn't care about.
Or should I use stubs/mocks for all calls to Profile
in my ProfileSearch
spec and make sure the correct methods are being called? This also seems wrong because I wouldn't really be testing the behaviour then, and I'd have to rewrite the test when switching to Solr or ElasticSearch even though the same behaviour would be expected.
Or I should actually create a working Profile model that doesn't use ActiveRecord for the time being but responds correctly to all the right methods as Uncle Bob demonstrated when building his wiki thing? This seems like it might be the best approach theoretically but knowing I will be using ActiveRecord in the future it also seems quite redundant.
Or... f*ck it and throw everything in the ActiveRecord model :\\
There are so many patterns, principles, and best practices floating around in my head I don't know WTF to do.
What would you do?
First you need to choose your testing approach:
Cucumber is designed for BDD (outside-in) testing. Cucumber scenarios are meant to exercise your entire stack (web page, controllers, models, database — or whatever your stack is), not to be used as unit tests as you seem to be doing above. So if the first thing you write is a Cucumber scenario, you'll need to call all of your layers into being to make that first scenario pass. This is what I do, whether with Cucumber (my favorite) or RSpec feature specs. After you've done that you're free to refactor to meet whatever design criteria you like, and you can test-drive detailed requirements with unit tests.
Alternatively, you could pick the trickiest part of your stack, unit-test that into existence, and unit-test up and down the layers and maybe eventually acceptance-test until you have a whole app. On one hand, that might be a good idea to prove that you can get the tricky parts to work before you invest in the easy parts. On the other, I find that when I guess at component interfaces without a working acceptance test I always guess wrong, so I don't do it this way.
Either way, your goal is a ProfileSearch
class that returns Profile
instances but doesn't promise that they're ActiveRecord objects or anything else persistence-specific. How you handle those classes in tests depends on the type of test:
If you use acceptance specs (Cucumber or RSpec feature specs), they won't use stubs, they will use all real implementation classes, and they'll load Rails, because that's their point and that's how they're designed. Sorry, they'll be slow. Don't write any more of them than you need to specify your important scenarios.
In unit tests of classes that use ProfileSearch
, you can stub it out and return POROs for speed. (If you're using rspec-rails, require 'spec_helper'
and not rails_helper
in those specs to avoid loading Rails when you run them by themselves.)
The easiest way to make unit tests of ProfileSearch
itself pass is probably to just make Profile
an ActiveRecord model and have ProfileSearch
use Profile
's ActiveRecord functionality and return real instances of Profile
. Those specs will need to require 'rails_helper'
too.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.