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.

99e0b39c091e10d9c7d4452a34ca52dc?s=128

Simone Carletti

June 20, 2011
Tweet

Transcript

  1. Simone Carletti simone@carletti.name twitter: @weppos Testing with RSpec 2011 Monday,

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

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

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

  5. Monday, June 20, 2011

  6. Monday, June 20, 2011

  7. Testing Monday, June 20, 2011

  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
  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
  10. Monday, June 20, 2011

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

  12. Monday, June 20, 2011

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

  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("weppos@weppos.net") }.to raise_error(Whois::ServerNotSupported) end end end Monday, June 20, 2011
  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("weppos@weppos.net") }.to raise_error(Whois::ServerNotSupported) end end end Monday, June 20, 2011
  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
  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
  18. describe RSpec, "Syntax" Monday, June 20, 2011

  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  35. describe RSpec, "Integration" Monday, June 20, 2011

  36. RSpec Monday, June 20, 2011

  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
  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
  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
  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
  41. describe RSpec, "Best Practices" Monday, June 20, 2011

  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
  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
  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
  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
  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
  47. spec_helper.rb & /support Monday, June 20, 2011

  48. Demo Time Monday, June 20, 2011

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

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

  51. Thanks! Monday, June 20, 2011