{"id":92,"date":"2019-07-08T14:31:27","date_gmt":"2019-07-08T14:31:27","guid":{"rendered":"https:\/\/www.bddtesting.com\/?page_id=92"},"modified":"2019-07-08T14:31:27","modified_gmt":"2019-07-08T14:31:27","slug":"rspec-and-bdd","status":"publish","type":"page","link":"https:\/\/www.bddtesting.com\/rspec-and-bdd\/","title":{"rendered":"RSpec and BDD"},"content":{"rendered":"\n
The Ruby community loves testing. It\u2019s an area that\u2019s under constant evolution and “best practices” are always shifting. But there\u2019s a majority forming around RSpec. It\u2019s a great tool for implementing Test Driven Development (TDD) and Behavior Driven Development (BDD).<\/p>\n\n\n\n
There are generally four tiers to testing practice: test last, test first, test-driven (TDD), and behavior-driven (BDD).<\/p>\n\n\n\n
In test last<\/em> development you write an automated test suite to exercise code that has already been written. This is the most common form of testing (across languages), but in the Ruby world it\u2019s considered “too little, too late.” It is an incredibly difficult challenge to write a comprehensive test suite at this stage of development. More importantly, the design wins from using a test-first approach have already been missed.<\/p>\n\n\n\n Test first<\/em> development is when we think about the details of an implementation first, write a test that exercises that implementation, then actually implement it. It follows the “red-green-refactor” cycle, but is kind of cheating since the test was only written to permit the implementation.<\/p>\n\n\n\n In true test-driven development<\/em>, the test is the important thing. We don\u2019t think of the implementation then write a test, we write a test then figure out a possible implementation. We shift to a goal-oriented focus. When the test is written, the only thing that matters is making it pass. The implementation lives to serve the test.<\/p>\n\n\n\n Then at the highest level we push towards BDD<\/em>. In testing behavior we focus on business value. TDD focuses on how something will work, BDD focuses on why we build it at all. BDD is a difficult science that we\u2019re still working out how to realize. RSpec, and tools like Capybara and Cucumber are pushing the envelope.<\/p>\n\n\n\n A good cycle to follow for BDD is this outside-in<\/em> approach:<\/p>\n\n\n\n During the process think of your red\/green state as a permission status:<\/p>\n\n\n\n When your low-level tests are green<\/em>, you have permission to write new examples or refactor existing implementation. You must not, in the context of that refactoring, add new functionality\/flexibility.<\/p>\n\n\n\n When your low-level tests are red<\/em>, you have permission to write or change implmentation code only for the purpose of making the existing tests go green. You must resist the urge to write the code to pass your next<\/em> test, which doesn\u2019t exist, or implement features you\u2019ll need “some day.”<\/p>\n\n\n\n There are two popular approaches to high-level testing:<\/p>\n\n\n\n In my opinion, Cucumber is a great tool to use when you have a highly-technical client. If it\u2019s feasible that the client could write or co-author these user stories, then go for it!<\/p>\n\n\n\n Very rarely, however, have I worked on projects where that was true. Instead, with Cucumber, the developer often writes the user stories for their own consumption. They then write a set of natural language parsers (not fun) to turn these stories into runnable specs, then can actually run the code.<\/p>\n\n\n\n In the case where the developer is the only one who deals with the test suite, a better choice is to pair RSpec directly with Capybara. You can develop awesome user-stories-in-executable-code that are still readable and realize the goals of BDD.<\/p>\n\n\n\n For this section, it\u2019s easiest to understand the concepts by following along and modifying the sample application as you go.<\/p>\n\n\n\n Follow these Setup Instructions<\/a> to get going with Blogger<\/p>\n\n\n\n To use the library, add Now you can run: Once we have a failing integration test we have permission<\/em> to write lower-level examples.<\/p>\n\n\n\n This is where RSpec really shines. We should write examples that exercise the “happy path”, examples that try the edge cases, and examples that test the exceptions.<\/p>\n\n\n\n We should not write a line of executable implementation code unless it is for the purpose of making a unit test go green. This means that each public method in a class should have at least one unit test.<\/p>\n\n\n\n As we look at code coverage later, our coverage is driven primarily by unit tests. In Ruby, it is reasonable to expect greater than 90% code coverage when following a BDD model. In fact, you should<\/em> strive for 100% code coverage; it\u2019s difficult to attain and maintain, but it will lead your team in the right direction and reinforce a culture of continually testing your code.<\/p>\n\n\n\n Unit tests should be collected by the Ruby class that they exercise. Those specs should be stored in a file Typically the file will look like this:<\/p>\n\n\n\n The The spec file<\/em> begins with the A typical example looks like this:<\/p>\n\n\n\n The example starts with the Naming examples is a matter of style, but a good technique is to start with a present tense verb, completing the sentence after the word “it.”<\/p>\n\n\n\n Many Rubyists start all tests with \u201cshould\u201d, but this is redundant. \u201cit should not be valid\u201d can just be shortened to \u201cit is not valid.\u201d Our names can get out of control if we don\u2019t emphasize simplicity.<\/p>\n\n\n\n An example often has a few steps of business logic: the previous example creates an object and sets the title to An RSpec expectation expression is made up of three parts: subject, expectation, and matcher. For instance:<\/p>\n\n\n\n We have the subject We can setup whatever subject we want to examine in the lines before the expectation expression, in this example we built the Article<\/em> object with a blank title<\/em>.<\/p>\n\n\n\n There are only two expectations that you need to know: The matchers are where it gets interesting. There are dozens of matchers available to you out of the box, more available as RSpec addons, and you can write your own. A few of the most common include:<\/p>\n\n\n\n When you want to test that exceptions are raised you need to jump through some extra hoops. RSpec needs to wrap the execution in a This is accomplished with the You can also give In general, matching the exception class is good enough, looking at the specific message is a bit too detailed unless the purpose of your example is to demonstrate customized error messages.<\/p>\n\n\n\n Writing unit tests with RSpec is awesome, but we shouldn\u2019t stop there. What about testing further up the stack?<\/p>\n\n\n\n A common feature of Rails projects that have gone wrong are heavyweight controllers. They have actions that are dozens or even hundreds of lines of business logic that should have been pushed down to the model layer.<\/p>\n\n\n\n That\u2019s bad! Code stuck in a controller action can\u2019t be reused. It\u2019s also more difficult to test.<\/p>\n\n\n\n You can write controller tests, though, like this:<\/p>\n\n\n\n Controller tests give too much respect to an object that is, by design, just meant to connect our models to our views.<\/p>\n\n\n\n For this reason, I don\u2019t believe in testing controllers directly. Instead, as Nick Gauthier describes, use an \u201chourglass\u201d approach to testing your stack. Test heavily at the bottom model level (unit tests<\/em> or specs<\/em>), write a few tests for the controllers in the middle if they feel necessary (which they rarely are), then test heavily the user interface with integration tests.<\/p>\n\n\n\n One area of the upper stack that\u2019s great to test are helpers. Typically a helper method takes in some domain data and performs a computation or formatting operation on it, returning a string.<\/p>\n\n\n\n These are just Ruby methods, we can test them just like our other unit tests. To test helpers in Testing\/Development Cycle<\/h3>\n\n\n\n
High-Level Integration Testing<\/h3>\n\n\n\n
Setup RSpec<\/h2>\n\n\n\n
gem 'rspec-rails'<\/code> to the
:test<\/code> and
:development<\/code> groups in your Gemfile, then run
bundle<\/code> from the command line.<\/p>\n\n\n\n
rails generate rspec:install<\/code> This adds the spec directory and some skeleton files, including the
rake spec<\/code>task.<\/p>\n\n\n\n
Unit Testing<\/h2>\n\n\n\n
Theory<\/h3>\n\n\n\n
Structure<\/h3>\n\n\n\n
spec\/models\/modelname_spec.rb<\/code> such as
spec\/models\/article_spec.rb<\/code> for an
Article<\/code> class.<\/p>\n\n\n\n
1\n2\n3\n4\n5\n<\/td> require 'spec_helper'\n\ndescribe Article do\n # Your Examples Here<\/em>\nend\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
require 'spec_helper'<\/code> pulls in
spec\/spec_helper.rb<\/code> where we can setup RSpec configuration information and any initialization code common to all our examples.<\/p>\n\n\n\n
Describe<\/h3>\n\n\n\n
describe<\/code> method.
describe<\/code> takes two parameters: the name of the class being exercised and a block containing the examples. We\u2019ll talk more about using multiple
describe<\/code> blocks in the RSpec Practices section.<\/p>\n\n\n\n
An Example<\/h3>\n\n\n\n
1\n2\n3\n4\n<\/td> it \"is not valid without a title\" do\n article =<\/strong> Article.<\/strong>new(title: nil<\/strong>)\n article.<\/strong>should_not be_valid\nend\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
it<\/code> method that takes two parameters: a string naming the example and a block containing the code and matchers to execute.<\/p>\n\n\n\n
Expectation Expressions<\/h3>\n\n\n\n
nil<\/code>. Then once the data is setup for examination, we start using expectations with matchers<\/em>.<\/p>\n\n\n\n
1\n<\/td> article.<\/strong>should_not be_valid\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
article<\/code>, the expectation
should_not<\/code>, and the matcher
be_valid<\/code>.<\/p>\n\n\n\n
Subject<\/h4>\n\n\n\n
Expectation<\/h4>\n\n\n\n
should<\/code> and
should_not<\/code>. They cooperate with a matcher. Matchers return true or false, so the expectations react to those return values. If
should<\/code> gets
true<\/code> from the matcher, the expectation passes. If it gets
false<\/code>, it fails. The reverse is true for
should_not<\/code>.<\/p>\n\n\n\n
Matchers<\/h4>\n\n\n\n
be<\/code> with no parameter returns true when the subject is not nil<\/li>
be<\/code> with a parameter returns true when the subject matches the param<\/li>
be_true<\/code> and
be_false<\/code> look at the subject\u2019s boolean value (everything in Ruby has a boolean value of true<\/em> except
false<\/code> and
nil<\/code>)<\/li>
be_instance_of<\/code> with a class as parameter checks the subject\u2019s type<\/li>
be_(xyz)<\/code> calls
xyz?<\/code> on the subject, like
be_admin<\/code> would call
admin?<\/code><\/li>
include(value)<\/code> checks that
value<\/code> is in the subject collection<\/li><\/ul>\n\n\n\n
Exceptions<\/h4>\n\n\n\n
begin<\/code>\/
rescue<\/code> block so it can evaluate the exception as opposed to letting the exception bubble up and stop your example\u2019s execution.<\/p>\n\n\n\n
expect<\/code> method, which accepts a block<\/em>:<\/p>\n\n\n\n
1\n2\n3\n4\n<\/td> it \"raises an error when saving with no author\" do\n article =<\/strong> Article.<\/strong>new(author: nil<\/strong>)\n expect{ article.<\/strong>save! }.<\/strong>to raise_error\n end\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
raise_error<\/code> parameters of a string to compare against the error\u2019s message and the error class to match like this:<\/p>\n\n\n\n
1\n<\/td> expect{ article.<\/strong>save! }.<\/strong>to raise_error(ActiveRecord::<\/strong>RecordInvalid, \"article is not valid\")\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
Testing Controllers & Helpers<\/h2>\n\n\n\n
Controller Tests<\/h3>\n\n\n\n
1\n2\n3\n4\n5\n6\n<\/td> describe ArticlesController do\n it \"renders the index template\" do\n get :index\n response.<\/strong>should render_template(\"index\")\n end\nend\n<\/code><\/td><\/tr><\/tbody><\/table>\n\n\n\n
Helper Tests<\/h3>\n\n\n\n
ArticlesHelper<\/code>, we\u2019d create
spec\/helpers\/articles_helpers_spec.rb<\/code> and frame it with this:<\/p>\n\n\n\n