As usual, my day did not pan out as expected. But, also as usual, I learned a lot!
Coming Up To Speed on Rspec
So, learning rspec has been on my list for a while. I finally got around to it. Nice framework. I am familiar with EasyMock, and am aware of JMock. I never had an opportunity to get into Mockito, but I’d give it a glance next time I put on my Java hat.
There is some decent documentation on it out there. I found these links particularly helpful:
During my ramping-up, I took the usual meta-approach of creating a test suite — and yaaay, that’s what
rspec is meant for! — which I then used to test out its own range of capabilities. The Modules in the rdoc which I’ve found to provide the most value are:
- Spec::Expectations::ObjectExpectations for conditionals (eg.
- Spec::Matchers for expectations (eg.
- Spec::Mocks::Methods for mock method definition (eg.
- Spec::Mocks::MessageExpectation for mock behaviour (eg.
- Spec::Mocks::ArgumentConstraints for mock arguments (eg.
- Spec::Mocks::BaseExpectation for mock responses (eg.
I won’t got into the deep details, but here are some examples of conditionals and expectations that I paraphrased into my meta-test:
Could not embed GitHub Gist 2167214: API rate limit exceeded for 22.214.171.124. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
And here’s a little bit of silly mocking:
Could not embed GitHub Gist 2167235: API rate limit exceeded for 126.96.36.199. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
These are just ways I thought of to exercise the width and breadth of the library. Very nice. I hope that these are useful examples for people new to this gem.
I recommend the other 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
No, I didn’t really want to spend time learning
rspec — I mean heck, I’m busy — but I had a personal need to expand the Twitter4R gem. Specifically, I wanted to add on some Twitter search features, and I was very impressed with how this library has been built. Contribution-wise, the final step that Susan Potter recommends is to craft up some rspec tests.
Of course, mock testing is only as good as the framework you’re built upon. The assumption is that
Net::HTTP is going to do it’s job, so mock it up and you can even test your Twitter features offline. When I built Bitly4R (given that name, my thinking has clearly been influenced), I did everything as full-on functional tests. It was easy; bit.ly has both
expand commands, so I could reverse-test real values without having any fixed expectations.
However, Twitter is live and user-generated, so who knows what you’ll find. Mocking covers that for you. And of course not having to hit the service itself shortest testing time dramatically.
Here’s one of my tests, again foreshortened:
Could not embed GitHub Gist 2167251: API rate limit exceeded for 188.8.131.52. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
Plus a little mocking of
Could not embed GitHub Gist 2167255: API rate limit exceeded for 184.108.40.206. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
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 that 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 the
Twitter::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 a
timeline 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 suitable
Twiltter::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
Could not embed GitHub Gist 2167260: API rate limit exceeded for 220.127.116.11. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
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!