testing rails: model vs integration

51
Testing? Why not! David Librera ([email protected]) Cantiere Creativo 24/02/2016 || (Cantiere Creativo) RSpec 24/02/2016 1 / 43

Upload: david-librera

Post on 07-Jan-2017

434 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Testing Rails: Model vs Integration

Testing? Why not!

David Librera ([email protected])

Cantiere Creativo

24/02/2016

|| (Cantiere Creativo) RSpec 24/02/2016 1 / 43

Page 2: Testing Rails: Model vs Integration

Quando scriviamo un’applicazione Rails, ci troviamo spesso in un ecosistema moltocomplesso e strutturato di directories e files

Models (ActiveRecord::Base)Controllers (ActionController::Base)Views (ActionView::Base)Presenters (Showcase::Presenter)Queries (Admino::Query::Base)Jobs ( ActiveJob::Base )Inputs ( SimpleForm::Inputs )...

|| (Cantiere Creativo) RSpec 24/02/2016 2 / 43

Page 3: Testing Rails: Model vs Integration

Vediamo velocemente a cosa servono i vari tipi dioggetti

|| (Cantiere Creativo) RSpec 24/02/2016 3 / 43

Page 4: Testing Rails: Model vs Integration

Models

I model hanno la responsabilità di gestite la persistenza dei dati a livello di DB

app/models/user.rb

class User < ActiveRecordhas_one :account

def last_loginend

end

|| (Cantiere Creativo) RSpec 24/02/2016 4 / 43

Page 5: Testing Rails: Model vs Integration

Controllers

I controller eseguono un’azione a livello di server e restituiscono una risposta al client

app/controllers/home_controller.rb

class HomeController < ApplicationControllerdef index

render template: ’home/index’end

end

|| (Cantiere Creativo) RSpec 24/02/2016 5 / 43

Page 6: Testing Rails: Model vs Integration

Views

Le views sono la definizione di come l’utente deve visualizzare la risposta fornita

app/views/home/index.html.erb

<h2>Wonderful world </h2><p>Sono le <%= Time.now.to_s %></p>

|| (Cantiere Creativo) RSpec 24/02/2016 6 / 43

Page 7: Testing Rails: Model vs Integration

Presenters

I presenter sono dei SimpleDelegator che rendono ’presentabili’ all’utente i dati di un oggetto

app/presenters/user_presenter.rb

class UserPresenter < Showcase :: Presenterdef full_name

[object.first_name , object.last_name ]. compact.join(‘ ‘)end

end

app/views/users/show.html.erb

<%- user = present(User.first)<span>Nome completo : <%= user.full_name %></span>

|| (Cantiere Creativo) RSpec 24/02/2016 7 / 43

Page 8: Testing Rails: Model vs Integration

Queries

I query object sono classi il cui compito è quello di definire delle query complesse sui model

app/queries/users_query.rb

class UsersQuery < Admino ::Query ::Basestarting_scope { Users.all }

def self.complex_query(attr)chain = User.all...chain

endend

|| (Cantiere Creativo) RSpec 24/02/2016 8 / 43

Page 9: Testing Rails: Model vs Integration

Jobs

I jobs sono operazioni complesse che effettuamo macro operazioni sui dati

app/jobs/remove_user_account.rb

class RemoveUserAccount < ActiveJob ::Basedef perform(args)

u = User.find(args[: user_id ])u.account = nilUserMailer.dropped_account_mailer(u). deliver_now

endend

somewhere_in_code

...RemoveUserAccount.new(user.id). perform_now...

|| (Cantiere Creativo) RSpec 24/02/2016 9 / 43

Page 10: Testing Rails: Model vs Integration

Supponiamo adesso di voler testare quanto più possibile ilCRUD di una risorsa abbastanza complessa.

Prenderemo in esame un modello che rappresenta unimmobile in vendita.

|| (Cantiere Creativo) RSpec 24/02/2016 10 / 43

Page 11: Testing Rails: Model vs Integration

Immobile

create_table "estate_proposals", force: :cascade do |t|t.integer "operator_id"t.integer "client_id"t.integer "zone_id"t.integer "signaler_id"t.integer "operation"t.integer "building_type_id"t.integer "rooms"t.integer "bedrooms"t.string "address"t.text "notes"t.integer "building_status"t.string "estimated_restructuring"t.integer "warm_up"t.integer "floor"t.integer "total_floors"t.integer "spending_condominium"t.integer "condominiums_number"t.integer "surface"t.boolean "garden", default: falset.integer "garden_surface"t.boolean "garage", default: false

end

|| (Cantiere Creativo) RSpec 24/02/2016 11 / 43

Page 12: Testing Rails: Model vs Integration

E mi sono trattenuto!!!!!!

|| (Cantiere Creativo) RSpec 24/02/2016 12 / 43

Page 13: Testing Rails: Model vs Integration

Possiamo anche pensare di spezzare la tabella inpiù tabelle e usare una direttiva has_one

:submodel per alleggerire la tabella.

Ciò non toglie che abbiamo molti campi da testare!

|| (Cantiere Creativo) RSpec 24/02/2016 13 / 43

Page 14: Testing Rails: Model vs Integration

Possiamo anche pensare di spezzare la tabella inpiù tabelle e usare una direttiva has_one

:submodel per alleggerire la tabella.

Ciò non toglie che abbiamo molti campi da testare!

|| (Cantiere Creativo) RSpec 24/02/2016 13 / 43

Page 15: Testing Rails: Model vs Integration

Se non troviamo un buon modo di organizzare i test, il doveraggiungere campi ad un model può diventare un processo lungo,

noioso e frustrante.

Nella peggiore delle ipotesi (che poi è quella che si verifica più spesso)il programmatore smette semplicemente di scrivere i test, soprattutto

durante la fase di release del progetto

|| (Cantiere Creativo) RSpec 24/02/2016 14 / 43

Page 16: Testing Rails: Model vs Integration

Se non troviamo un buon modo di organizzare i test, il doveraggiungere campi ad un model può diventare un processo lungo,

noioso e frustrante.

Nella peggiore delle ipotesi (che poi è quella che si verifica più spesso)il programmatore smette semplicemente di scrivere i test, soprattutto

durante la fase di release del progetto

|| (Cantiere Creativo) RSpec 24/02/2016 14 / 43

Page 17: Testing Rails: Model vs Integration

Facciamo un esempio pratico di quanto detto

|| (Cantiere Creativo) RSpec 24/02/2016 15 / 43

Page 18: Testing Rails: Model vs Integration

Partiamo dai feature test

|| (Cantiere Creativo) RSpec 24/02/2016 16 / 43

Page 19: Testing Rails: Model vs Integration

C’è una corrente di pensiero per cui i soli test diintegrazione possono bastare!

|| (Cantiere Creativo) RSpec 24/02/2016 17 / 43

Page 20: Testing Rails: Model vs Integration

Ma possiamo sempre fare i soli test di integrazione?

Pensando al model di prima, proviamo a testare il form dicreazione

Che scenari possiamo immaginare?

|| (Cantiere Creativo) RSpec 24/02/2016 18 / 43

Page 21: Testing Rails: Model vs Integration

Ma possiamo sempre fare i soli test di integrazione?

Pensando al model di prima, proviamo a testare il form dicreazione

Che scenari possiamo immaginare?

|| (Cantiere Creativo) RSpec 24/02/2016 18 / 43

Page 22: Testing Rails: Model vs Integration

Ma possiamo sempre fare i soli test di integrazione?

Pensando al model di prima, proviamo a testare il form dicreazione

Che scenari possiamo immaginare?

|| (Cantiere Creativo) RSpec 24/02/2016 18 / 43

Page 23: Testing Rails: Model vs Integration

spec/features/manage_estate_proposals_spec.rb

require ’rails_helper ’

RSpec.feature ‘Managing estate proposals ‘, type: :feature dodescribe ‘Creating an entry ‘ do

given (: new_page) { Pages :: EstateProposals ::New.new }describe ‘field xxx ‘ do

scenario ‘with a valid value ‘ donew_page.loadnew_page.xxx_field.set ‘valid ‘

new_page.submit!

expect(new_page ).to have_noticeend

scenario ‘with an invalid value ‘ donew_page.loadnew_page.xxx_field.set ‘invalid ‘

new_page.submit!

expect(new_page ).to have_alertend

endend

end

|| (Cantiere Creativo) RSpec 24/02/2016 19 / 43

Page 24: Testing Rails: Model vs Integration

Ecco, se ora volessimo, con le sole features, testare i varifield del nostro model, scriveremo qualcosa lungo come laDivina Commedia, portandoci dietro tutti i problemi di

manutenzione dei file lunghi.

|| (Cantiere Creativo) RSpec 24/02/2016 20 / 43

Page 25: Testing Rails: Model vs Integration

|| (Cantiere Creativo) RSpec 24/02/2016 21 / 43

Page 26: Testing Rails: Model vs Integration

Se ci fermiano un attimo a pensare, il nostro controllerdovrebbe avere, nella stragrande maggioranza dei casi,

questo aspetto

app/controllers/estate_proposals_controller.rb

class EstateProposalsController < ApplicationControllerdef create

@resource = EstateProposal.create(permitted_params)respond_with @resource

endend

|| (Cantiere Creativo) RSpec 24/02/2016 22 / 43

Page 27: Testing Rails: Model vs Integration

Il controller può solo completare con successo ofallire, quindi il test di integrazione dovrebbe

considerare solo questi 2 scenari

|| (Cantiere Creativo) RSpec 24/02/2016 23 / 43

Page 28: Testing Rails: Model vs Integration

O bene bene, o male male.

Non ci interessa sapere come si comporta (a livello utente)ogni singolo campo.

Usiamo strumenti come SimpleForm, che sappiamo(speriamo!) segnalano correttamente i campi errati, quindi a

noi basta solo che la risposta sia Verde o Rossa

|| (Cantiere Creativo) RSpec 24/02/2016 24 / 43

Page 29: Testing Rails: Model vs Integration

O bene bene, o male male.

Non ci interessa sapere come si comporta (a livello utente)ogni singolo campo.

Usiamo strumenti come SimpleForm, che sappiamo(speriamo!) segnalano correttamente i campi errati, quindi a

noi basta solo che la risposta sia Verde o Rossa

|| (Cantiere Creativo) RSpec 24/02/2016 24 / 43

Page 30: Testing Rails: Model vs Integration

spec/features/manage_estate_proposals_spec.rb

require ’rails_helper ’

RSpec.feature ‘Managing estate proposals ‘, type: :feature dodescribe ‘Creating an entry ‘ do

given (: new_page) { Pages :: EstateProposal ::New.new }

scenario ‘with valid values ‘ donew_page.loadnew_page.field1_field.set ‘valid ‘new_page.field2_field.set ‘valid ‘...new_page.fieldn_field.set ‘valid ‘new_page.submit!

expect(new_page ).to have_noticeend

scenario ‘with invalid values ‘ donew_page.loadnew_page.submit!

expect(new_page ).to have_alertend

endend

|| (Cantiere Creativo) RSpec 24/02/2016 25 / 43

Page 31: Testing Rails: Model vs Integration

Adesso deleghiamo ai test sui model, appoggiandoci alibrerie come soulda-matcher, il compito di testare la validità

di ogni singolo campo

|| (Cantiere Creativo) RSpec 24/02/2016 26 / 43

Page 32: Testing Rails: Model vs Integration

spec/models/estate_proposal_spec.rb

require ’rails_helper ’

RSpec.describe EstateProposal , type: :model doit { is_expected.to allow_value(’xxx’).for(:field) }it { is_expected.to validate_presence_of (: another_field) }...

end

|| (Cantiere Creativo) RSpec 24/02/2016 27 / 43

Page 33: Testing Rails: Model vs Integration

Con questa scomposizione, con molta probabilità lemodifiche al controller, e relativamente al test di

integrazione, non saranno più necessarie.

Eventuali modifiche alla logica avranno luogo quasiesclusivamente sul model, che è un file abbastanza snello da

leggere, non facendo più scappare il programmatore.

|| (Cantiere Creativo) RSpec 24/02/2016 28 / 43

Page 34: Testing Rails: Model vs Integration

Con questa scomposizione, con molta probabilità lemodifiche al controller, e relativamente al test di

integrazione, non saranno più necessarie.

Eventuali modifiche alla logica avranno luogo quasiesclusivamente sul model, che è un file abbastanza snello da

leggere, non facendo più scappare il programmatore.

|| (Cantiere Creativo) RSpec 24/02/2016 28 / 43

Page 35: Testing Rails: Model vs Integration

Proviamo adesso ad astrarre la logica dei test di integrazionein modo da semplificare anche la scrittura delle features

|| (Cantiere Creativo) RSpec 24/02/2016 29 / 43

Page 36: Testing Rails: Model vs Integration

spec/support/shared_examples/resource_you_can_create.rb

RSpec.shared_example ‘a resource you can create ‘ dodescribe ‘Creating an entry ‘ do

scenario ‘with valid values ‘ donew_page.loadfields.each do |field , value|

new_page.send("#{field.to_s}_field").set valueendnew_page.submit!

expect(new_page ).to have_noticeend

scenario ‘with invalid values ‘ donew_page.loadnew_page.submit!

expect(new_page ).to have_alertend

endend

|| (Cantiere Creativo) RSpec 24/02/2016 30 / 43

Page 37: Testing Rails: Model vs Integration

Adesso l’aspetto del nostro test sarà questo

spec/features/manage_estate_proposals_spec.rb

require ’rails_helper ’

RSpec.feature ‘Managing estate proposals ‘, type: :feature doit_behaves_like ‘a resource you can create ‘

given (: new_page) { Pages :: EstateProposal ::New.new }given (: fields) do

{field1: ’value1 ’,field2: ’value2 ’,field3: ’value3 ’

}end

endend

|| (Cantiere Creativo) RSpec 24/02/2016 31 / 43

Page 38: Testing Rails: Model vs Integration

Altro aspetto dei CRUD: i filtri sulle pagine index!

Vediamo un caso d’uso

|| (Cantiere Creativo) RSpec 24/02/2016 32 / 43

Page 39: Testing Rails: Model vs Integration

Altro aspetto dei CRUD: i filtri sulle pagine index!

Vediamo un caso d’uso

|| (Cantiere Creativo) RSpec 24/02/2016 32 / 43

Page 40: Testing Rails: Model vs Integration

|| (Cantiere Creativo) RSpec 24/02/2016 33 / 43

Page 41: Testing Rails: Model vs Integration

Come per il form di creazione, noi voglimoassicurarci che tutti quei campi funzionino.

Ma è compito del test di integrazione?

|| (Cantiere Creativo) RSpec 24/02/2016 34 / 43

Page 42: Testing Rails: Model vs Integration

Come per il form di creazione, noi voglimoassicurarci che tutti quei campi funzionino.

Ma è compito del test di integrazione?

|| (Cantiere Creativo) RSpec 24/02/2016 34 / 43

Page 43: Testing Rails: Model vs Integration

NOOOO!!

|| (Cantiere Creativo) RSpec 24/02/2016 35 / 43

Page 44: Testing Rails: Model vs Integration

Come prima, facendo una considerazione,dobbiamo cercare di capire quali sono le azioni

basiliari e i loro esiti.

|| (Cantiere Creativo) RSpec 24/02/2016 36 / 43

Page 45: Testing Rails: Model vs Integration

In questo caso noi possiamoFare una ricerca senza inserire alcun campo ottenendo tutti irecordsFare una ricerca inserendo TUTTI i campi ottenendo degli specificirecordsFare una ricerca inserendo TUTTI i campi senza ottenere records

Ovviamente stiamo dando per scontato che la concatenazione delleclausole where funzioni, ma stiamo usando Rails, no?

|| (Cantiere Creativo) RSpec 24/02/2016 37 / 43

Page 46: Testing Rails: Model vs Integration

spec/features/as_user/manage_estate_proposals_spec.rb

...describe ‘When I filter for estate_proposals ‘ do

given (: index_page) { Pages:: EstateProposals ::Index.new }given (: record) { create (: estate_proposal , :with_all_fields) }

before { index_page.load }

scenario ‘withour any search field I find the record ‘ doexpect(index_page ).to have_record(record)

end

scenario ‘with existing values I find the record ‘ do# here i fill the form

expect(index_page ).to have_record(record)end

scenario ‘with not existing values I do not find any record ‘ do#here I fill the form

expect(index_page ). to_not have_record(record)end

end

|| (Cantiere Creativo) RSpec 24/02/2016 38 / 43

Page 47: Testing Rails: Model vs Integration

app/controllers/estate_proposals_controller

class EstateController < ApplicationControllerdef index

@query = EstateProposalsQuery.new(params)@collection = @query.scoperespond_with @collection

endend

|| (Cantiere Creativo) RSpec 24/02/2016 39 / 43

Page 48: Testing Rails: Model vs Integration

Come prima, non è necessario scendere in maggioredettaglio

Il compito di assicurarsi che, inseriti i vari campi, ilrecord venga trovato, è del model.

|| (Cantiere Creativo) RSpec 24/02/2016 40 / 43

Page 49: Testing Rails: Model vs Integration

spec/queries/estate_proposals_query_spec.rb

describe ".title_matches" dolet(: with_a_valid_title) do

create (: estate_proposal , title: ‘A title ‘)endlet(: result) { EstateProposalsQuery.new(query: query ). scope }let(:query) { { title_matches: title } }

context ‘with a matching value ‘ dolet(:title) { "title" }it ‘retrieves the record ‘ do

expect(result ).to match_array [with_a_valid_title]end

end

context ‘without a matching value ‘ dolet(:title) { "foobar" }it ‘does not retrieve the record ‘ do

expect(result ).to match_array [with_a_valid_title]end

end

|| (Cantiere Creativo) RSpec 24/02/2016 41 / 43

Page 50: Testing Rails: Model vs Integration

app/models/estate_proposal.rb

class EstateProposal < ActiveRecord ::Basescope :title_matches , ->(text) do

where( at[:title]. matches("%#{ text}%") ) }end

def self.atself.arel_table

endend

|| (Cantiere Creativo) RSpec 24/02/2016 42 / 43

Page 51: Testing Rails: Model vs Integration

|| (Cantiere Creativo) RSpec 24/02/2016 43 / 43