History of RSpec
stevenrbaker.com
stevenrbaker.com
It's humbling to see the creator of a widely used and loved library express these vulnerable - and relatable - thoughts. Thank you for RSpec and all that you do, Steven. It changed so many people's lives for the better. We all do what we can.
If you don't have the skill to find another place and time - and it is a different, distinct skill which can depend on social and economic capital for the search - the one chance may be all you get. It doesn't necessarily speak to your capability or lack of it. For most of us, it's luck.
And it’s not for lack of skill or even ideas - often it’s time combined with luck.
The thing that causes me a lot of angst is that I used to feel like I was "on the frontier." Skating to where the puck is going.
These days? I am confused by where the puck ends up, and am left scratching my head.
They say "luck is the intersection of preparation and opportunity." As a straight white dude, opportunity is not my problem. But I increasingly find myself being ill-prepared for the weird places this industry is going. :/
Today what we take for granted that testing is the solution to so many problems, yet in the past I had to have great lengthy debates over if testing has any value whatsoever.
You were on the forefront of that battle. And thank you.
expect(x).to(equal(y))
or
x.should_be(y)
or
assert_eq(x, y)
These are just different ways of saying the same thing. The first two aren't really better than the last at conveying what's going on.
Although I like Ruby and Rspec, this desire for "beautiful code" and "making the code read like English" never rhymed with me.
And from a more practical standpoint, RSpec is now much more than just a different mapping of some key words.
the DSL rspec landed on can be very concise, which I appreciate. This is mostly made possible by Ruby’s short block syntax and the ability to nest them.
One thing this enables is an easy way to define a hierarchy of concerns with their own nested setup/tear down blocks, which allows you to easily share code across similar example groups.
This is nice in and of itself, but it also leads you to differentiate the “boring setup” code from the “here’s the part I’m actually testing code” which is helpful for clarity when coming back to old tests.
These things are sharp knives and can be used poorly, but I’ve never yet found a system I like better.
This is why I thought it was valuable as a teaching tool, and not valuable beyond that. Others disagreed. I'm just a dude trying to help people, though. What do I know?
Though I've never really liked the verbiage, TFA does correctly remark that it guides towards the correct parameter ordering in ways `assert_eq` does not. And depending on the way assertion errors are formatted, getting the ordering wrong can lead to odd / awkward messages.
'course these days I use pytest which just asks for an `assert` and takes care of taking apart the expression being asserted, so it's a non-issue.
Because at the end of the day the function of the code is not the function of speaking or writing the English language.
Styling front-end components does not work like the English language works. I can call something "big" in English and everyone knows what I mean, but the browser will determine what an element having the "big" class means based on the context of its parent elements and its children elements. That difference makes it extra confusing to use code that's trying to be "semantic" especially if the "semantics" are trying to hide more expressive and complicated functionality from the user.
So, thanks Steven R Baker and everyone else for making software better tested and more reliable.
The specification goes in the test title and if necessary, a comment. Then the programmer writes code to test the spec. I've never understood what is suboptimal about this. Why do developers need to be tricked?
Cucumber otoh is an absolutely amazing tool...in the correct organization. When used as it was designed, ie analysis's working with stakeholders to gather requirements which can then be turned into executable tests for the developers to conform with is amazing. Its when developers start writing cucumber features without talking to anybody else that the whole thing becomes pointless.
1. Variations in preferences. Given that every brain is structured a bit differently, and that every person has different backgrounds and experiences, it's reasonable to conclude that different syntax or vocabulary will make things "click" for different people. This is evidenced by the fact that there is more than one programming language in daily use.
2. Cognitive burden. We're all smart enough to learn whatever syntax, framework, or vocabulary is necessary to accomplish a task. But if there is a different way of structuring common tasks that flows better for someone, then choosing that flow over something more traditional frees up that tiny extra bit of mental effort for other tasks. These optimizations accumulate, so even a very smart person can benefit from doing easy things in the easiest way for them. Then, teams benefit on top of that from the reduced communication burden.
Rspec was a product of that era of DSL mania in software that has thankfully passed.
Edit: To expand on that. I thought I knew and understood the model–view–controller having only read about it. But only when I actually used it in early RoR version I truly understood what kind of advantages and disadvantages this approach had.
The article explains the reasoning of RSpec and I'd say it should answer your questions.
I think there's a combination of things in play here which caused RSpec to take off:
- RSpec's matchers were a bit more expressive than the built-in assertions. This meant not only that you could write "should_be_greater" and when an error occurs it would present it in a much nicer way ("expected result to be greater than X, got Y") than the simple "assert".
- For many people when getting started with TDD/BDD it does help to start from a library/toolkit which has the concepts built-in. A good example is being able to actually write full sentences for the test name instead of being forced to summarize in snake_case.
That said, I do find the current RSpec expectation syntax to be annoying and over complicated. And that's not because I don't like DSL, but because I think that this DSL is focused on making it readable as an English sentence instead of trying to build its own language with its own primitives.
Example:
expect(result).to eq(3)
- `eq(3)` I can understand: This creates an assertion. This knows how to validate the value and how to format the error message. Having these as standalone objects seem to allow some pretty cool usages.- However, what is expect()? What does it return? What does that object represent? Why is there a to() method? What other methods are on `to`? It seems to me that this could have been solved by a single method: expect_match(result, eq(3)). Yes, it doesn't read as an English sentence, but it's easy to understand the components: You have the assertion which is a standalone object, and then you have the `expect_match` which checks a result against that assertion. `expect_match` belongs to the test runner because we want it to fail the test if the assertion fails.
And when you look at comparisons it's even worse:
expect(actual).to be > expected
I've been writing Ruby for over 10 years and immediately I struggle to parse this line. I guess `be` is a local method (coming from what module?) which returns an object whose only purpose is to convert operators into an assertion? To make things even more confusing, you can also write `expect(actual).to be`, but that is something completely different (it passes when the result is truthy).And here's the problem: The be() method doesn't actually serve any useful purpose in the "language" of matchers. It doesn't provide anything that couldn't have been accomplished within the existing framework. It's only there as a placeholder to make the whole expression read as an English sentence instead of `expect_match(result, gt(3))`.
Except now you've circled back to the issue rspec originally tried to solve: getting the result and the reference in the correct order.
While I care very little about rspec, that is definitely one thing to be said for it: the matcher system makes it extremely obvious and regular: you `expect` a (result) to (match) a (reference).
I believe there's an excellent masters thesis to be written that investigates the histories of these 3 tools and their adoption curves.
The history that I've seen is teams doing 4 steps: try RSpec and love it, try Cucumber and hate it, try Minitest and eventually favor it over RSpec because of directness and refactorability, then much later on revisit Cucumber for inter-team business specs and love it.
Along the curve, some teammates would express strong preferences, some wouldn't care much, and some would actively say only one was the One True Tool. Areas like this-- much like vim vs. emacs-- fascinate me because they hint at deep ways we think about code and work.
I firmly believe that Elixir's ExUnit mostly got it right: `refute` and `assert` — that's it. I do miss nested context's and describes though.
Interesting that he's very happy with
expect(expected).to.equal(actual)
I wonder if `assert` or `check` or even `test` makes us think differently about what we're doing (in the assertion itself, not in the method name to set up the test).The ideal is a single method (call it `assert` if you want) that takes a block, and does the right thing, is the overall best.
Given { @stack = Stack.new }
When { @stack.push(1) }
Then { @stack.peek == 1 }
And { @stack.size == 1 }
What you wish for is embedded in When and And. Check it out: https://github.com/jimweirich/rspec-givenI'd be really interested to hear which suggestions you don't agree with and why, if you could spare the time for a comment or another blog post!
Thanks again for RSpec, it's wonderful.
Ouch!
In his defense, he certainly had a hand in creating _me_, but he didn't have much to do with RSpec.