Slide 1

Slide 1 text

RSpec Rémi Prévost — OpenCode #8

Slide 2

Slide 2 text

“ RSpec is a Behaviour-Driven Development tool for Ruby programmers ”

Slide 3

Slide 3 text

Teste le comportement et non la finalité des objets

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Test::Unit vs. RSpec gem install rspec

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Matchers Goodbye asserts!

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

% 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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

% 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

Slide 13

Slide 13 text

"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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Contextes

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Variables

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Rails gem install rspec-rails gem install shoulda

Slide 22

Slide 22 text

Modèles

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Contrôleurs

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

<%= product.name %>

<% end %> Vues

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Merci! Questions? Commentaires?