I never met Sapir or Whorf. My hypothesis is that libraries like RSpec which offer a thin "rephrasing" veneer over the assertion-based test module are a waste of mental capacity (and this goes triple for Cucumber and other similar regex-based trash).
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?
Honestly I just like the flow of it a little more aesthetically and I've never been so restricted on mental capacity that I noticed the extra burden. Condolences though.
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.
I'm right there with you on RSpec. We're programmers, we think in code, we think in the native primitives of the language. Now you bring in RSpec equality and comparison operators and I have to go look up RSpecs semantics vs just leveraging all my existing Ruby knowledge.
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.
This doesn't sound like you ever programmed RSpec tests?
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.
> 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?
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))`.
> It seems to me that this could have been solved by a single method: expect_match(result, eq(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 use Minitest over Rspec whenever I can in new Rails projects. The ecosystem of tools isn't as rich around Minitest but I spend a lot less time trying to remember various bits of the Rspec DSL and less time deciphering my colleauges' overly clever Rspec code.
Rspec was a product of that era of DSL mania in software that has thankfully passed.
RSpec was the first testing system that "felt right" to me. I had previously hand-rolled things in C and shell scripts for my C projects, and used a bit of JUnit, but for whatever reason the ease of just typing "rspec" and the language change to expectations instead of assertions was a lot more comfortable.
So, thanks Steven R Baker and everyone else for making software better tested and more reliable.
RSpec, Minitest, and Cucumber have a huge history in bringing Test Driven Development (TDD) and Behavior Driven Development (BDD) to many developers via Ruby on Rails. All three have fascinating interactions with language and psychology.
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 also find this fascinating. My thesis, if I have one, is that everyone learns and internalises things in different ways. And I just wanted to provide a new way of learning.
Having used RSpec and Cucumber for quite a number of years, and on quite a number of projects I think that, were I to (begrudgingly) start a greenfield Ruby project tomorrow I'd stick with MiniTest out of the box. I'm done with RSpec's extensive and seemingly magic API, and Cucumber's promises are hollow — business is not quite to write specs.
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.
> The first two aren't really better than the last at conveying what's going on.
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.
The first 1/3 of the article explains their historical motivation for this approach and specifically why this was a breakthrough for teaching. Names can matter.
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.
Exactly this. My main point, I think, is that once people learned how to _think_ about these problems, they could be shown the "normal" syntax and it would click.
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?
"making the code read like English" is what doomed Semantic UI. There was a brief period of popularity for it, but looking at all the popular Bootstrap-type libraries now, none of them try to be "semantic" or "read like English"
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.
yeah, could never understand what's wrong with assert(x == y). Why learn yet another awkward DSL on top of all other DSLs and hidden conventions, which RoR already has a lot (which kind of turned me away from it)
I’m surprised the approach of https://github.com/sconover/wrong never caught on where you just write assert { x == y } and it inspects the values you’re comparing and the methods you’re using to generate the appropriate error messages and, say, a diff of where two arrays diverge.
> I also thought (naïvely, the historical record has shown so far) that RSpec wasn’t going to be the only lasting contribution I made to the software industry as a whole. I was insistent, in 2007, that I wasn’t going to be a One Trick Pony. I still like to hope that I have many tricks left in me, but I’m less confident that I will be able to move the needle again in a similar way. I’ll be honest: this fact has caused me a lot of sadness over the years.
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.
Impact is really about being in the right place at the right time with the right skills.
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.
Absolutely. I got lucky with RSpec. I was doing the thing that was needed at the time.
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. :/
Been using RSpec for so many years. Also I still remembering picking jobs on their philosophy over testing.
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.
Like every human, even our mentors can be flawed. This one turned out to be an utter shit show who tried to take my own creations and make them his own.
In his defense, he certainly had a hand in creating _me_, but he didn't have much to do with RSpec.
Really enjoyed this article. Fascinating to see the resistance to testing back when he started, and how he used a bit of clever marketing to change the way people thought about it.
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 biggest benefit in my mind of rspec-given is that it allows you to combine the speed benefits of running the test setup once (When) with the clarity of granular assertions that report failures independently (Then).
I've used RSpec for about 3 years now and I absolutely love it - and I've also come to agree with most of the betterspecs suggestions (which I reference quite frequently).
I'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!
42 comments
[ 2.1 ms ] story [ 93.8 ms ] threadThe 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?
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.
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.
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:
- `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:
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).
Rspec was a product of that era of DSL mania in software that has thankfully passed.
So, thanks Steven R Baker and everyone else for making software better tested and more reliable.
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.
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.
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.
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?
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.
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.
Ouch!
In his defense, he certainly had a hand in creating _me_, but he didn't have much to do with RSpec.
Interesting that he's very happy with
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.
[1] https://www.betterspecs.org/
I'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.