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

Introduction à RSpec

Introduction à RSpec

I presented RSpec at OpenCode #8.

Rémi Prévost

December 18, 2012
Tweet

More Decks by Rémi Prévost

Other Decks in Technology

Transcript

  1. RSpec
    Rémi Prévost — OpenCode #8

    View Slide


  2. RSpec is a
    Behaviour-Driven
    Development tool for
    Ruby programmers

    View Slide

  3. Teste le
    comportement et
    non la finalité des
    objets

    View Slide

  4. Langage naturel,
    écrire le moins de
    texte possible dans
    nos tests

    View Slide

  5. Test::Unit
    vs.
    RSpec
    gem install rspec

    View Slide

  6. require "test/unit"
    class ProductTest < Test::Unit::TestCase
    def test_initial_quantity
    assert_equal 4, Product.new.initial_quantity
    end
    end
    describe Product do
    subject { Product.new }
    its(:initial_quantity) { should eq 4 }
    end

    View Slide

  7. Matchers
    Goodbye asserts!

    View Slide

  8. describe Array do
    subject { [1, 2, 3, 4] }
    # Teste subject.size
    its(:size) { should eq 4 }
    # Teste subject.empty?
    it { should_not be_empty }
    # Teste subject.size, subject.clear et subject.size encore
    it do
    expect { subject.clear }.to change(subject, :size).to(0)
    end
    end

    View Slide

  9. % rspec array_spec.rb
    ...
    Finished in 0.00454 seconds
    3 examples, 0 failures

    View Slide

  10. % rspec array_spec.rb --format documentation --colour
    Array
    should not be empty
    should change size
    size
    should eq 4
    Finished in 0.00316 seconds
    3 examples, 0 failures

    View Slide

  11. describe Array do
    subject { [1, 2, 3, 4] }
    # Teste subject.size
    its(:size) { should eq 3 }
    end

    View Slide

  12. % rspec array_spec.rb --format documentation --colour
    Array
    size
    should eq 3 (FAILED - 1)
    Failures:
    1) Array size
    Failure/Error: its(:size) { should eq 3 }
    expected: 3
    got: 4
    (compared using ==)
    Finished in 0.0009 seconds
    1 example, 1 failure
    Failed examples:
    rspec ./array_spec.rb:7 # Array size

    View Slide

  13. "foo".should eq "foo"
    # "foo" == "foo"
    Product.new.should be_available
    # Product.new.available? == true
    Product.new.should be_instance_of(Product)
    # Product.new.instance_of?(Product)
    "foo".should respond_to(:upcase)
    # "foo".respond_to?(:upcase)
    { :foo => "bar" }.should have_key(:foo)
    # { :foo => "bar" }.has_key?(:foo)
    [1, 2, 3].should have(3).items
    # [1, 2, 3].size == 3

    View Slide

  14. expect { 42/0 }.to raise_error(ZeroDivisionError)
    # &block.call rescue ZeroDivisionError
    @a = [1, 2, 3, 4]
    expect { @a.clear }.to change{ @a.length }.from(4).to(0)
    # &block.call == 4
    # &expectation.call
    # &block.call == 0
    @a = [1, 2, 3, 4]
    expect { @a.clear }.to change(@a, :length).from(4).to(0)
    # @a.length == 4
    # &expectation.call
    # @a.length == 0

    View Slide

  15. Contextes

    View Slide

  16. describe Product do
    context "without supply" do
    subject { Product.new(:quantity_left => 0) }
    it { should be_sold_out }
    end
    context "with supply" do
    subject { Product.new(:quantity_left => 10) }
    it { should_not be_sold_out }
    end
    end

    View Slide

  17. describe Product do
    describe :build do
    context "as admin user" do
    before { @user = User.new(:roles => [:admin]) }
    context "without scope" do
    subject { Product.build(:as => @user) }
    its(:owner) { should eq @user }
    it { should_not be_available }
    end
    context "with available scope" do
    subject { Product.available.build(:as => @user) }
    its(:owner) { should eq @user }
    it { should be_available }
    end
    end
    end
    end

    View Slide

  18. Variables

    View Slide

  19. describe Product do
    describe :similar_products do
    context "without supply" do
    subject { Product.new }
    it "returns the next sold out product" do
    subject.similar_products(10).first.should be_sold_out
    end
    end
    context "with supply" do
    subject { Product.new(:quantity_left => 5) }
    it "returns the next available product" do
    subject.similar_products(10).first.should_not be_sold_out
    end
    end
    end
    end

    View Slide

  20. describe Product do
    describe :similar_products do
    let(:next_product) { subject.similar_products(10).first }
    context "without supply" do
    subject { Product.new }
    specify { next_product.should be_sold_out }
    end
    context "with supply" do
    subject { Product.new(:quantity_left => 5) }
    specify { next_product.should_not be_sold_out }
    end
    end
    end

    View Slide

  21. Rails
    gem install rspec-rails
    gem install shoulda

    View Slide

  22. Modèles

    View Slide

  23. describe Product do
    describe :Associations do
    # TODO
    end
    describe :Validations do
    # TODO
    end
    describe :Callbacks do
    # TODO
    end
    describe :InstanceMethods do
    # TODO
    end
    describe :ClassMethods do
    # TODO
    end
    end

    View Slide

  24. describe Product do
    describe :Associations do
    it { should belong_to(:brand) }
    it { should have_many(:pictures) }
    end
    end
    class Product < ActiveRecord::Base
    belongs_to :brand
    has_many :pictures
    end

    View Slide

  25. describe Product do
    describe :Validations do
    it { should validate_presence_of(:name) }
    it { should ensure_inclusion_of(:state).in(%w{visible hidden}) }
    it { should_not allow_value("_$#!").for(:sku) }
    end
    end
    class Product < ActiveRecord::Base
    validates :name, :presence => true
    validates :state, :inclusion => { :in => %w{visible hidden} }
    validates :sku, :rosette_sku => true
    end

    View Slide

  26. describe Product do
    describe :Callbacks do
    describe :adjust_sold_out_flag do
    subject { Product.new(:sold_out => false) }
    specify do
    subject.quantity_left = 0
    expect {
    subject.save!
    }.to change(subject, :sold_out?).from(false).to(true)
    end
    end
    end
    end
    class Product < ActiveRecord::Base
    before_save :adjust_sold_out_flag, :if => proc {
    self.quantity_left_changed? and self.quantity_left == 0
    }
    end

    View Slide

  27. describe Product do
    describe :Callbacks do
    describe :adjust_sold_out_flag do
    subject { Product.new(:sold_out => false) }
    before { subject.quantity_left = 0 }
    specify do
    subject.should_receive(:adjust_sold_out_flag).once
    subject.save!
    end
    end
    end
    end
    class Product < ActiveRecord::Base
    before_save :adjust_sold_out_flag, :if => proc {
    self.quantity_left_changed? and self.quantity_left == 0
    }
    end

    View Slide

  28. Contrôleurs

    View Slide

  29. describe ProductsController do
    # POST /products
    describe :create do
    end
    # GET /products
    describe :index do
    end
    # PUT /products/:id
    describe :update do
    end
    # GET /products/:id
    describe :show do
    end
    # DELETE /products/:id
    describe :destroy do
    end
    end

    View Slide

  30. describe ProductsController do
    # POST /products
    describe :create do
    before do
    post :create, :product => { :name => "Foo" }
    end
    it { should redirect_to(products_path) }
    it { should set_the_flash[:notice].to("Yay!") }
    end
    end
    class ProductsController < ApplicationController
    def create
    if Product.create(params[:product])
    flash[:notice] = "Yay!"
    redirect_to products_path
    end
    end
    end

    View Slide

  31. class ProductsController < ApplicationController
    def create
    if Product.create(params[:product])
    flash[:notice] = "Yay!"
    redirect_to products_path
    else
    flash[:error] = "Nope!"
    end
    end
    end

    View Slide

  32. describe ProductsController do
    # POST /products
    describe :create do
    let :create_product! { post :create, :product => attrs }
    context "with valid attributes" do
    let(:attrs) { { :name => "Foo" } }
    before { create_product! }
    it { should redirect_to(products_path) }
    it { should set_the_flash[:notice].to("Yay!") }
    end
    context "with invalid attributes" do
    let(:attrs) { { :name => "" } }
    before { create_product! }
    it { should render_template("products/create") }
    it { should set_the_flash[:error].to("Nope!") }
    end
    end
    end

    View Slide

  33. describe ProductsController do
    # GET /products
    describe :index do
    before do
    @product = stub_model(Product)
    Product.stub(:all).returns([@product])
    get :index
    end
    it { should respond_with(:success) }
    it { should render_template("products/index") }
    it { should assign_to(:products).with([@product]) }
    end
    end
    class ProductsController < ApplicationController
    def index
    @products = Product.all
    end
    end

    View Slide

  34. describe "products/index" do
    before do
    assign(:products, [stub_model(Product, :name => "Foo")])
    render
    end
    it { expect(rendered).to include("Foo") }
    end



    Vues

    View Slide

  35. describe "GET /products" do
    it do
    expect(:get => "/products").to route_to(
    :controller => "products",
    :action => "index"
    )
    end
    end
    resources :products
    Routes

    View Slide

  36. describe ProductsHelper do
    describe :format_price do
    subject do
    helper.format_price(stub_model(Product, :price => 1200))
    end
    it { should eq "$12.00" }
    end
    end
    module ProductsHelper
    def format_price(product)
    Money.new(product.price).format
    end
    end
    Helpers

    View Slide

  37. Merci!
    Questions? Commentaires?

    View Slide