Life beyond the cargo cult

Jens Jakob Balvig
Source Diving
Published in
4 min readSep 29, 2020

--

This isn’t your typical tech post evangelizing a cool new pattern or tool, or picking a fight with a popular convention.

Well, maybe a touch of the latter, but mainly it’s a tale of coming to terms with past decisions, making the best of what one is given, and finding peace with living in the uncanny valley.

So, put on your rubber boots as we get into the weeds and take a look at why and how we switched from RSpec to TestUnit, without actually switching.

The early days

I recall the time I discovered Rails (version 1.2) as an extremely exciting period of my life, spending many a happy moment scouring the internet for any knowledge I could find about this new amazing framework.

Once I’d gotten past the sheer joy of “whoops”ing together one toy app after another, eager to use it for larger projects and hungry for more knowledge, I began learning about testing.

Curiously, all the material and tutorials I came across on the subject told me to first remove the built-in /test folder and install RSpec instead.

I duly complied, without ever questioning why one wouldn’t just use the testing framework that came with Rails itself.

Thus began a journey of getting better at testing using RSpec and a series of apps all initiated with the ritual of replacing /test with /spec - to the point of eventually having it automated.

I don’t know if the people I’ve worked with since share a similar story, but every person I came into contact with seemed only to validate the choice: “everyone” was using RSpec.

It was only years later, starting a new gem from scratch, that I thought to actually give Minitest/TestUnit a whirl and…what a breath of fresh air it was!

…even if it was a little difficult to breathe through the palm that was thoroughly stuck to my face.

The revelation

Let me be clear: This isn’t an RSpec-bashing post.

It’s an impressive piece of software, and I know people who swear by it and love the unique DSL.

For my part, it might have been that I (and the people I worked with) just weren’t very good at “RSpeccing,” but the results always felt a little too elaborate and somewhat foreign when compared with code written to drive the app itself.

It would probably take an entirely separate blog post to go into details about what I mean by that, but to try to sum it up, the tests we used to write looked something like this:

Writing that using TestUnit might look like this:

Now, I don’t know if everyone will necessarily agree that the second example looks better, and either way, I’m being unfair…to both frameworks!

The difference becomes more apparent when seen across an entire test suite, and I can’t think of any single example I can write without either doing too much or too little.

What struck me, however, is the main thing I’m trying to illustrate above:

TestUnit code looks just like any other Ruby class.

Not only that, but the same core principles that go into writing solid business logic can, for the most part, be applied to writing good test cases — albeit in a slightly more restrained manner to keep the risk of bugs in the tests themselves low.

Once I’d seen the alternative, I couldn’t escape the feeling that all the extra DSL, nesting, and features were all just a little bit…well, unnecessary.

The fix

So…what is one to do when the project you’re working on already has a gem "rspec-rails" entry that’s several years old?

How long would it take to rewrite tens of thousands of lines of test code?

Thankfully, the team had begun to suspect that maybe there was a better way early on and therefore had been fairly conservative with using the full RSpec toolbox.

Therefore, ultimately what we ended up doing was keeping the framework, but adopting a “TestUnit-like” style to writing tests.

In practice, this meant:

  • Discouraging some of the more advanced features in our style guides.
  • Flattening contexts.
  • Applying a “What would TestUnit do?”-mentality.

The good thing was that for the last item, more often than not, it mostly boiled down to “do what you would do writing a normal Ruby class.”

For the example above, that equates to something like this:

Yes, it’s a little awkward!

That dangling private method is looking particularly out of place (at least until you get used to it), and we still have a handful of RSpec conventions that would be difficult to break while also keeping things somewhat consistent.

We’ve considered rewriting the suite in TestUnit (hard to justify the time investment), using it only for new tests (the context switching would probably be a nightmare), and even mixing/matching using assert in place of expect within the RSpec blocks.

None of those solutions felt realistic or even desirable, and so instead, we found a middle ground.

I still feel a twinge of sadness whenever I have to write something like:

expect(user.like(recipe)).to be_true

…knowing that it could simply be:

assert user.like(recipe)

…and I still haven’t given up hope that maybe one day we’ll have a big hackathon and leave the uncanny valley for good.

Until that day comes, nothing stops us from trying to make our livelihoods as comfortable as possible within the world we’ve been born into.

“Rails is omakase” not only made for a great meme, but also, whatever you might think of it, in the spirit of the original message I would encourage anyone who’s diverged from the menu to revisit the Rails defaults.

You might just find something you didn’t know you were missing.

--

--