I'm working my way through CS1691.X, a free online course offered through edx.org from Berkeley. So far we've done an introduction to the Agile software development lifecycle, a whirlwind (although enlightening) overview of ruby, and an introduction to rails in the context of its SaaS architecture and REST-ful web interface. This past week has focused on Behavior-Driven Development (BDD) using the Cucumber gem.
BDD is the practice of using the behavioral requirements and specifications of a software application to drive development. Usually, these "specifications", colloquially called user stories, are developed side-by-side with stakeholders, and serve as informal requirements documentation. Using the Cucumber tool, these user stories can be turned into acceptance and integration tests. Finally, because stakeholder's want to see a visual representation of the software, low fidelity (Lo-Fi) UI sketches (mockups/wireframes) can be quickly created and bounced off of stakeholder's for feedback and approval.
As an example, if you were building an online e-commerce bookstore, one behavior that visitors to your online store would need to be able to accomplish would be to add a book of interest to their "shopping cart", so that they could later purchase it. That behavior can be documented using the Cucumber tool as an informal specification of the application, and would look something like this:
Feature: Add a book to the shopping cart As a shopper [As some kind of stakeholder] I want to add a book to my shopping cart [I want to accomplish some task] So that I can purchase it [So that I can achieve some goal]
This short narrative acts as a user story that describes a specific feature of the application, and is usually developed on 3x5" index cards along with the stakeholder to encourage feedback and reduce the chances of developing something that the customer does not actually want (remember, it's all about what your users want, for the most part!). The brackets to the right indicate the general form each line of the story should take.
Using Cucumber, this user story can be parlayed into differing scenarios for the same feature, each of which contains many steps that specify the general behavior of the feature as it would be used by a shopper:
Background: Given the following books exist: | title | author | price | publisher | num_in_stock | | War of the Worlds | H.G. Wells | $7.11 | Tribeca Books | 10 | | 1984 | George Orwell | $10.55 | Plume | 0 | And I am on the Book Lover's home page Scenario: Add an In Stock item to the shopping cart Given I follow "The War of the Worlds" And I am on the "War of the Worlds" book page And I press "Add to Cart" Then I should be on the Shopping Cart page And I should see "1 item added to Cart" And I should see "Proceed to Checkout" Scenario: Add an Out of Stock item to the shopping cart Given I follow "1984" And I am on the "1984" book page And I press "Add to Cart" Then I should be on the Shopping Cart page And I should see "We're Sorry, that book is out of stock." And I should not see "Proceed to Checkout"
The Feature section specifies what task you want to be able to accomplish, and gives the basic reasoning for why a given stakeholder would want that functionality implemented. The behavior of adding a product to an online shopping cart is a core business value of any e-commerce platform, and this feature would be high on the priority list. BDD is useful in that sense because it helps developers stick to core business values and complete the highest priority features first. Writing these behavioral tests first also gives you some specification requirements to work from, and lets other collaborating developers read in plain language what features you have been diligently working on.
The Background section above allows you to DRY (Don't Repeat Yourself) out your feature files, and allows you to specify common steps for each scenario of that feature. For example, each scenario in the feature above starts with being on a given book's product information page. In the Background section you can specify the steps that are common between each scenario of that feature. In this case, the common steps include the creation of two test books (The War of the Worlds and 1984), and the assertion that the user is on the "Book Lover's" home page.
From there, the two scenarios describe two possible behaviors: a user adds a book that is in stock, or a user adds a book that is out of stock. In each scenario, the application behaves differently, but the feature being used remains constant.
Of course, we would need to translate this plain language user story into code that can be tested. Cucumber makes this pretty easy by allowing us to map the steps under each scenario to step definitions (placed in a separate file). These step definitions are written in a Domain-Specific Language (DSL) written on top of Ruby that uses regexes to match the steps to their step definitions. For example, the step definition for the last step in the Out of Stock scenario would look something like this:
Then /^(?:|I )should not see "([^"]*)"$/ do |text| if page.respond_to? :should page.should have_no_content(text) else assert page.has_no_content?(text) end end
What this step definition does is it captures the text that it should NOT see in a regexp backreference (a.k.a. capture group), and then passes it to a regular ruby block. The method definition then checks, using the Capybara API (Capybara allows developers to simulate how a real user would interact with an app) if the page has that content or not: if it does, the test fails (red), if it does not, the test passes (green).
Automating tests this way makes it much easier to check if you have broken previously implemented features during the continual development of a software project. I certainly plan to use this much more in my current projects. Up next in CS169.1X is Test-Driven Development with RSpec!