April #TMIL - RSpec
As expected, this series of posts has drifted off schedule. At least the drafts have been on time! Below is a list of RSpec tips, preferences and best practices I've been accumulating over the last year, finally realizing that's it much more art than science.
- Start by writing an outline of your specs so that when you run them with the --format documentation, or -fd, option, you get a readable outline that clearly explains what the code should (or shouldn't) be doing. This can be more high level, or have a line for the happy/sad/edge paths of each method, or whatever works for you. I've found that using documentation format as a guide, along with organizing your tests using context/describe blocks, helps keep the code SOLID and intentions clear. You can make this the default, and enhance readability with --color, by adding a line for each one to a .rspec file.
- Along with the above, driving from the outside in has been extremely useful in many situations for me. This Ruby Tapas episode provides a great example of this approach. I've found that starting with BDD leads to more reliable code and helps me to see relationships between objects and scenarios, though I try to avoid mocking (or get rid of them once the design is driven out). I like to start by describing what should happen as a series of Given-When-Then statements, e.g. Given I'm on the home page, When I sign in with valid credentials, Then I should see something awesome, then converting those into grouped specs, extracting helper methods, etc. While Cucumber more clearly enforces that syntax, the additional overhead of setting it up gets in the way, and I stick with Capybara in RSpec feature specs.
- Add complete coverage, but don't over do it. If it feels like you're rewriting your code in the specs or you're testing parts of your framework, you're doing it wrong. This video has a great discussion of the idea that testing should be "as little as possible to achieve a given level of confidence."
- Don't forget about Mini Test as an alternative to RSpec. After all, it's in the standard library, complete with examples involving cheeseburgers! Brandon Hilkert's post also reminds us of other benefits, like using fixtures (especially if your factories' associations and traits are getting out of control).
- Get to know VCR for recording HTTP interactions and Puffing Billy to record browser interactions. This way your specs never make real web requests, resulting in more deterministic tests, and so you don't have to deal with "stubbing the internet" and can run your test suite without an internet connection.
- Be careful with mocks. I find them helpful when it's absolutely essential to know a message on another object gets called as a result of what you're testing. More succintly, "stubs are for queries and mocks are for commands." Or sometimes using them to drive out the design of collaborator objects (and to encourage decoupled code via dependency injection as suggested here). For example, mocking out a Song class when writing tests for your Playlist class then converting the mocks to real objects (and non-stubbed methods) when you've built your Song class. More often, I've found that when there's too much mocked or stubbed behavior necessary to set up a test, it's usually a sign the test is focused on the wrong thing, in the wrong place, or the code is too coupled. Opinions on the use and misuse of mocks vary greatly; I've found more success avoiding them when possible. More discussion on the use/misuse of mocks here, here and here. And here. This post by James Golick demonstrates some benefits of the mockist style and reminds one of the importance of having intergration tests (which you should regardless of your testing style) to avoid the common problems with mocking (i.e. mocked behavior allows false positives when the underlying interface has changed).
- Redo these exercises every now and then and see how much better and faster you're getting.
- Guard, Guard::LiveReload, and Guard::RSpec are essential for front end development. The basic setup of your Guardfile found in the Railscast will get you going. Just add the gems and bundle, then install the LiveReload browser extension (for Chrome, Safari or Firefox). You can add more plugins, like Guard::CoffeeScript, by running `guard init pluginname`. A list of plugins can be found on the Guard wiki. After enabling the extension in your browser while your development server is running, when you change a file, Guard will refresh your browser window and kick off your specs. Nice.
- There are lots of other must-have's in your development group, a good overview can be found here. I also like to liberally use Pry and so-called "REPL Driven Development" as described by Pry core contributor Conrad Irwin. (Video | Slides)