Last active
April 6, 2022 19:47
-
-
Save myronmarston/9c21b85c784871161d36 to your computer and use it in GitHub Desktop.
Revisions
-
myronmarston revised this gist
Jan 23, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -21,7 +21,7 @@ to require from `~/.rspec`) put: ``` ruby RSpec.configure do |c| # Each of these can be one of the ANSI color code integers or one # of [:black, :white, :red, :green, :yellow, :blue, :magenta, :cyan]. # The values I've assigned here are the defaults so you'll want to change them. -
myronmarston revised this gist
Jan 23, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,7 +16,7 @@ apply to any project on your machine. In this file put something like: --require "~/configure_rspec_colors.rb" ``` And then in `~/configure_rspec_colors.rb` (or whatever file you decide to require from `~/.rspec`) put: ``` ruby -
myronmarston revised this gist
Jan 23, 2015 . 1 changed file with 45 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -129,6 +129,39 @@ or `context`. (I'd also point out that we do not consider `set_it_up`, `children` and `register` to be part of our public API and this may break in future versions...) > Can I put it in a module? You can indeed. This works: ``` module MyHelpers def helper 11 end end describe "something" do include MyHelpers it "works" do expect(10).to eq(helper) end it "really works" do expect(11).to eq(helper) end end ``` If you wanted the module globally included in all example groups, you can call `include` on config: ``` ruby RSpec.configure do |config| config.include MyHelpers end ``` > But if that’s the case, why not just use Ruby classes? There are a few reasons for this: @@ -259,3 +292,15 @@ this feature is, we also have some shortcuts: `fit` is an alias for `it` with `:focus`, `fdescribe` is an alias for `describe` with `:focus` and `fcontext` is an alias for `context` with `:focus` -- so just prefix any of these with the letter `f` to temporarily make RSpec run only them. > Now, do I care which one you use? No. As long as you test your code, I > am happy. A professional developer should be able to work in either > one of these because they essentially do the same thing: test your > code. <3 <3 I completely agree with this. I'd go so far as to say that as lead RSpec maintainer, I'm extremely grateful that RSpec's not the only testing library around. It's good for the Ruby community that we have multiple well-maintained libraries for testing. Thanks again, @tenderlove! -
myronmarston created this gist
Jan 23, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,261 @@ > I highly suspect that the RSpec core team all use black backgrounds in > their terminals because sometimes the colors aren’t so nice on my > white terminal I certainly use a black background. I'm not sure about the other RSpec core folks. Regardless, if there are some color changes we can make that would make output look good on a larger variety of backgrounds, we'll certainly consider that (do you have some suggested changes?). In the meantime, the colors are configurable, so you can change the colors to fit your preferences on your machine. First, create a file at `~/.rspec`. If this file exists, RSpec will use the command line options configured in this file, allowing you to set personal preferences that apply to any project on your machine. In this file put something like: ``` --require "~/configure_rspec_colors.rb" ``` And then in "~/configure_rspec_colors.rb" (or whatever file you decide to require from `~/.rspec`) put: ``` ruby RSpec.configure do |c| # Each of these can be one of the ANSI color codes integers or one # of [:black, :white, :red, :green, :yellow, :blue, :magenta, :cyan]. # The values I've assigned here are the defaults so you'll want to change them. # Color config is available in RSpec 2.13+. Since this file will # always be loaded by RSpec and you may have projects on earlier # versions, it's a good idea to only do this on versions that support it. if c.respond_to?(:default_color=) c.default_color = :white c.detail_color = :cyan c.failure_color = :red c.fixed_color = :blue c.pending_color = :yellow c.success_color = :green end end ``` > But where is the `helper` method defined? What’s it’s visibility? Can I > put it in a module? Can I use inheritance? Who can call it? Can I call > `super` from the extracted method? If so, where does `super` go? These are > not the types of questions I want to be pondering when I have 3000 > test failures and really long spec files to read through. My mind > spins off in to thoughts like “I wonder how RSpec works?”, and “I > wonder what my cat is doing?” > > From what I can gather, calling `describe` in RSpec essentially defines > a class. This is indeed what it does (plus some other stuff that I'll get into in a bit). I think it'll help you understand by showing an example: ``` ruby describe "Using an array as a stack" do def build_stack [] end def stack @stack ||= build_stack end it 'is initially empty' do expect(stack).to be_empty end context "after an item has been pushed" do def build_stack super.push :item end it 'allows the pushed item to be popped' do expect(stack.pop).to eq(:item) end end end ``` This example is almost exactly (sans some details such as RSpec assigning the classes to different constants) doing this: ``` ruby class UsingAnArrayAsAStack < RSpec::Core::ExampleGroup set_it_up "Using an array as a stack" def build_stack [] end def stack @stack ||= build_stack end it 'is initially empty' do expect(stack).to be_empty end class AfterAnIteHasBeenPushed < self set_it_up "after an item has been pushed" def build_stack super.push :item end it 'allows the pushed item to be popped' do expect(stack.pop).to eq(:item) end end children << AfterAnIteHasBeenPushed end RSpec.world.register UsingAnArrayAsAStack ``` As you can see, RSpec is in fact creating classes, just like you guessed, and it uses `class_exec` to evaluate the `describe` block. Individual examples are evaluated in the context of an instance of the class (like minitest!). Nested contexts are simply subclasses, which means they inherit all helper methods just like you would expect, and you can use `super` as you would expect. You can also see that RSpec does some extra stuff (`set_it_up` plus registering the classes via `children <<` and `RSpec.world.register` for the top-level one) when you call `describe` or `context`. (I'd also point out that we do not consider `set_it_up`, `children` and `register` to be part of our public API and this may break in future versions...) > But if that’s the case, why not just use Ruby classes? There are a few reasons for this: * In its original formulation (as per [Dave Astel's](http://blog.daveastels.com.s3-website-us-west-2.amazonaws.com/2014/09/29/a-new-look-at-test-driven-development.html) and [Dan North's](http://dannorth.net/introducing-bdd/) articles introducing BDD), one of the things BDD was about was using a new vocabulary to discuss testing, that, in their experience, led you in a better direction when learning TDD. `describe` and `it` come from that heritage. I think that they were on to something, but I also think that the alternate vocabulary is most useful for people learning TDD (as they discuss in their articles). For someone who has developed code using TDD for years, the alternate vocabulary probably doesn't matter much, and may even be a hindrance for someone very used to the xUnit style. * `describe` allows you to pass arbitrary English descriptions. Ruby classes have many restrictions on what is allowed in their names. Many people (including myself!) find it very useful to be able to use arbitrary English descriptions. I often include a rationale in my descriptions so that if a test ever fails, the developer is given an explanation for _why_ the behavior expressed in the test was desirable at the time it was written (which can help them decide if they still need to maintain that behavior). For example, here's one test description from a project at work: ``` ruby it 'ignores subsecond differences in timestamps since MySQL and redis store them at different granularities' do # ... end ``` * The fact that `describe` is a method that accepts arguments means we can provide APIs for extensions to be able to get the "class being tested". This is a more explicit alternative to what `ActionController::TestCase` does: rather than trying to infer the class-under-test from the test class name, the user gives it to us as an explicit argument. Explicit argument means less potential confusion when the user misspells it (they'll get an immediate NameError from ruby!), and the user doesn't have to know anything about how inferrence works. * `describe` also supports RSpec's metadata system, where example groups (test classes) and examples (individual test methods) have attached metadata that can be used to slice and dice your suite in many ways. For example, you can tag an example group as `:slow` (`describe MyClass, :slow do`) and exclude groups with that tag (`rspec --tag "~slow"`). rspec-rails uses `:type` metadata to know which Rails testing APIs to make available to which example groups. You can tag an example group as `:pending` to indicate that it should not yet pass, and RSpec will run it, expecting it to fail, and notify you if it now passes. These are all features that would be harder to support if you couldn't pass additional arguments when defining your test class. > You’ll see “hi!” printed 3 times, once for each it, and “hello!” > printed once. For the nested context, I would like to print “hello!” > before it prints “hi!”. In a normal inheritance situation, I would > change the call to super. I am not sure how to do what I just > described without learning a new thing Honestly, I never thought about the fact that the `def setup` style of hooks allows you to control the order like this! This is indeed something that doesn't have a simple solution in RSpec. I'll think more about if there's anything we can do to improve it. Overall, when I run into this type of situation -- where I've got a code in a `before` hook, but an individual test needs to run something else first -- I treat it as a code smell. It suggests that the code doesn't really belong in a `before` hook since it's not actually something that we always want to run first. Usually I'll pull the code in the hook out into a helper method and then explicitly call it from the places where it needs to happen. All that said, there is a way to accomplish what you are trying to do, but it's pretty obtuse: ``` ruby describe "something" do def say_hi puts "hi!" end before do |ex| say_hi unless ex.metadata[:skip_hi] end # ... context "another thing", :skip_hi do before do puts "hello!" say_hi end # ... end end ``` This uses the metadata system (which I mentioned earlier) to skip the logic in the before hook, and then in the nested group we can call it at an appropriate time. As I said, that's far less elegant than using `super` like you mentioned, and I don't recommend doing this, but it could be a useful temporary step during a larger refactoring. > Another thing that bugs me is that if I do try to refactor my RSpec > tests, and I change any lines, then the line that I copy and pasted to > run just the one test won’t work anymore. I have to figure out the new > line. I can use the name of the test by specifying the -e flag on RSpec, > but then I lose the “copy, paste, run” feature that I love so much. My suggestion is to use RSpec's focus-filtering feature. The `spec_helper.rb` file generated by `rspec --init` has a couple lines of config that you'll need for this: ``` ruby RSpec.configure do |config| config.filter_run :focus config.run_all_when_everything_filtered = true end ``` With that configured, you can (temporarily) add `:focus` metadata to any example or example group, and RSpec will run just those examples or example groups. If nothing has `:focus`, then everything will be run (that's what the last line of config is for). With the `:focus` metadata it doesn't matter if your specs move around to other lines. Given that you'll only want to temporarily change the metadata, and how useful this feature is, we also have some shortcuts: `fit` is an alias for `it` with `:focus`, `fdescribe` is an alias for `describe` with `:focus` and `fcontext` is an alias for `context` with `:focus` -- so just prefix any of these with the letter `f` to temporarily make RSpec run only them.