Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD and BDD in ruby

Roberto Zen
February 27, 2014

TDD and BDD in ruby

Roberto Zen

February 27, 2014
Tweet

More Decks by Roberto Zen

Other Decks in Programming

Transcript

  1. “..is an evolutionary approach to development which combines test-first development

    where you write a test before you write just enough production code to fulfill that test and refactoring.” http://www.agiledata.org/essays/tdd.html Test Driven Development www.ict4g.org
  2. Refactor TDD Schema Write code to make test
 pass Write

    a
 failing test Red - Green - Refactor process in TDD www.ict4g.org
  3. In RoR we can test: • Models • Controllers •

    Views • Routes • Helpers • Mailers • API www.ict4g.org
  4. • Models • Controllers • Views • Routes • Helpers

    • Mailers • API A User should have firstname and lastname. The user password should contain at least 5 characters. www.ict4g.org In RoR we can test:
  5. • Models • Controllers • Views • Routes • Helpers

    • Mailers • API After the registration, a new user should redirect to the index page. After logout, a user should see the message “Goodbye!”. www.ict4g.org In RoR we can test:
  6. • Models • Controllers • Views • Routes • Helpers

    • Mailers • API If I write values such as “/home” or “/root” in the address bar of the browser, I should redirect to the homepage. www.ict4g.org In RoR we can test:
  7. “By default, every Rails application has three environments: development, test,

    and production. The database for each one of them is configured in config/database.yml.” www.ict4g.org
  8. Rails creates a test folder for you as soon as

    you create a Rails project. www.ict4g.org rails g scaffold users name:string surname:string email:string
  9. require 'test_helper' ! class UserTest < ActiveSupport::TestCase test "the truth"

    do assert true end end test/unit/user_test.rb Unit Tests A test in ruby is a method www.ict4g.org
  10. An assertion (refutation) is a line of code that evaluates

    an object (or expression) for expected results. assertions/refutations www.ict4g.org
  11. assert (test, message = nil) assert_not_nil (test, message = nil)

    assert_equal (expected, actual, message = nil) assert_difference (expression, difference = 1, message = nil, &block) assert_nil (object, message = nil) assert_raise (*args, &block) assert_not_equal (expected, actual, message = nil) Assertions refute (test, message = nil) refute_equal (expected, actual, message = nil) refute_match (pattern, string, message = nil) refute_nil (object, message = nil) refute_empty(object, message = nil) refute_includes(collection, object, message = nil) Refutations www.ict4g.org
  12. require 'test_helper' ! class UsersControllerTest < ActionController::TestCase setup do @user

    = users(:one) end ! test "should get new" do get :new assert_response :success end ! test "should create user" do assert_difference('User.count') do post :create, user: { email: @user.email, name: @user.name, surname: @user.surname } end assert_redirected_to user_path(assigns(:user)) end ! test "should show user" do get :show, id: @user assert_response :success end ! test "should get edit" do get :edit, id: @user assert_response :success end ! test "should update user" do put :update, id: @user, user: { email: @user.email, name: @user.name, surname: @user.surname } assert_redirected_to user_path(assigns(:user)) end ! test "should destroy user" do assert_difference('User.count', -1) do delete :destroy, id: @user end assert_redirected_to users_path end end www.ict4g.org one: name: MyString surname: MyString email: MyString ! two: name: MyString surname: MyString email: MyString test/fixtures/users.yml test/functional/users_controller_test.rb
  13. A test could contains multiple assertions/refutations (bad practice). assertions/refutations Suppose

    I have 2 assertions. If the first assert fails I have no clue what is the state of the 2nd assertion. www.ict4g.org
  14. require 'test_helper' ! class UserTest < ActiveSupport::TestCase test "should not

    save user without name" do user = User.new assert !user.save # refute user.save end end $ rake test test/unit/user_test.rb test_should_not_save_user_without_name F ! Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s. ! 1) Failure: test_should_not_save_user_without_name(UserTest) [test/unit/user_test.rb:6]: Failed assertion, no message given. ! 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips 1 2 TDD Step by step www.ict4g.org
  15. $ rake test test/unit/user_test.rb test_should_not_save_user_without_name . Finished tests in 0.047721s,

    20.9551 tests/s, 20.9551 assertions/s. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips TDD Step by step 3 4 class User < ActiveRecord::Base validates :name, presence: true end www.ict4g.org
  16. rake test test/functional/user_controller_test.rb ! # Running tests: ! F…..F……………………………………………… [

    … ] ….. (other dots)…..(and dots again)…. ! Finished tests in 0.188464s, 37.1424 tests/s, 47.7545 assertions/s. ! 1) Failure: test_should_create_user(UsersControllerTest) [/Users/…/test/functional/users_controller_test.rb:20]: "User.count" didn't change by 1. <3> expected but was <2>. ! 2) Failure: test_should_update_user(UsersControllerTest) [/Users/…/test/functional/users_controller_test.rb:39]: Expected response to be a <:redirect>, but was <200> ! 7 tests, 9 assertions, 2 failures, 0 errors, 0 skips www.ict4g.org www.ict4g.org
  17. RSpec “RSpec is a great tool in the behavior-driven development

    (BDD) process of writing human readable specifications that direct and validate the development of your application.” www.ict4g.org
  18. require 'faker' ! FactoryGirl.define do ! ! # INVALID USER

    factory :user do |user| sequence(:email) {|n| "user-#{n}@example.com" } user.name { Faker::Name.first_name } user.phone { Faker::PhoneNumber.phone_number } user.password { "password" } user.password_confirmation { "password" } user.addresses { |a| [a.association(:address)] } end ! # COLLECTOR USER factory :collector, parent: :user do |user| sequence(:email) {|n| "collector-#{n}@example.com" } user.role { "collector" } end ! # SUPPLIER USER factory :supplier, parent: :user do |user| sequence(:email) {|n| "supplier-#{n}@example.com" } user.role { "supplier" } end ! end www.ict4g.org
  19. require 'spec_helper' ! describe User do ! # VALID USER

    ! it "Should create a user with name, surname, email and password" do u = FactoryGirl.create(:user) u.should be_valid end ! # WITHOUT SOME ATTRIBUTES ! it "Should not create a user without name" do u = FactoryGirl.build(:user, name: nil) u.should_not be_valid # assert !u.save or refute u.save end ! end Come back to RSpec www.ict4g.org spec/models/user_spec.rb
  20. spec/models/user_spec.rb require 'spec_helper' ! describe User do ! # VALID

    USER ! it "Should create a user with name, surname, email and password" do u = FactoryGirl.create(:user) u.should be_valid end ! # WITHOUT SOME ATTRIBUTES ! it "Should not create a user without name" do u = FactoryGirl.build(:user, name: nil) u.should_not be_valid end ! it "Should not create a user without surname" do u = FactoryGirl.build(:user, surname: nil) u.should_not be_valid end ! it "Should not create a user without username" do u = FactoryGirl.build(:user, username: nil) u.should_not be_valid end ! it "Should not create a user without email" do u = FactoryGirl.build(:user, email: nil) u.should_not be_valid end ! describe "Testing instance methods" do ! it "full_name method should returns full name" do user = FactoryGirl.build(:user) user.full_name.should eq [user.name, user.surname].join(" ") end end end www.ict4g.org
  21. describe User do ! # WITHOUT SOME ATTRIBUTES ! it

    "Should not create a user without name" do u = FactoryGirl.build(:user, name: nil) u.should_not be_valid end ! it "Should not create a user without surname" do u = FactoryGirl.build(:user, surname: nil) u.should_not be_valid end ! it "Should not create a user without username" do u = FactoryGirl.build(:user, username: nil) u.should_not be_valid end ! it "Should not create a user without email" do u = FactoryGirl.build(:user, email: nil) u.should_not be_valid end end describe User do ! # WITHOUT SOME ATTRIBUTES %w(name surname username email).each do |attr| context "with a nil #{attr}" do it "should not create a valid user" do FactoryGirl.build(:user, attr => nil ).should_not be_valid end end end www.ict4g.org
  22. require "spec_helper" ! describe UsersController do ! describe "routing" do

    ! it "routes to #index" do get("/users").should route_to("users#index") end ! it "routes to #show" do get("/users/1").should route_to("users#show", :id => "1") end ! it "routes to #edit" do get("/users/1/edit").should route_to("users#edit", :id => "1") end ! it "routes to #create" do post("/users").should route_to("users#create") end ! it "routes to #update" do put("/users/1").should route_to("users#update", :id => "1") end ! it "routes to #destroy" do delete("/users/1").should route_to("users#destroy", :id => "1") end ! end ! end Test routes www.ict4g.org
  23. Capybara “Capybara helps you test web applications by simulating how

    a real user would interact with your app.” www.ict4g.org http://jnicklas.github.io/capybara/
  24. Capybara actions click_link(‘id-of-link’) click_link('Link Text’) ! click_button('Save') click_on('Link Text') click_on('Button

    Text’) ! fill_in 'First Name', :with => 'John' fill_in 'Password', :with => ‘Password' ! choose ('A Radio Button') check ('A Checkbox') uncheck ('A Checkbox') select 'Option', :from => 'Select Box’ ! visit('http://www.google.com') www.ict4g.org
  25. page.has_selector?('table tr') page.has_css?('table tr.foo') page.has_content?(‘foo') ! page.should have_selector('table tr') page.should

    have_css('table tr.foo') page.should have_content('foo') find_field('First Name').value find_link('Hello').visible? find_button('Send').click ! find("#overlay").find("h1").click ! find('#navigation').click_link('Home') Capybara matchers Capybara finders www.ict4g.org
  26. describe "the signin process" do ! before :each do FactoryGirl.create(:email

    => '[email protected]', :password => 'password') end ! it "signs me in" do visit '/sessions/new' within("#session") do fill_in 'Login', :with => '[email protected]' fill_in 'Password', :with => 'password' end click_link 'Sign in' expect(page).to have_content 'Success' end ! end Capybara with RSpec www.ict4g.org
  27. A feature usually contains a list of scenarios. You can

    write whatever you want up until the first scenario, which starts with the word Scenario on a new line. ! Every scenario consists of a list of steps, which must start with one of the keywords Given, When, Then, But or And.
 Cucumber treats them all the same, but you shouldn’t. Features www.ict4g.org
  28. Feature: Login ! I want to login to bring the

    food as supplier @javascript Scenario: successful login as a supplier Given there exists [email protected] And I am on the landing page When I sign in as [email protected] Then I should be on the home page And I should see "Offers" And I should see "New Offer" www.ict4g.org
  29. Feature: User sign in ! @javascript Scenario: successful sign in

    as a supplier Given I am on the landing page When I sign in as … ! @javascript Scenario: successful sign in as a collector Given I am on the landing page When I sign in as … ! @javascript Scenario: successful sign in as a manager Given I am on the landing page When I sign in as … www.ict4g.org
  30. Feature: User sign in ! Background: Given I am on

    the landing page ! @javascript Scenario: successful sign in as a supplier When I sign in as … ! @javascript Scenario: successful sign in as a collector When I sign in as … ! @javascript Scenario: successful sign in as a manager When I sign in as … www.ict4g.org
  31. Step definitions Given /^(?:|I )am on (.+)$/ do |page_name| visit

    path_to(page_name) end ! When /^(?:|I )follow "([^"]*)"$/ do |link| click_link(link) end ! When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value| fill_in(field, :with => value) end ! # Examples: # # Given I am signed in as [email protected] # Given I am logged in as [email protected] # When I sign in as [email protected] # When I log in as [email protected] ! Given /^I (?:am signed in|sign in|am logged in|log in) as ([\w]+[\w-]*[\w\d](?:@example.com))$/ do |mail| @current_user = User.find_by_email(mail) @current_user.should_not be_nil ! steps %{ Given I am on the landing page When I follow "Login" within the menu And I fill in "Email" with "#{mail}" And I fill in "Password" with "password" Then I press "Sign in" within the modal popup } end Capybara gem Capybara gem Capybara gem RSpec gem www.ict4g.org