… so much so, that I thought to write a post about it.
I promise I’ll get to Twitter4R in a moment – but first, a prelude. Think of it like eating dessert before dinner, except that dessert is a testing framework.
Coming Up To Speed on Rspec
Learning how to write rspec had been on my list for a while. And this development effort allowed me to finally got around to that. It’s a nice framework. I am familiar with EasyMock, and am aware of JMock. I never had an opportunity to get into Mockito, but I would give it a glance the next time I put on my Java hat.
There is some decent rspec
documentation out there. I found these links particularly helpful:
- The formal documentation.
- David Chelimsky’s from-the-ground-up description
- rspec.info’s drill-down into Mocks and Stubs
- Luke Redpath’s cross-over into usage under Rails
To ramp-up up on this codebase I’d be extending, I took the usual meta-approach of creating a Test Suite – yaaay, that’s what rspec
is meant for! – which I then used to test out Twitter4R
’s range of capabilities. The Modules in the rspec
rdoc which I’ve found to provide the most value are:
-
Spec::Expectations::ObjectExpectations for conditionals (eg.
should
&should_not
) -
Spec::Matchers for expectations (eg.
equal(*value*)
,be_a(*class*)
,respond_to(*method_sym*)
,raise_error
) -
Spec::Mocks::Methods for mock method definition (eg.
should_receive(*method_sym*)
) -
Spec::Mocks::MessageExpectation for mock behaviour (eg.
with(*args*)
,once
,exactly(*n*).times
,any_number_of_times
) -
Spec::Mocks::ArgumentConstraints for mock arguments (eg.
an_instance_of(*class*)
,anything
) -
Spec::Mocks::BaseExpectation for mock responses (eg.
and_return(*value*)
,and_yield(*&block*)
)
I won’t got into the deep details, but here are some examples of conditionals and expectations that I paraphrased into my meta-test:
And here’s a little bit of silly mocking:
These are just ways I thought of to exercise the width and breadth of the Twitter4R
’s featureset. Very nice. I hope that these are useful examples for people new to this gem.
I recommend the other rspec
references from above for filling in the missing details. Once you have a context / specify
or a describe / it
specification set up, you’ll be good to go. There’s much more to the library – Stories, for instance – but that’s for another day.
An Informative Walk through Twitter4R
Yes, now onto our core story.
Because no, ultimately this effort wasn’t about sinking time into learning rspec
– rather, it was a means to an end, since I had a personal need to expand the Twitter4R
gem. Specifically, I wanted to add on some Twitter search features. And contribution-wise, the final step that Susan Potter recommends is to craft up some rspec
Test Cases.
Of course, mock testing is only as good as the framework you’re built upon. Often it can be avoided. I built my Bitly4R gem (given that name, you can see that my thinking has clearly been influenced), I was able to do everything as full-on functional tests. It was easy; the bit.ly service has both shorten
and expand
endpoints, so I could reverse-test real values without having any fixed expectations.
However, the Twitter service is live and user-generated, so who knows what you’ll find. A good approach is to pinpoint a cross-cutting concern in your stack and write a mock around it. In in this case, the library relies upon Net::HTTP
. That seems like a good place to simulate responses. Of course, not having to hit the Twitter servers shortens the testing time dramatically. Anc you mock it up, and you can even test your features offline.
Here’s one of my Test Suites, again foreshortened:
Plus a little mocking of Net::HTTPResponse
:
Nothing like having some good inspiration to get you thinking about maintainability. I’m a not a strong adherent to any of the TDD or BDD denominations, but testability itself is close to my heart. Just ask the folks who’ll be looking at the micro-assertion Module that I wrote for my Facebook Puzzle submissions (which work locally under test conditions but fail miserably once thrown over their wall). Then again, I won’t need to write that (and test that) from scratch again.
So, back to talking glowingly about the Twitter4R
architecture. Yes, I used the “A” word. Yet regardless of that term’s heavyweightedness, it’s simply the result of carefully thinking out the formalization of a complex design. And once a non-author delves into extending an existing implementation, that impl’s demeanor becomes readily clear
Some of the interesting things I saw:
- An inner
bless
-ish strategy, to inject theTwitter::Client
into instanciated result classes. I’ve seen blessing as an OO-Perl-ism, for self-ication, and that metaphor carries over very nicely to this context. - Generous use of blocks / lambdas / closures in methods, for contextual iteration (eg. iterate through each
Twitter::Message
in atimeline
response). Unnecessary, but an excellent convenience for a language that offers that optional capability in a very disposable fashion (from the caller’s perspective). - Retroactive sub-object population after construction.
Twitter4R
relies upon the json gem, which deals primarily in Hashes. Post-injection, the library itereates through Arrays of Hashes and transforms them into suitableTwiltter::User
containers, etc. A great place to put such logic in the construction chain, and it doesn’t take long to get really tired of Hash hierarchies.
Good stuff, and learning with in a Ruby-centric mindset was invaluable for me. We all have to start somewhere, eh.
The Acute Long-Term Pain of Staticness
There was one issue that I ran into; static configuration. During my years of using the Spring Framework, my IOC-addled brain started thinking of everything in terms of instances – Factory Beans, POJOs, Observers & Listeners, Chain-of-Responsibility wrappers. Static configuration is a common metaphor, and in this case, there’s a static Twitter::Config
instance. Convenient and centralized. Makes perfect sense.
I mean, the fact that it was a configurable library at all was awesome. I was able to easily set up a Twitter::Client
to reference search.twitter.com. However, of course as soon as I did that, I whacked the ability for clients to talk to twitter.com
in the process. Oops!
On GPs, I refused to modify the original code. And I wanted to make sure that my superficial tweaks to the library would be thread-safe – temporarily swaping out the global Twitter::Config
in mid-operation would be an issue. Using Mutex.synchronize
seemed like the perfect choice. After finding that the same thread can’t lock a Mutex
instance twice – grr!, that’s a great trick if you can work it – I overrode the one method that cared the most about @@config
:
It works like a charm. Please, everyone just line up to tell me how I could have done it better. And I don’t mean quicker or cheaper, I mean better. Believe me, I would not have sunk the time into this end-around approach, if not for the fact that:
- I don’t want to maintain a local gem code modification (even though my impl is closely coupled to the gem impl already)
- I intend to follow that practice, so every opportunity to pull and end-around is a Valuable Learning Experience.
So, now my local Twitter4R
has search capability gracefully latched onto it (and implemented much in the flavor of the library itself). I have a mass of rspec
examples to work off in the future.
Now, I haven’t spent a great amount of time testing the thread-safeness – no one in their right mind wants to do that – but my sundry Twitter::Client
instances play nicely together in between searches and normal status operations.
And I had something useful to blog about!