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

Testing with RSpec (RubyDay 2011)

Testing with RSpec (RubyDay 2011)

Presented at RubyDay 2011

Il testing è da sempre uno dei pilastri dello sviluppo in Ruby. Ruby è stato uno dei primi linguaggi ad avere un framework completo per lo unit testing direttamente all'interno della standard library.
Tuttavia, questo non ha impedito la nascita di nuovi strumenti e librerie per il testing e continuous integration.

Questo talk prende in esame RSpec, una delle alternative più mature e diffuse a Test::Unit, la libreria di testing distribuita nella Ruby Standard Library.
RSpec è una libreria elegante e completa, in grado di combinare l'importanza di scrivere test "leggibili" con l'obiettivo fondamentale di riprodurre e validare al meglio l'efficacia del nostro programma.
Vedremo come usare al meglio RSpec, quali sono i vantaggi rispetto a Test::Unit e come scrivere test in modo efficace ed efficiente.

Non mancheranno accenni al testing in generale, altre librerie come Cucumber, Shoulda, Mocha, RR e all'uso di RSpec con Rails ed altri framework Ruby.

Simone Carletti

June 20, 2011
Tweet

More Decks by Simone Carletti

Other Decks in Programming

Transcript

  1. Simone Carletti
    [email protected]
    twitter: @weppos
    Testing with RSpec
    2011
    Monday, June 20, 2011

    View Slide

  2. Me.about(:robodomain)
    Monday, June 20, 2011

    View Slide

  3. Me.about(:ifad)
    Monday, June 20, 2011

    View Slide

  4. describe RSpec, "Agenda"
    Monday, June 20, 2011

    View Slide

  5. Monday, June 20, 2011

    View Slide

  6. Monday, June 20, 2011

    View Slide

  7. Testing
    Monday, June 20, 2011

    View Slide

  8. TDD seems to be applicable in various domains and can
    significantly reduce the defect density of developed
    software without significant productivity reduction of the
    development team.
    http://www.infoq.com/news/2009/03/TDD-Improves-
    Quality
    Monday, June 20, 2011

    View Slide

  9. The pre-release defect density of the four products,
    measured as defects per thousand lines of code,
    decreased between 40% and 90% relative to the
    projects that did not use TDD. The teams' management
    reported subjectively a 15–35% increase in initial
    development time for the teams using TDD, though the
    teams agreed that this was offset by reduced
    maintenance costs.
    Monday, June 20, 2011

    View Slide

  10. Monday, June 20, 2011

    View Slide

  11. $ rspec
    http://relishapp.com/rspec
    https://github.com/rspec
    Monday, June 20, 2011

    View Slide

  12. Monday, June 20, 2011

    View Slide

  13. describe RSpec, "Features"
    Monday, June 20, 2011

    View Slide

  14. Readability
    describe Whois::Client do
    describe "#initialize" do
    it "accepts a timeout setting with a value in seconds" do
    klass.new(:timeout => 100).timeout.should == 100
    end
    it "accepts a timeout setting with a nil value" do
    klass.new(:timeout => nil).timeout.should be_nil
    end
    end
    describe "#query" do
    it "detects email" do
    expect {
    klass.new.query("[email protected]")
    }.to raise_error(Whois::ServerNotSupported)
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  15. Readability
    describe Whois::Client do
    describe "#initialize" do
    it "accepts a timeout setting with a value in seconds" do
    klass.new(:timeout => 100).timeout.should == 100
    end
    it "accepts a timeout setting with a nil value" do
    klass.new(:timeout => nil).timeout.should be_nil
    end
    end
    describe "#query" do
    it "detects email" do
    expect {
    klass.new.query("[email protected]")
    }.to raise_error(Whois::ServerNotSupported)
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  16. Isolation
    describe UsersController do
    context "when logged as administrator" do
    before(:each) do
    log_in_as @admin = Factory(:user, :username => "admin")
    end
    context "with a bunch of users" do
    before(:each) { @users = 3.times.map { Factory(:user) } }
    describe "GET index"
    end
    context "with no users" do
    describe "GET new"
    describe "POST create"
    end
    describe "with a user" do
    before(:each) { @user = Factory(:user) }
    describe "GET show"
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  17. Extensible
    describe 9 do
    it "should be a multiple of 3" do
    (9 % 3).should == 0
    end
    end
    # vs
    RSpec::Matchers.define :be_a_multiple_of do |expected|
    match do |actual|
    actual % expected == 0
    end
    end
    describe 9 do
    it "should be a multiple of 3" do
    9.should be_a_multiple_of(3)
    end
    end
    Monday, June 20, 2011

    View Slide

  18. describe RSpec, "Syntax"
    Monday, June 20, 2011

    View Slide

  19. Basic Example
    describe String do
    describe "#upcase" do
    it "returns the original string converted to uppercase" do
    "hello".upcase.should == "HELLO"
    end
    it "returns a String" do
    "hello".upcase.should be_a(String)
    end
    it "does not change the original string" do
    string = "hello"
    string.upcase
    string.should_not == "HELLO"
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  20. Basic Example
    describe String do
    describe "#upcase" do
    it "returns the original string converted to uppercase" do
    "hello".upcase.should == "HELLO"
    end
    it "returns a String" do
    "hello".upcase.should be_a(String)
    end
    it "does not change the original string" do
    string = "hello"
    string.upcase
    string.should_not == "HELLO"
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  21. context / describe
    describe "something" do
    context "in one context" do
    it "does one thing" do
    end
    end
    context "in another context" do
    it "does another thing" do
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  22. before / after
    describe "before / after" do
    before(:each) {}
    before(:all) {}
    before(:suite) {}
    after(:each) {}
    after(:all) {}
    after(:suite) {}
    end
    # Before and after blocks are called in the following order:
    #
    # before suite
    # before all
    # before each
    # after each
    # after all
    # after suite
    Monday, June 20, 2011

    View Slide

  23. before / after
    describe "before / after" do
    before(:each) do
    @hash = {}
    end
    it "does something" do
    @hash[:key] = "value"
    @hash[:key].should_not be_empty
    end
    end
    Monday, June 20, 2011

    View Slide

  24. let
    $count = 0
    describe "let" do
    let(:count) { $count += 1 }
    it "memoizes the value" do
    count.should == 1
    count.should == 1
    end
    it "is not cached across examples" do
    count.should == 2
    end
    end
    Monday, June 20, 2011

    View Slide

  25. Implicit subject
    describe Array do
    describe "when first created" do
    it "should be empty" do
    subject.should eq([])
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  26. subject
    describe Array do
    subject { [1,2,3] }
    describe "with some elements" do
    it "should have the prescribed elements" do
    subject.should == [1,2,3]
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  27. expect
    describe Integer do
    describe "#/" do
    it "raises ZeroDivisionError when divisor is 0" do
    lambda do
    1 / 0
    end.should raise_error(ZeroDivisionError)
    end
    end
    end
    # vs
    describe Integer do
    describe "#/" do
    it "raises ZeroDivisionError when divisor is 0" do
    expect {
    1 / 0
    }.to raise_error(ZeroDivisionError)
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  28. Shared Behaviors
    shared_examples "a collection" do
    let(:collection) { described_class.new([7, 2, 4]) }
    context "initialized with 3 items" do
    it "says it has three items" do
    collection.size.should eq(3)
    end
    end
    describe "#include?" do
    context "with an an item that is in the collection" do
    it "returns true" do
    collection.include?(7).should be_true
    end
    end
    context "with an an item that is not in the collection" do
    it "returns false" do
    collection.include?(9).should be_false
    end
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  29. Shared Behaviors
    describe Array do
    it_behaves_like "a collection"
    end
    describe Set do
    it_behaves_like "a collection"
    end
    Monday, June 20, 2011

    View Slide

  30. pending
    describe Hash do
    it "adds a key" do
    { :key => "value" }[:key].should == "value"
    end
    it "does something unknown"
    it "does something buggy" do
    pending "This method is buggy"
    end
    end
    describe Integer do
    before(:each) { pending }
    it "computes 1 + 1" do
    (1 + 1).should == 2
    end
    it "computes 1 - 1" do
    (1 - 1).should == 0
    end
    end
    Monday, June 20, 2011

    View Slide

  31. pending
    describe Hash do
    it "adds a key" do
    { :key => "value" }[:key].should == "value"
    end
    it "does something unknown"
    it "does something buggy" do
    pending "This method is buggy"
    end
    end
    describe Integer do
    before(:each) { pending }
    it "computes 1 + 1" do
    (1 + 1).should == 2
    end
    it "computes 1 - 1" do
    (1 - 1).should == 0
    end
    end
    Monday, June 20, 2011

    View Slide

  32. pending
    describe "an example" do
    xit "is pending using xit" do
    true.should be(true)
    end
    end
    describe "an example" do
    it "checks something" do
    (3+4).should == 7
    end
    pending do
    "string".reverse.should == "gnirts"
    end
    end
    Monday, June 20, 2011

    View Slide

  33. Metadata
    describe "something", :this => { :is => :arbitrary } do
    it "does something", :and => "so is this" do
    # ...
    end
    end
    RSpec.configure do |c|
    c.filter_run_excluding :ruby => lambda {|version|
    !(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
    }
    end
    describe "something" do
    it "does something", :ruby => 1.8 do
    # ....
    end
    it "does something", :ruby => 1.9 do
    # ....
    end
    end
    Monday, June 20, 2011

    View Slide

  34. Metadata
    RSpec.configure do |c|
    c.filter_run :focus => true
    end
    describe "something", :focus => true do
    it "does something" do
    # ....
    end
    end
    RSpec.configure do |c|
    c.filter_run_excluding :slow => true
    end
    describe "something", :slow => true do
    it "does something" do
    # ....
    end
    end
    Monday, June 20, 2011

    View Slide

  35. describe RSpec, "Integration"
    Monday, June 20, 2011

    View Slide

  36. RSpec
    Monday, June 20, 2011

    View Slide

  37. describe AccountsController do
    describe "GET show" do
    before(:each) do
    get :show
    end
    it { should respond_with :success }
    it { should render_template "accounts/show" }
    it "assigns user" do
    assigns(:user).should == @user
    end
    end
    end
    RSpec
    # Gemfile
    group :development, :test do
    gem 'rspec-rails', '~> 2.6.0'
    end
    spec folder
    Monday, June 20, 2011

    View Slide

  38. RSpec
    RSpec.configure do |config|
    config.include Rack::Test::Methods
    end
    describe Tremonti::Application do
    def app
    Tremonti::Application
    end
    describe "/" do
    it "should respond with 200" do
    get "/"
    last_response.should be_ok
    last_response.body.should == "Hello from RoboFinance!"
    end
    end
    end
    rack-test
    Monday, June 20, 2011

    View Slide

  39. RSpec Mocha
    RR
    RSpec.configure do |config|
    # == Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr
    end
    Monday, June 20, 2011

    View Slide

  40. RSpec Factories
    describe BillingCharge do
    subject { Factory.build(:billing_charge) }
    describe "associations" do
    it { should belong_to(:user) }
    end
    describe "validations" do
    it "is valid with factory" do
    Factory.build(:billing_charge).should be_valid
    end
    it { should validate_presence_of(:user_id) }
    it { should validate_format_of(:period).with('2010-02') }
    it { should validate_format_of(:period).not_with('201002') }
    it { should validate_format_of(:period).not_with('2010') }
    end
    end
    Monday, June 20, 2011

    View Slide

  41. describe RSpec, "Best Practices"
    Monday, June 20, 2011

    View Slide

  42. describe vs context
    describe ".guess" do
    it "recognizes tld" do
    s = Whois::Server.guess(".com")
    s.should be_a(Whois::Server::Adapters::Base)
    s.type.should == :tld
    end
    context "when the input is a tld" do
    it "returns a IANA adapter" do
    Whois::Server.guess(".com").should ==
    Whois::Server.factory(:tld, ".", "whois.iana.org")
    end
    end
    context "when the input is a domain" do
    it "lookups definitions and returns the adapter" do
    Whois::Server.guess("example.test").should ==
    Whois::Server.factory(:tld, ".test", "whois.test")
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  43. it vs specify
    describe User do
    before do
    @user = User.new :username => 'weppos'
    end
    it "is not an administrator" do
    @user.should_not be_admin
    end
    end
    # vs.
    describe User do
    let(:user) do
    User.new :username => 'weppos'
    end
    specify do
    user.should_not be_admin
    end
    end
    Monday, June 20, 2011

    View Slide

  44. should
    describe "#order_price" do
    it "should be a Money" do
    # ...
    end
    it "should return nil when amount is blank" do
    # ...
    end
    end
    # vs
    describe "#order_price" do
    it "is a Money" do
    # ...
    end
    it "returns nil when amount is blank" do
    # ...
    end
    end
    Monday, June 20, 2011

    View Slide

  45. context vs it
    describe Hash, "#size" do
    it "returns 0 when the Hash is empty" do
    end
    it "returns the number of elements when the Hash is not empty" do
    end
    end
    # vs
    describe Hash, "#size" do
    context "when the Hash is empty" do
    it "returns 0" do
    end
    end
    context "when the Hash is not empty" do
    it "returns the number of elements" do
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  46. Instance vs Class methods
    describe Time do
    describe ".parse" do
    it "converts given string into a Time" do
    Time.parse('2010-06-12').should == Time.mktime(2010, 06, 12)
    end
    end
    describe "#to_i" do
    it "returns the timestamp corresponding to the Time object" do
    t = Time.mktime(2010, 06, 12)
    t.to_i.should == 1276293600
    end
    end
    end
    Monday, June 20, 2011

    View Slide

  47. spec_helper.rb & /support
    Monday, June 20, 2011

    View Slide

  48. Demo Time
    Monday, June 20, 2011

    View Slide

  49. Choose the right tool...
    Monday, June 20, 2011

    View Slide

  50. ... and Test!
    Monday, June 20, 2011

    View Slide

  51. Thanks!
    Monday, June 20, 2011

    View Slide