I enjoyed the depth of this article, but I think it missed the point of much of Minitest’s criticism of RSpec. In the first example the difference between Minitest’s test in function and RSpec’s test in a block is not about def test_ vs it, it’s about execution context.
In Minitest, the execution context of a test is the scope of a function in a class, it’s “just Ruby”.
In RSpec, your context is… well, it’s a block, so I’ll have to see what calls it and what that has in scope: looking at the rspec gem it’s just a dep target but rspec-core sound promising, so let’s look in the source dir and I guess core.rb is it but it’s only loading more modules, the “runner” it mentions sounds promising, so I descend into the core subdirectory, lots of code here, find runner.rb, and I find run_specs juggling a @world, @configuration, its reporter, and example_groups, ah, I see g.run(reporter) and I know when I group specs in rspec it calls them “example groups” so a single spec is probably just an implicit example group of one spec, I’ll pop back up into example_group.rb and a search for “def run” doesn’t find anything, but there’s a self.run class method and, yeah, scrolling back up past a couple hundred lines about context hooks and replacing metadata I see this is all a module that’s getting mixed into something else so that makes sense, and run is mostly about error-handling and delegates its work to a private run_examples and that creates an instance of… I guess whatever this module got mixed into because it’s a bare new call with no class name, then it calls example.run(instance, reporter) and I’m browsing back up to core, ah, there is an example.rb and in its run method in the Example class it starts a block named with_around_and_singleton_context_hooks and run_before_example and then I guess the main work is @example_group_instance.instance_exec(self, &@example_block) so I go back up to example_group.rb and it doesn’t define instance_exec so in conclusion I have used RSpec professionally for five years and I have no fucking clue what the execution context is for my spec and what could be in or out of scope, but it usually works and when it doesn’t or I’m curious the only possible answer I can give is “it’s magic”.
I’m always curious about the cases where it doesn’t work in RSpec as you might expect. I do hear that, and can understand that it looks different from a typical module/class definition, but I just don’t run into weird cases very much. I include modules and write methods in my specs, use shared contexts and shared examples… I may have just developed a good mental model around how it works from years of experience, which is why I like hearing from a fresh perspective about what is not intuitive.
I think I remember Tenderlove mentioning a similar issue in his mind - that he knows and understands Ruby inheritance and method lookup hierarchy and whatnot, but then RSpec is a whole ‘nother system to wrap your head around.
I think there’s a mindset for some that they need to deeply understand the tools they use, and that can definitely get tricky in RSpec. In fairness it can get tricky in LOTS of common libraries, though - any time I dive into ActiveRecord I feel the same way. I would consider myself fairly proficient at using it; whether or not I can explain how it works is another matter.
I’m sorta ok with magic if I find it useful - and I find a lot of RSpec features super-useful, and I feel a bit like I’m fighting to get the same level of features/expressiveness in Minitest (usually by cobbling together extension libraries). I absolutely agree that it’s easier to understand Minitest - the library. It’s less code, and it does less.
I have always found the “it’s too magical” criticism very humorous coming from the Rails side of things. Rails is just absolutely filled with magic. What’s in session and how does it get there without any line of code referencing it? What happens when you reach a controller action defined as
def index; end
and so on. Can anyone give a coherent 1 or 2 sentence explanation of has_and_belongs_to_many? Where do all these ActiveRecord methods (find_by_attr_or_other_attr) come from, why do the controllers for my models need to be pluralized, etc etc? Granted much of this magic is what makes Rails so easy to work with - it does a great deal of things for you, and it does them with a somewhat coherent “convention over configuration” design aesthetic… but it’s hard to see what’s wrong with Rspec aside from that their magic was NIH.
The it "something" do syntax is almost always easier to read than a def test_something in my opinion, and the extremely stubborn rejection of any reasonable support for stubbing calls[1] (aside from nested do...end blocks) by the Minitest devs has at any rate convinced me that Rspec is the testing library for me.
[1] There is of course a reasonable point to be made that “one ought not to make a habit of overly stubbing calls in tests,” and there is a reasonable counterpoint that some of us inherit projects with large numbers of complex external integrations and stubbing these calls in order to get a set of tests established before refactoring is a lot easier than redesigning the system because somebody at Minitest thinks it’s poor design.
I enjoyed the depth of this article, but I think it missed the point of much of Minitest’s criticism of RSpec. In the first example the difference between Minitest’s test in function and RSpec’s test in a block is not about
def test_vsit, it’s about execution context.In Minitest, the execution context of a test is the scope of a function in a class, it’s “just Ruby”.
In RSpec, your context is… well, it’s a block, so I’ll have to see what calls it and what that has in scope: looking at the
rspecgem it’s just a dep target butrspec-coresound promising, so let’s look in the source dir and I guesscore.rbis it but it’s only loading more modules, the “runner” it mentions sounds promising, so I descend into thecoresubdirectory, lots of code here, findrunner.rb, and I findrun_specsjuggling a@world,@configuration, itsreporter, andexample_groups, ah, I seeg.run(reporter)and I know when I group specs in rspec it calls them “example groups” so a single spec is probably just an implicit example group of one spec, I’ll pop back up intoexample_group.rband a search for “def run” doesn’t find anything, but there’s aself.runclass method and, yeah, scrolling back up past a couple hundred lines about context hooks and replacing metadata I see this is all a module that’s getting mixed into something else so that makes sense, andrunis mostly about error-handling and delegates its work to a privaterun_examplesand that creates an instance of… I guess whatever this module got mixed into because it’s a barenewcall with no class name, then it callsexample.run(instance, reporter)and I’m browsing back up tocore, ah, there is anexample.rband in itsrunmethod in theExampleclass it starts a block namedwith_around_and_singleton_context_hooksandrun_before_exampleand then I guess the main work is@example_group_instance.instance_exec(self, &@example_block)so I go back up toexample_group.rband it doesn’t defineinstance_execso in conclusion I have used RSpec professionally for five years and I have no fucking clue what the execution context is for my spec and what could be in or out of scope, but it usually works and when it doesn’t or I’m curious the only possible answer I can give is “it’s magic”.I’m always curious about the cases where it doesn’t work in RSpec as you might expect. I do hear that, and can understand that it looks different from a typical module/class definition, but I just don’t run into weird cases very much. I include modules and write methods in my specs, use shared contexts and shared examples… I may have just developed a good mental model around how it works from years of experience, which is why I like hearing from a fresh perspective about what is not intuitive.
I think I remember Tenderlove mentioning a similar issue in his mind - that he knows and understands Ruby inheritance and method lookup hierarchy and whatnot, but then RSpec is a whole ‘nother system to wrap your head around.
I think there’s a mindset for some that they need to deeply understand the tools they use, and that can definitely get tricky in RSpec. In fairness it can get tricky in LOTS of common libraries, though - any time I dive into ActiveRecord I feel the same way. I would consider myself fairly proficient at using it; whether or not I can explain how it works is another matter.
I’m sorta ok with magic if I find it useful - and I find a lot of RSpec features super-useful, and I feel a bit like I’m fighting to get the same level of features/expressiveness in Minitest (usually by cobbling together extension libraries). I absolutely agree that it’s easier to understand Minitest - the library. It’s less code, and it does less.
I have always found the “it’s too magical” criticism very humorous coming from the Rails side of things. Rails is just absolutely filled with magic. What’s in
sessionand how does it get there without any line of code referencing it? What happens when you reach a controller action defined asand so on. Can anyone give a coherent 1 or 2 sentence explanation of
has_and_belongs_to_many? Where do all these ActiveRecord methods (find_by_attr_or_other_attr) come from, why do the controllers for my models need to be pluralized, etc etc? Granted much of this magic is what makes Rails so easy to work with - it does a great deal of things for you, and it does them with a somewhat coherent “convention over configuration” design aesthetic… but it’s hard to see what’s wrong with Rspec aside from that their magic was NIH.The
it "something" dosyntax is almost always easier to read than adef test_somethingin my opinion, and the extremely stubborn rejection of any reasonable support for stubbing calls[1] (aside from nesteddo...endblocks) by the Minitest devs has at any rate convinced me that Rspec is the testing library for me.[1] There is of course a reasonable point to be made that “one ought not to make a habit of overly stubbing calls in tests,” and there is a reasonable counterpoint that some of us inherit projects with large numbers of complex external integrations and stubbing these calls in order to get a set of tests established before refactoring is a lot easier than redesigning the system because somebody at Minitest thinks it’s poor design.