TESTING
what for and how

     Michał Czyż
       @cs3b
selleo.com
http://webout.eu
http://www.worksmartmompreneurs.com/blog/wp-content/uploads/2012/03/url.jpg
● precise requirements definition
● automatic requirements
  verification
● code design
● documentation
● safety net for code changes
● catch errors
http://www.projzilla.com/blog/wp-content/uploads/2011/12/Testing.jpg
http://www.projzilla.com/blog/wp-content/uploads/2011/12/Testing.jpg
● precise requirements definition
● automatic requirements
  verification
● code design
● documentation
● safety net for code changes
● catch errors
TDD
BDD
OUTSIDE-IN
http://pragprog.com/book/achbd/the-rspec-book page#29
http://pragprog.com/book/achbd/the-rspec-book page#29
User perspective
testing using Ruby
https://twitter.com/#!/moonmaster9000/status/169540724474384384
https://twitter.com/#!/athoshun/statuses/165138347432488962
https://twitter.com/#!/defkode/status/124066265642967040
http://www.jackkinsella.ie/2011/09/26/why-bother-with-cucumber-testing.html
http://blog.jonasbandi.net/2010/09/acceptance-vs-integration-tests.html
click "Products"
within('table.index) do
   see :ordered => [ "apache baseball cap",
                      "zomg shirt" ]
end

click "admin_products_listing_name_title"
within('table.index') do
   see :ordered => [ "zomg shirt",
                      "apache baseball cap"]
end
click "Orders"
within('table#listing_orders', :row => 1) do
   click_link "R100"
end

click "Payments"
within('#payment_status') do
   see("Payment: balance due")
end

within(:cell =>[2,2]) { see "$39.98" }
within(:cell =>[2,3]) { see "Credit Card" }
within(:cell =>[2,4]) { see "pending" }
click
click 'Administrations',
      'Products',
click :and_dismiss => 'Delete'
click :and_accept => 'Delete'

click :image => 'OK'

click :element => "#some_div"
fill_in
fill_in 'www.selleo.com' => 'Link'

fill_in 'www.selleo.com' => 'Link'
        'Selleo' => 'Company',
        'Poland' => 'Country'
fill_in :check => 'Remember me'

fill_in :check => [ 'Red',
                    'Green',
                    'Grey' ]
fill_in :uncheck => 'Remember me'

fill_in :uncheck => [ 'Red',
                      'Green',
                      'Grey' ]
fill_in :choose => 'Female'

fill_in :choose => [ 'Europe',
                     'Poland' ]
fill_in :select => { 'Poland' =>
                     'Countries' }

fill_in :select => { 'Poland' =>
                     'Countries',

                    'Silesia' =>
                    'Provinces' }
fill_in :unselect => { 'Poland' =>
                       'Countries' }

fill_in :unselect => { 'Poland' =>
                       'Countries',

                      'Silesia' =>
                      'Provinces' }
fill_in :attach => { 'me.png' =>
                     'Avatar'}

fill_in :attach => { 'me.png' =>
                     'Avatar',
                     'selleo.png' =>
                     'Company Logo' }
# spec_helper.rb
Kameleon.configure do |c|
 c.assets_dir = '(...)/fixtures/assets'
end
see
see 'www.selleo.com',

see 'one',
    'another piece of text',
    'and another one'
see :link => 'Wrocloverb'

see :link => { 'Wrocloverb' =>
               'http://wrocloverb.com' }

see :links => { 'Wrocloverb' =>
                'http://wrocloverb.com',
                'Selleo' =>
                'http://www.selleo.com' }
see :image => 'OK'

see :image => 'icon_ok.png'

see :images => [ 'completed',
                 'approved' ]
see 'Ruby' => 'First Name'

see 'Wisła' => 'City',
    'Silesia' => 'Province'
see :field => 'Email'

see :fields => ['Email',
                'City',
                'Street' ]
see :empty => 'Last Name'

see :empty => [ 'Last Name',
                'City' ]
see :checked => 'Remember me'

see :checked => [ 'Newsletter',
                  'Remember me' ]
see :unchecked => 'Remember me'

see :unchecked => [ 'Newsletter',
                    'Remember me' ]
see :selected => { 'Poland' =>
                   'Countries' }

see :selected => { 'Poland' =>
                   'Countries',

                   'Silesia' =>
                   'Provinces' }
see :selected => { [ 'Red',
                     'Green',
                     'Orange' ] =>
                   'Colors' }
see :unselected => { 'Poland' =>
                     'Countries' }

see :unselected => { 'Poland' =>
                     'Countries',

                     'Silesia' =>
                     'Provinces' }
see :unselected => { [ 'Red',
                       'Green',
                       'Orange' ] =>
                     'Colors' }
see :ordered => [ 'Bart',
                  'Tom',
                  'Anette' ]
see 5 => ".element"

see 3 => :menu_item

see 5 => [:xpath,
          '//div[@class="element"]' ]
not_see
not_see 'php'

not_see 'java',
        'c#',
        '.net'
not_see :field => 'Email'

not_see :fields => [ 'Email',
                     'City',
                     'Street' ]
not_see :link => 'Wrocloverb'

not_see :link => { 'Wrocloverb' =>
             'http://wrocloverb.com' }

not_see :links => [ 'Wrocloverb',
                    'Selleo' ]
not_see :image => 'OK'

not_see :image => 'ok.png'

not_see :images => [ 'OK',
                     'rails.png' ]
act_as
visit('/')

act_as(:default) do
  click 'Products'
end
visit('/')

create_session(:user)
visit('/login')

act_as(:default) do
  not_see :field => 'Email'
end

act_as(:user)
see :fields => [ 'Email', 'Password' ]
http://s1.desktopia.net/wp-content/uploads/walls/thumbs/Funny-Chameleon-575x359.jpg
click_link "Products"
within('table.index tr:nth-child(2)') {
   page.should have_content("apache baseball
   cap") }
within('table.index tr:nth-child(3)') {
   page.should have_content("zomg shirt") }

click_link "admin_products_listing_name_title"
within('table.index tr:nth-child(2)') {
   page.should have_content("zomg shirt") }
within('table.index tr:nth-child(3)') {
   page.should have_content("apache baseball
   cap") }
https://github.com/spree/spree/blob/master/core/spec/requests/admin/products/products_spec.rb#L22
click "Products"
within('table.index) do
   see :ordered => [ "apache baseball cap",
                      "zomg shirt" ]
end

click "admin_products_listing_name_title"
within('table.index') do
   see :ordered => [ "zomg shirt",
                      "apache baseball cap"]
end
click_link "Products"
click_link "admin_new_product"

fill_in "product_name", :with => "Baseball Cap"
fill_in "product_sku", :with => "B100"
fill_in "product_price", :with => "100"
fill_in "product_available_on", :with =>
                                 "2012/01/24"
select "Size", :from => "Prototype"
check "Large"
click_button "Create"
page.should have_content("successfully
created!")
https://github.com/spree/spree/blob/master/core/spec/requests/admin/products/products_spec.rb#L77
click "Products",
      "admin_new_product"

fill_in "Baseball Cap" => "product_name",
        "B100" => "product_sku",
        "100" => "product_price",
        "2012/01/24" => "product_available_on",
        :select => {
                      "Size" => "Prototype"
                   },
        :check => "Large"
click "Create"
see "successfully created!"
within(:css, 'table.index tr:nth-child(2)') {
                           click_link "Edit" }

fill_in "adjustment_amount", :with => "99"
fill_in "adjustment_label", :with => "rebate 99"
click_button "Continue"
page.should have_content("successfully
                              updated!")
page.should have_content("rebate 99")
page.should have_content("$99.00")




https://github.com/spree/spree/blob/master/core/spec/requests/admin/orders/adjustments_spec.rb#L48
within(:row => 2) { click "Edit" }

fill_in 99 => "adjustment_amount",
          "rebate 99" => "adjustment_label"
click "Continue"
see "successfully updated!",
       "rebate 99",
       "$99.00"
click_link "Orders"
within('table#listing_orders tbody tr:nth-child
(1)') { click_link "R100" }

click_link "Payments"
within('#payment_status') { page.should
have_content("Payment: balance due") }

find('table.index tbody tr:nth-child(2)
   td:nth-child(2)').text.should == "$39.98"
find('table.index tbody tr:nth-child(2)
   td:nth-child(3)').text.should == "Credit Card"
find('table.index tbody tr:nth-child(2)
   td:nth-child(4)').text.should == "pending"
https://github.com/spree/spree/blob/master/core/spec/requests/admin/orders/payments_spec.rb#L32
click "Orders"
within('table#listing_orders', :row => 1) do
   click_link "R100"
end

click "Payments"
within('#payment_status') do
   see("Payment: balance due")
end

within(:cell =>[2,2]) { see "$39.98" }
within(:cell =>[2,3]) { see "Credit Card" }
within(:cell =>[2,4]) { see "pending" }
DEMO
 inspired by a case taken
from a production system
create_session(:user)
sign_in_as(:user)

create_session(:admin)
sign_in_as(:admin)
act_as(:user) do
 not_see 'IS OPEN',
           'IS CLOSED'
 within(:right_column) do
   not_see 'Submit a proposal',
              'Submit'
 end
end
act_as(:admin) do
 within(:menu) { click 'Set Contest' }
 fill_in 'S vs C' => 'Name',
         '2012-01-19 08:00:00' => 'starts at',
         '2012-01-20 08:00:00' => 'ends at'
 click 'Create Contest'
 see 'Contest was successfully created'
end
Timecop.travel(2012, 1, 19, 8, 59, 55) do
 act_as(:user) do
  refresh_page
  see 'IS CLOSED'
  sleep 10
  not_see 'IS CLOSED'
  see 'IS OPEN'
 end
end
Timecop.travel(2012, 1, 19, 9, 1, 1) do
 act_as(:user) do
  refresh_page
  %w(ruby ... haml).each do |content|
     fill_in content => 'proposal_content'
     click 'Submit'
  end
 end
act_as(:admin) do
 click 'Sticker proposals',
  'Load more'
  %w(ruby ... html).each do |c|
     within(:proposal_with_content => c) do
     click 'favorite'
   end
 end
end
act_as(:user) do
 refresh_page
 %w(...).each_with_index do |c, r|
   within(:proposal_with_content => c) do
     click "Rate #{r+1} out of 5"
   end
 end
end
act_as(:user) do
 %w(ruby coffescript).each do |c|
  within(:proposal_with_content => c) do
    click 'Comment'
    fill_in "sample text #{c}" => 'body'
    click 'Create comment'
  end
 end
end
Timecop.travel(2012, 1, 20, 11, 10, 10) do
 act_as(:admin) do
  refresh_page
  click 'Load more'
  %w(html python).each do |c|
    within(:proposal_with_content => c) do
      click 'choose'
   end
  end
 end
act_as(:user) do
 refresh_page
 click 'Gallery'
 within(:ordered_list) do
  see *(%w(html python))
  not_see *(%w(ruby ... coffescript))
 end
end
act_as(:admin) do
 click 'Gallery', 'Add new production picture'
 fill_in 'Sample title for picture' => 'Title',
         :attach => {'pict.jpg' => 'Image'}
 click 'Create Gallery image'
end
act_as(:user) do
 click 'Gallery'
 fill_in :select => {'S vs C' => 'contest_id'}
 within(:gallery_list) do
   see :image => 'Thumb_pict'
 end
end
act_as(:admin) do
 click 'Add new contest sticker'
 fill_in 'Sample title for picture' => 'Title',
          :attach => {'pict2.jpg' => 'Image'}
 click 'Create Gallery image'
end
act_as(:user) do
 click 'Gallery'
 fill_in :select => {'S vs C' => 'contest_id'}
 within(:gallery_list) do
   see :image => 'Thumb_pict2'
 end
end
http://2.bp.blogspot.com/_bVtGlUaW-tA/TMOUvmYXzHI/AAAAAAAAP90/SR5yHHWydN4/s1600/Mech_chameleon_by_tommaso_sanguigni.jpg
# gemfile
gem 'kameleon', '>= 0.2.0.alpha.2'
gem 'headless'

# spec_helper.rb
require 'kameleon'
require 'kameleon/ext/rspec/all'
# or
require 'kameleon/ext/rspec/dsl'

# optional
# .../rspec/garbage_collector'
# .../rspec/headless'
SHARED DB CONNECTION




example: lib/kameleon/ext/active_record/

●   shared_single_connection.rb

●   shared_single_connection_with_spork.rb
HEADLESS


RSpec.configure do |config|
 config.before(:suite) do
   @headless = Headless.new(:display => '100')
   @headless.start
 end

 config.after(:suite) do
   @headless.stop if defined?(@headless)
 end
end

require 'kameleon/ext/rspec/headless'
RIPL



   config.after(:each) do
     if exception = example.instance_variable_get(:@exception)


     Ripl.start :binding => binding
     end
   end




example: lib/kameleon/utils/debug_console.rb
FAKE DATA

 IN ACCEPTANCE AND
  INTEGRATION TESTS
DATABASE
FIXTURES
# fernitures.yml
couch:
 name: Couch
 price: 399.99
 manufacturer: lazyboy
 categories: furniture
tv_stand:
 name: TV Stand
 price: 149.95
 manufacturer: highdeph
 categories: furniture, electronics

# categories.yml
furniture:
 name: Furniture
electronics:
 name: Electronics
FIXTURES

--
-- PostgreSQL database dump
--

SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
FACTORIES

10.times do |i|
 banner = BannerMessage.new :content => "text_#{i}"
 banner.origin_ip = "127.#{i+1}.#{i}.#{i}"
 banner.save!
 rand(6).times do |v|
   vote = banner.votes.build
   vote.origin_ip = "127.0.0.#{v}"
   vote.cookies = {vote.banner_message_id.to_s =>
                                           Time.now.to_s}
   vote.save!
 end
end
FACTORIES
FactoryGirl.define do
 factory :comment do
   sequence(:body) { |n| "this is #{n} comment!" }
   banner_message
   sequence(:origin_ip) { |n| "127.0.0.#{n}" }
   user
 end
end

###


10.times { Factory.create(:comment) }
SERVICES
VCR + WEB MOCK



VCR.configure do |c|
 c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
 c.hook_into :webmock
 c.default_cassette_options = { :record => :new_episodes }
end
VCR + WEB MOCK
context "passing authentication process", :status => :done do
  use_vcr_cassette 'authorization_pass'
  before(:each) do
  get '/', nil,
       { 'HTTP_AUTHORIZATION' => {"domain" => "selleo2",
                       "email" => "m.czyz@selleo.com",
                       "token" => "xK1KPKCcP3v6VvTkhDogzI"}.to_json }
  end

  it { response.code.should == "200" }

  it 'message' do
       ...
VCR + WEB MOCK
---
recorded_with: VCR 2.0.0
http_interactions:
- request:
     method: get
     uri: https://someapp.com/api/v1/users/current
     body:
     string: ""
     headers:
     ...
     Authorization:
     - Basic bSnl6QHNlbjb20vdG9rZW46eS1BL
     User-Agent:
     - Some Ruby Client 0.1.0
VCR + WEB MOCK
response:
   status:
   code: 200
   message: OK
   headers:
   Content-Type:
   - application/json; charset=utf-8
   Server:
   - nginx/0.8.53
   ....
   Connection:
   - keep-alive
   body:
   string: "{"name":"Selleo","locale_id":null,"pho":"wow"}"
   http_version:
recorded_at: Mon, 09 Apr 2012 11:32:23 GMT
VCR + WEB MOCK
response:
   status:
   code: 200
   message: OK
   headers:
   Content-Type:
   - application/json; charset=utf-8
   Server:
   - nginx/0.8.53
   ....
   Connection:
   - keep-alive
   body:
   string: "{"name":"Selleo","locale_id":null,"pho":"wow"}"
   http_version:
recorded_at: Mon, 09 Apr 2012 11:32:23 GMT
META-
DATA
context "passing auth", :status => :done do
  use_vcr_cassette 'authorization_pass'
  ...
end
#::: page: sign_in
#::: wireframe: http://some_url

context "passing auth", :status => :done do
  use_vcr_cassette 'authorization_pass'
  ...
end
INTEGRATION
   TESTS
http://blog.jonasbandi.net/2010/09/acceptance-vs-integration-tests.html
INTEGRATION TEST
describe "fill in" do
 before(:each) { visit('/form_elements') }
 context "single value in field" do
  it "text" do
    see :empty => [ 'sampleEmptyInput',
                        'textarea2']
    fill_in    'Value for sampleEmtyInput' => 'sampleEmptyInput',
               'Value for textarea2' => 'textarea2'
    see 'Value for textarea2' => 'textarea2',
           'Value for sampleEmtyInput' => 'sampleEmptyInput'
  end

  it "checkbox" do
    see        :unchecked => 'Sample unchecked checkbox'
    fill_in :check => 'Sample unchecked checkbox'
    see        :checked => 'Sample unchecked checkbox'
    fill_in :uncheck => 'Sample checked checkbox'
    see        :unchecked => 'Sample checked checkbox'
  end
INTEGRATION TEST
describe 'order' do
 before(:each) { visit('/special_elements') }

 it 'should see text in proper order' do
   see :ordered => ['Michal Czyz', 'Tomasz Bak', 'Rafal Bromirski']
 end

 context 'raise error' do
  it 'different order' do
    expect do
          see :ordered => ['Tomasz Bak', 'Michal Czyz', 'Rafal Bromirski']
    end.to raise_error(RSpec::Expectations::ExpectationNotMetError)
  end
  it 'not all elements present' do
    expect do
          see :ordered => ['Michal Czyz', 'Lukas Bak', 'Rafal Bromirski']
    end.to raise_error(RSpec::Expectations::ExpectationNotMetError)
  end
 end
end
INTEGRATION TEST
before(:each) do
 post "/users", {
    :user => {
      :name => "Wookie 5", :email => "wookie5@selleo.com",
      :roles => 4, "is_verified" => true, :groups => [20188052, 20180893]
    }
 }, SelleoSomeApi.headers
end

it { response.status.should == 201 }

subject do
 JSON.parse(response.body)
end

it { subject["id"].should be_a(Fixnum) }
it { should include "email" => "wookie5@selleo.com" }
it { should include "name" => "Wookie 5" }
it { should include "roles" => 4 }
it { should include "is_active" => true }
it { should include "is_verified" => "true" }
UNIT TESTS
UNIT TEST
describe "PARSING" do
 before(:each) do
   subject { CucumberFM::Feature.new('some_path') }
   subject.stub(:raw).and_return(FEATURE_CONTENT)
   cfm = mock(:cfm)
   filter = CucumberFM::TagFilter.new('')
   cfm.stub(:filter).and_return(filter)
   subject.stub(:cfm).and_return(cfm)
 end
 it "should parse feature info" do
   CucumberFM::FE::Info.should_receive(:new).with(subject, INFO_CONTENT)
   subject.info
 end
 it "should parse background" do
   CucumberFM::FE::Background.should_receive(:new).with(subject, BAC..._C)
   subject.background
 end
UNIT TEST
describe CucumberFM::FeatureElement::Info do
 before(:all) do
  raw = <<EOF
    #{@comment_1 = "# some comment"}
    @tag @mc
    Feature: #{@title = "Tag filter"}
EOF
  feature = CucumberFM::Feature.new('fake_path')
  @info = CucumberFM::FeatureElement::Info.new(feature, raw)
 end

 it "should parse tags" do
   @info.tags.should == %w(@tag @mc)
 end

 it "should parse comments lines" do
   CucumberFM::FE::Comment.should_receive(:new).with(@info, @comment_1)
   CucumberFM::FE::Comment.should_receive(:new).with(@info, @comment_2)
   @info.should have(2).comments
 end
UNIT
     vs
INTEGRATION
http://www.codingthearchitecture.com/images/20120412-sketches.jpg
PERFORMANCE
   TESTS
http://2.bp.blogspot.com/-yR4off2JRt0/TzZEOolbm5I/AAAAAAAAB_Q/Sl7UKjVl9Cw/s400/Snail%2Bon%2Bturtle%2Bfast.jpg
http://www.projzilla.com/blog/wp-content/uploads/2011/12/Testing.jpg
LOAD
TESTS
http://www.funtim.com/wp-content/uploads/2009/01/indian-funny-pic.jpg
http://www.projzilla.com/blog/wp-content/uploads/2011/12/Testing.jpg
SECURITY
 TESTS
http://www.penetration-testing-group.co.uk/penetration-testing.jpg
http://www.projzilla.com/blog/wp-content/uploads/2011/12/Testing.jpg
STATIC
ANALYSIS
http://metric-fu.rubyforge.org/reek.gif
CONTINUOUS
INTEGRATION
http://www.jetbrains.com/teamcity/features/index.html
http://beta.travis-ci.com/?lrRef=rk8v2
http://www.worksmartmompreneurs.com/blog/wp-content/uploads/2012/03/url.jpg
● precise requirements definition
● automatic requirements
  verification
● code design
● documentation
● safety net for code changes
● catch errors
LINKS
RSPEC
 ● http://pragprog.com/book/achbd/the-rspec-book
 ● https://www.relishapp.com/rspec
CAPYBARA
 ● https://github.com/jnicklas/capybara
XPATH
 ● http://www.w3.org/TR/xpath/
 ● https://addons.mozilla.org/en-US/firefox/addon/firepath/
SELENIUM
 ● http://seleniumhq.org/docs/
WEBKIT
 ● https://github.com/thoughtbot/capybara-webkit
KAMELEON

●   https://github.com/cs3b/kame   leon
THANKS :-)




questions ?
  http://selleo.com/people/michal-czyz


  http://cs3b.com

[ HackFest.pl 2012] Testing - what for and how