Explicit vs. Implicit and Imperative vs. Declarative Scenarios (Engineering Software as a Service §7.9) © 2013 Armando Fox & David Patterson, all rights reserved 1 Types of Scenarios • Are all requirements directly from the User Stories? • Scenarios should have 3 to 8 steps; is there a way to keep them closer to 3 than to 8? 2 Explicit vs. Implicit Scenarios • Explicit requirements usually part of acceptance tests – Likely explicit user stories and scenarios: list movies • Implicit requirements are logical consequence of explicit requirements, typically integration testing – Movies listed in chronological order or alphabetical order? 3 Imperative vs. Declarative Scenarios • Imperative: Initial user stories with many steps, specifying logical sequence to desired result – Not-DRY if many user stories imperative • Declarative: describe state, not sequence – Fewer steps • Example Feature: movies should appear in alphabetical order, not added order • Example Scenario: view movie list after adding 2 movies 4 Example Imperative Scenario Given I am on the Create New Movie page RottenPotatoes home page When I fill in "Title" with When I follow "Add new "Apocalypse Now" movie" And I select "R" from Then I should be on the "Rating" Create New Movie page And I press "Save Changes" When I fill in "Title" with Then I should be on the "Zorro" RottenPotatoes home page And I select "PG" from When I follow ”Movie Title" "Rating" Then I should see And I press "Save Changes" "Apocalypse Now" before Then I should be on the "Zorro" RottenPotatoes home page Only step specifying behavior; When I follow "Add new Rest are implementation. But movie" BDD specifies behavior, 5 not implementation! Then I should be on the Domain Language • • • • Declarative as if making domain language Uses terms and concept of app Informal language Declarative steps describe the state app should be in – Imperative: sequence of steps to change current state into desired state 6 Example Declarative Scenario Feature: movies when added should appear in movie list Scenario: view movie list after adding movie (declarative and DRY) Given I have added "Zorro" with rating "PG13" And I have added "Apocalypse Now" with rating "R" Then I should see "Apocalypse Now" before "Zorro" on the Rotten Potatoes home page 3 steps vs. 15 steps: 2 to set up test, 1 for behavior Declarative scenarios focus attention on feature being described and tested vs. steps needed to set up test What about new step definitions? 7 Declarative Scenario Needs New Step Definitions Given /I have added "(.*)" Then /I should see "(.*)" with rating "(.*)"/ do before "(.*)" on (.*)/ do |title, rating| |string1, string2, path| steps %Q{Given I am on step “I am on #{path}" the Create New Movie page regexp = When I fill in "Title" /#{string1}.*#{string2}/m with "#{title}” # /m means match across newlines And I select "#{rating}" from "Rating" page.body.should =~ regexp And I press "Save Changes“} end end • As app evolves, reuse steps from first few imperative scenarios to create more concise and descriptive declarative scenarios 8 9 Which is TRUE about implicit vs. explicit and declarative vs. imperative scenarios? 1. Explicit requirements are usually defined with imperative scenarios and implicit requirements are usually defined with declarative scenarios 2. 3. Declarative scenarios aim to capture implementation as well as behavior 4. All are false 10 11 Fallacies & Pitfalls, BDD Pros & Cons, End of Chapter 7 (Engineering Software as a Service §7.11) © 2013 Armando Fox & David Patterson, all rights reserved 12 Pitfalls • Customers confuse digital mock-ups with completed features – Nontechnical customers think highly polished digital mock-up = working feature • Use Lo-Fi mockups, as clearly representations of proposed feature 13 Pitfalls • Sketches without storyboards – Need to reach agreement with customer on interaction with pages as well as page content • Storyboards / “animating” sketches reduces misunderstandings 14 Pitfalls • Adding cool features that do not make the product more successful – Customers reject what programmers liked • Trying to predict what code you need before need it – BDD: write tests before you write code you need, then write code needed to pass the tests • User stories help prioritize & BDD minimizes what you code => reduce wasted effort 15 Pitfalls • Delivering a story as “done” when only the happy path is tested – Need to test both happy path and sad path • Correct app behavior when user accidentally does wrong thing is just as important as correct behavior when does right thing – To err is human 16 Pitfalls • Careless use of negative expectations – Beware of overusing “Then I should not see….” – Can’t tell if output is what want, only that it is not what you want – Many, many outputs are incorrect • Include positives to check results “Then I should see …” 17 Pitfalls • Careless use of positive expectations – Then I should see “Emma” what if string appears multiple times on page? – Can pass even if feature not working • Use Capybara’s within helper – Constrains scope of matchers in a CSS selector – Then I should see “Emma” within “div#shopping_cart” – See Capybara documentation 18 19 Which statement is FALSE about Lo-FI UI and BDD? 1. The purpose of the Lo-Fi UI approach is to debug the UI before you program it 2. 3. A BDD downside is that it may lead to a poor software architecture, since focus is on behavior 4. None are false; all three above are true 20 21 How Popular is Agile? • IT SW companies using Agile: Amazon, eBay, Facebook, Microsoft, Salesforce, … • 2011 Survey of UCB ESaaS Alumni in industry – 68% Agile vs. 32% P&D (5% Spiral, 5% Waterfall) • 2012 survey of 66 distributed projects* – 55% Agile vs. 45% P&D • Forrester: 60% teams use Agile as primary SW development in 2012 vs. 45% in 2009** • Gartner: 80% teams primarily Agile by end of 2012*** *H.-C. Estler, M. Nordio, C. A. Furia, B. Meyer, and J. Schneider. Agile vs. structured distributed software development: A case study. Proc. 7th Int’l Conf. on Global Software Engineering (ICGSE’12), pp 11–20, 2012. **http://articles.economictimes.indiatimes.com/2012-08-06/news/33065621_1_thoughtworks-software-development-iterative. ***http://www.pmi.org/en/Professional-Development/Career-Central/Must_Have_Skill_Agile.aspx. 22 Testing Tools in Book Autotest (Ch. 6) Cucumber (Ch. 5) SimpleCov (Ch. 6) RSpec (Ch. 6) reek (Ch. 8) Capybara (Ch. 5) flog/flay (Ch. 8) Rack::Test (Ch. 5) Webdriver (Ch. 12) SauceLabs (Ch. 12) No JavaScript JavaScript JavaScript + Multiple Browsers (Figure 7.15, Software as a Service by Armando Fox and David Patterson, 1st edition, 2013.) 23 BDD Good & Bad • User stories - common language for all stakeholders, including nontechnical – 3x5 cards – LoFi UI sketches and storyboards • Write tests before coding • Difficult to have continuous contact with customer? • Leads to bad software architecture? – Will cover design patterns, refactoring in future – Validation by testing vs. debugging Images used for satire purposes only 24 BDD • Doesn’t feel natural at first • Rails tools make it easier to follow BDD • Once learned BDD and had success at it, no turning back – 2/3 Alumni said BDD/TDD useful in industry 25 26 RSpec on Rails (Engineering Software as a Service §8.2) © 2012 Armando Fox & David Patterson Licensed under Creative Commons AttributionNonCommercial-ShareAlike 3.0 Unported License Rspec: Domain-Specific Language for Testing • RSpec tests (specs) inhabit spec directory rails generate rspec:install creates structure • Unit tests (model, helpers) • Functional tests (controllers) • Integration tests (views)? app/models/*.rb app/controllers/ *_controller.rb spec/models/*_spec.rb spec/controllers/ *_controller_spec.rb app/views/*/*.html.haml (use Cucumber!) Example: Calling TMDb • New RottenPotatoes feature: add movie using info from TMDb (vs. typing in) • How should user story steps behave? When I fill in "Search Terms" with "Inception" And I press "Search TMDb" Then I should be on the RottenPotatoes homepage ... Recall Rails Cookery #2: adding new feature == new route+new controller method+new view The Code You Wish You Had What should the controller method do that receives the search form? 1. It should call a method that will search TMDb for specified movie 2. If match found: it should select (new) “Search Results” view to display match 3. If no match found: it should redirect to RP home page with message http://pastebin.com/kJxjwSF6 31 The method that contacts TMDb to search for a movie should be: ☐A class method of the Movie model ☐ ☐ A controller method ☐ A helper method 32 33 The TDD Cycle: Red–Green–Refactor (Engineering Software as a Service §8.2) © 2013 Armando Fox & David Patterson, all rights reserved Test-First Development • Think about one thing the code should do • Capture that thought in a test, which fails • Write the simplest possible code that lets the test pass • Refactor: DRY out commonality w/other tests • Continue with next thing code should do Red – Green – Refactor Aim for “always have working code” How to test something “in isolation” if it has dependencies that would affect test? The Code You Wish You Had What should the controller method do that receives the search form? 1. It should call a method that will search TMDb for specified movie 2. If match found: it should select (new) “Search Results” view to display match 3. If no match found: it should redirect to RP home page with message TDD for the Controller Action: Setup • Add a route to config/routes.rb # Route that posts 'Search TMDb' form post '/movies/search_tmdb' – Convention over configuration will map this to MoviesController#search_tmdb • Create an empty view: touch app/views/movies/search_tmdb.html.haml • Replace fake “hardwired” sad path method in movies_controller.rb with empty method: def search_tmdb end What Model Method? • Calling TMDb is responsibility of the model... but no model method exists to do this yet! • No problem...we’ll use a seam to test the code we wish we had (“CWWWH”), Movie.find_in_tmdb • Game plan: – Simulate POSTing search form to controller action. – Check that controller action tries to call Movie.find_in_tmdb with data from submitted form – The test will fail (red), because the (empty) controller method doesn’t call find_in_tmdb – Fix controller action to make green http://pastebin.com/zKnwphQZ 40 Which is FALSE about should_receive? It provides a stand-in for a real method ☐ that doesn’t exist yet ☐ ☐ It can be issued either before or after the code that should make the call ☐ It exploits Ruby’s open classes and metaprogramming to create a seam 41 42 Seams (Engineering Software as a Service §8.3) © 2013 Armando Fox & David Patterson, all rights reserved Seams • A place where you can change app’s behavior without changing source code. (Michael Feathers, Working Effectively With Legacy Code) • Useful for testing: isolate behavior of some code from that of other code it depends on. • should_receive uses Ruby’s open classes to create a seam for isolating controller action from behavior of (possibly buggy or missing) Movie.find_in_tmdb • Rspec resets all mocks & stubs after each example (keep tests Independent) How to Make This Spec Green? • Expectation says controller action should call Movie.find_in_tmdb • So, let’s call it! http://pastebin.com/DxzFURiu The spec has driven the creation of the controller method to pass the test • But shouldn’t find_in_tmdb return something? Test Techniques We Know obj.should_receive(a).with(b) Optional! 47 Eventually we will have to write a real find_in_tmdb. When that happens, we should: ☐ Replace the call to should_receive in our test with a call to the real find_in_tmdb ☐ find_in_tmdb should_receive Keep the should_receive seam in the spec, ☐ but if necessary, change the spec to match the API of the real find_in_tmdb ☐ Remove this spec (test case) altogether since it isn’t really testing anything anymore 48 49 Expectations (Engineering Software as a Service §8.4) © 2013 Armando Fox & David Patterson, all rights reserved Where We Are & Where We’re Going: “Outside In” Development • Focus: write expectations that drive development of controller method – Discovered: must collaborate w/model method – Use outside-in recursively: stub model method in this test, write it later • Key idea: break dependency between method under test & its collaborators • Key concept: seam—where you can affect app behavior without editing code The Code You Wish You Had What should the controller method do that receives the search form? 1. It should call a method that will search TMDb for specified movie 2. If match found: it should select (new) “Search Results” view to display match 3. If no match found: it should redirect to RP home page with message “It Should Select Search Results View to Display Match” • Really 2 specs: 1. It should decide to render Search Results – more important when different views could be rendered depending on outcome 2. It should make list of matches available to that view • New expectation construct: obj.should match-condition – Many built-in matchers, or define your own Should & Should-Not • Matcher applies test to receiver of should count.should == 5` Syntactic sugar for count.should.==(5) 5.should(be.<(7)) be creates a lambda that tests the predicate expression 5.should be < 7 5.should be_odd Syntactic sugar allowed result.should include(elt) calls #include?, which usually gets handled by Enumerable republican.should cooperate_with(democrat) calls programmer’s custom matcher #cooperate_with (and probably fails) Use method_missing to call odd? on 5 result.should render_template('search_tmdb') Checking for Rendering • After post :search_tmdb, response() method returns controller’s response object • render_template matcher can check what view the controller tried to render http://pastebin.com/C2x13z8M • Note that this view has to exist! – post :search_tmdb will try to do the whole MVC flow, including rendering the view – hence, controller specs can be viewed as functional testing Test Techniques We Know obj.should_receive(a).with(b) obj.should match-condition Rails-specific extensions to RSpec: response() render_template() 57 Which of these, if any, is not a valid use of should or should_not? ☐ result.should_not be_empty ☐ ☐ result.should_not match /^D'oh!$/ ☐ All of the above are valid uses 58 59