Test RSpec per principianti, parte 2

Il secondo articolo di questa breve serie ti insegna come utilizzare vari abbinamenti che vengono con RSpec. Ti mostra anche come suddividere la tua suite di test tramite tagging, come funzionano i callback e come estrarre alcuni dati. Espandiamo un po 'il kit base di sopravvivenza dal primo articolo e ti dimostriamo abbastanza pericoloso da non avere troppa corda per impiccarti.

Temi

  • matchers
  • Permettere
  • Soggetti
  • callback
  • generatori
  • tag

Nel primo articolo abbiamo passato parecchio tempo cercando di rispondere al "perché?" Dei test. Suggerisco di tornare subito al "come?" E di risparmiarci un altro contesto. Abbiamo già ampiamente coperto questa parte. Vediamo cos'altro RSpec ha da offrire che tu come principiante puoi gestire subito.

matchers

Quindi questo si avvicina al cuore delle cose. RSpec ti offre una tonnellata di cosiddetti matchers. Questi sono il tuo pane e burro quando scrivi le tue aspettative. Finora hai visto .per eq e .non_a eq. Ma c'è un arsenale molto più grande per scrivere le tue specifiche. Puoi testare gli errori, i valori di verità e falsi o anche per classi specifiche. Esaminiamo alcune opzioni per iniziare:

  • .per eq
  • .non_a eq 

Questo test per l'equivalenza.

Alcune specifiche

... si aspetta una 'descrizione intelligente' (agent.enemy). Eq 'Ernst Stavro Blofeld' si aspetta (agent.enemy) .non-eq 'Winnie Pooh' fine ... 

Attenzione!

Per farla breve, ho preparato due dichiarazioni di aspettativa all'interno di una esso bloccare. È buona pratica, tuttavia, testare solo una singola cosa per test. Ciò mantiene le cose molto più focalizzate, e i tuoi test finiranno meno fragili quando cambierai le cose.

  • .essere_truthy
  • .per essere vero

Alcune specifiche

... è 'una descrizione intelligente' aspettarsi (agent.hero?). Di essere_truthy expect (enemy.megalomaniac?). Essere vero fine ... 

La differenza è questa be_truthy è vero quando non lo è zero o falso. Così passerà se il risultato non è né di questi due tipi di "veri". .per essere vero d'altra parte accetta solo un valore che è vero e nient'altro.

  • .per essere_falsy
  • .essere falso

Alcune specifiche

... è 'una descrizione intelligente' aspettarsi (agente.coward?). Essere-federica si aspetta (nemico.megalomane?). Essere falsa fine ... 

Simile ai due esempi sopra, .per essere_falsy si aspetta che a falso o a zero valore, e .essere falso farà solo un confronto diretto su falso.

  • .essere nullo
  • .to_not be_nil

E, ultimo ma non meno importante, questo test esattamente per zero si. Ti risparmio l'esempio.

  • .abbinare ()

Spero che tu abbia già avuto il piacere di guardare nelle espressioni regolari. In caso contrario, si tratta di una sequenza di caratteri con cui è possibile definire un modello che si inserisce tra due barre in avanti per cercare stringhe. Una regex può essere molto utile se si desidera cercare pattern più ampi che si possano generalizzare in tale espressione.

Alcune specifiche

... è 'una descrizione intelligente' aspettarsi (agent.number.to_i). To match (/ \ d 3 /) end ... 

Supponiamo di avere a che fare con agenti come James Bond, 007, a cui sono assegnati numeri a tre cifre. Quindi potremmo testarlo in questo modo, in primis qui, ovviamente.

  • >
  • <
  • <=
  • > =

I confronti sono più utili di quanto si possa pensare. Presumo che gli esempi seguenti riguarderanno ciò che è necessario sapere.

Alcune specifiche

... E 'una descrizione intelligente' ... Aspettatevi (numero dell'agente). Essere < quartermaster.number expect(agent.number).to be > m.number expect (agent.kill_count) .to be> = 25 expect (quartermaster.number_of_gadgets) .to essere <= 5 end… 

Ora, stiamo diventando meno noiosi. Puoi anche provare per classi e tipi:

  • .essere_an_instance_of
  • .essere un
  • .essere un

Alcune specifiche

... è 'una descrizione intelligente' do mission = Mission.create (nome: 'Moonraker') agent = Agent.create (nome: 'James Bond') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end… 

Nell'esempio fittizio di cui sopra, puoi vedere che un elenco di agenti associati a una missione non è di classe Agente ma di ActiveRecord :: :: Associazioni CollectionProxy. Quello che dovresti togliere da questo è che possiamo facilmente testare le classi stesse pur restando altamente espressive. .essere un e .essere un fai una e la stessa cosa Avete entrambe le opzioni disponibili per mantenere le cose leggibili.

Anche la verifica degli errori è estremamente comoda in RSpec. Se sei super fresco di Rails e non sei ancora sicuro di quali errori possa scatenare il framework, potresti non sentire il bisogno di usarli, ovviamente questo è assolutamente logico. In una fase successiva del tuo sviluppo, li troverai comunque molto utili. Hai quattro modi per affrontarli:

  • .per raise_error

Questo è il modo più generico. Qualsiasi errore verrà generato nella tua rete.

  • .to raise_error (ErrorClass)

In questo modo puoi specificare esattamente da quale classe deve venire l'errore.

  • .to raise_error (ErrorClass, "Some error message")

Questo è ancora più fine in quanto non si menziona solo la classe dell'errore, ma un messaggio specifico che dovrebbe essere gettato con l'errore.

  • .to raise_error ("Qualche messaggio di errore)

O si cita solo il messaggio di errore stesso senza la classe di errore. La parte che ci si aspetta ha bisogno di essere scritta un po 'diversamente, però - abbiamo bisogno di avvolgere la parte sotto il testo in un blocco di codice stesso:

Alcune specifiche

... è 'una descrizione intelligente' do agent = Agent.create (nome: 'James Bond') si aspetta agent.lady_killer?. A raise_error (NoMethodError) aspettere double_agent.name .to a raise_error (NameError) expect double_agent. name .to a raise_error ("Error: No double agents around") expect double_agent.name .to a raise_error (NameError, "Error: No double agents around") end ... 
  • .iniziare con
  • .per terminare

Dato che spesso gestiamo le raccolte quando creiamo app web, è bello avere uno strumento per sbirciarle. Qui abbiamo aggiunto due agenti, Q e James Bond, e volevamo solo sapere chi è il primo e l'ultimo nella collezione di agenti per una particolare missione - qui Moonraker.

Alcune specifiche dell'agente

... è 'una descrizione intelligente' fai moonraker = Mission.create (nome: 'Moonraker') bond = Agent.create (nome: 'James Bond') q = Agent.create (nome: 'Q') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end… 
  • .includere

Questo è anche utile per controllare il contenuto delle collezioni.

Alcune specifiche dell'agente

... è 'una descrizione intelligente' do mission = Mission.create (nome: 'Moonraker') bond = Agent.create (nome: 'James Bond') mission.agents << bond expect(mission.agents).to include(bond) end… 
  • predicatori di corrispondenza

Questi abbinamenti predicati sono una funzionalità di RSpec per creare dinamici i corrispondenti per te. Se si dispone di metodi predicati nei propri modelli, ad esempio (che terminano con un punto interrogativo), RSpec sa che è necessario creare per te corrispondenze che è possibile utilizzare nei test. Nell'esempio seguente, vogliamo verificare se un agente è James Bond:

Modello agente

agente di classe < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end

Ora, possiamo usare questo nelle nostre specifiche in questo modo:

Alcune specifiche dell'agente

... è 'una descrizione intelligente' do agent = Agent.create (nome: 'James Bond', numero: '007', giocatore d'azzardo: true) expect (agent). Be bebond end it 'some intelligent description' do agent = Agent. creare (nome: 'James Bond') aspettarsi (agente) .non-essere bebond fine ... 

RSpec ci permette di usare il nome del metodo senza il punto interrogativo per formulare una sintassi migliore, suppongo. Cool, non è vero??

Permettere

permettere e permettere! all'inizio potrebbero sembrare delle variabili, ma in realtà sono metodi di supporto. Il primo viene valutato pigramente, il che significa che viene eseguito e valutato solo quando una specifica lo usa effettivamente, e l'altro lascia con il botto (!) Viene eseguito indipendentemente dall'essere utilizzato da una specifica o meno. Entrambe le versioni sono memoized e i loro valori verranno memorizzati nella cache all'interno dello stesso ambito di esempio.

Qualche file spec

Descrivi Mission, '#prepare',: let let (: mission) Mission.create (nome: 'Moonraker') let! (: bond) Agent.create (nome: 'James Bond') it 'aggiunge agenti in missione 'do mission.prepare (bond) expect (mission.agents). includere bond end end

La versione bang che non viene ponderata può richiedere molto tempo e quindi è costosa se diventa il tuo nuovo amico di fantasia. Perché? Perché imposterà questi dati per ogni test in questione, non importa cosa, e potrebbe alla fine finire per essere una di queste cose sgradevoli che rallentano significativamente la tua suite di test.

Da allora dovresti sapere questa funzionalità di RSpec permettere è ampiamente conosciuto e usato. Detto questo, il prossimo articolo ti mostrerà alcuni problemi che dovresti conoscere. Usa questi metodi di supporto con cautela, o almeno in piccole dosi per ora.

Soggetti

RSpec ti offre la possibilità di dichiarare l'argomento sottoposto a test in modo molto esplicito. Ci sono soluzioni migliori per questo, e discuteremo gli aspetti negativi di questo approccio nel prossimo articolo quando mostro alcune cose che in genere si vuole evitare. Ma per ora, diamo un'occhiata a cosa soggetto può fare per te:

Qualche file spec

descrivere l'agente, '#status' fare oggetto Agent.create (nome: 'Bond') esso 'restituisce lo stato degli agenti' si aspetta (subject.status) .non-essere 'MIA' end end

Questo approccio può, da un lato, aiutare a ridurre la duplicazione del codice, avere un protagonista dichiarato una volta in un determinato ambito, ma può anche portare a qualcosa chiamato un ospite misterioso. Ciò significa semplicemente che potremmo finire in una situazione in cui utilizziamo i dati per uno dei nostri scenari di test, ma non abbiamo più idea da dove provenga e di cosa sia composto. Maggiori informazioni su questo nel prossimo articolo.

callback

Nel caso in cui non sei ancora a conoscenza dei callback, lascia che ti dia una breve panoramica. Le callback vengono eseguite in determinati punti specifici del ciclo di vita del codice. In termini di Rails, ciò significherebbe che il codice è in esecuzione prima che gli oggetti vengano creati, aggiornati, distrutti, ecc. 

Nel contesto di RSpec, è il ciclo di vita dei test in esecuzione. Ciò significa semplicemente che è possibile specificare ganci che devono essere eseguiti prima o dopo l'esecuzione di ogni test nel file spec, o semplicemente attorno a ciascun test. Sono disponibili alcune opzioni a grana fine, ma per il momento mi raccomando di evitare di perdersi nei dettagli. Cominciando dall'inizio:

  • prima (: ciascuno)

Questo callback viene eseguito prima di ogni esempio di test.

Qualche file spec

descrivi Agent, '#favorite_gadget' fai prima (: each) do @gagdet = Gadget.create (nome: 'Walther PPK') termina 'restituisce un elemento, il gadget preferito dell'agente' do agent = Agent.create (name : "James Bond") agente.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end

Supponiamo che tu abbia bisogno di un determinato gadget per ogni test eseguito in un determinato ambito. prima ti consente di estrarlo in un blocco e preparare questo piccolo frammento per te comodamente. Quando si impostano i dati in questo modo, è necessario utilizzare variabili di istanza, ovviamente, per avere accesso ad esso tra vari ambiti.

Attenzione!

Non farti ingannare dalla convenienza in questo esempio. Solo perché puoi fare questo genere di cose non significa che dovresti. Voglio evitare di entrare nel territorio di AntiPattern e confondere l'inferno da te, ma d'altra parte, voglio spiegare gli svantaggi di questi semplici esercizi fittizi un po 'pure. 

Nell'esempio sopra, sarebbe molto più espressivo se si impostassero gli oggetti necessari su base test-by-test. Soprattutto su file spec più grandi, è possibile perdere rapidamente la vista di queste piccole connessioni e rendere più difficile per gli altri mettere insieme questi enigmi.

  • prima (: all)

Questo prima il blocco viene eseguito solo una volta prima di tutti gli altri esempi in un file spec.

Qualche file spec

descrivi l'agente, fai '#enemy' prima (: all) do @main_villain = Villain.create (nome: 'Ernst Stavro Blofeld') @mission = Mission.create (nome: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end

Quando ricordi le quattro fasi del test, prima i blocchi a volte sono utili per impostare qualcosa per te che deve essere ripetuto su base regolare, probabilmente roba che è un po 'più meta in natura.

dopo ogni) e Dopotutto) hanno lo stesso comportamento ma vengono semplicemente eseguiti dopo che i test sono stati eseguiti. dopo è spesso usato per pulire i tuoi file, per esempio. Ma penso che sia un po 'presto per affrontarlo. Quindi affidati alla memoria, sappi che è lì nel caso in cui ne avessi bisogno, e passiamo ad esplorare altre cose più basilari.

Tutti questi callback possono essere posizionati strategicamente per soddisfare le tue esigenze. Mettili in qualsiasi descrivere blocco di cui è necessario eseguirli, non devono necessariamente essere posizionati sopra il file spec. Possono essere facilmente inseriti all'interno delle specifiche. 

Qualche file spec

descrivere Agent do before (: each) do @mission = Mission.create (nome: 'Moonraker') @bond = Agent.create (nome: 'James Bond', numero: '007') termina descrivere '#enemy' fare prima (: each) do @main_villain = Villain.create (nome: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end

 Come puoi osservare, puoi posizionare blocchi di callback in qualsiasi ambito a tuo piacimento, e andare più in profondità di cui hai bisogno. Il codice nel callback verrà eseguito nell'ambito di qualsiasi ambito del blocco descrittivo. Ma un piccolo consiglio: se senti il ​​bisogno di nidificare troppo e le cose sembrano un po 'complicate e complicate, ripensa il tuo approccio e considera come potresti semplificare i test e la loro configurazione. BACIO! Mantienilo semplice, stupido. Inoltre, fai attenzione a come questo si legge quando forziamo questi test a fallire:

Produzione

Fallimenti: 1) Agente # nemico Double 0 Agente con missione associata restituisce il nemico principale che l'agente deve affrontare nella sua missione Errore / Errore: aspettarsi (@ bond.enemy). A eq 'Ernst Stavro Blofeld' atteso: "Ernst Stavro Blofeld "got:" Blofeld "2) Agente # nemico Agente di basso livello con missione associata non restituisce informazioni sul cattivo principale coinvolto Errore / Errore: aspettati (some_schmuck.enemy). su eq 'Questo è sopra il tuo stipendio!' atteso: "Questo è superiore al tuo stipendio!" ottenuto: "Blofeld"

generatori

Diamo anche una rapida occhiata a quali generatori sono forniti da RSpec per te. Ne hai già visto uno quando abbiamo usato rails genera rspec: installa. Questo piccolo amico ha creato RSpec per noi in modo facile e veloce. Cos'altro abbiamo?

  • RSpec: Modello

Vuoi avere un'altra speculazione del modello fittizio?

terminale

rails genera rspec: modella another_dummy_model

Produzione

crea spec / models / another_dummy_model_spec.rb

Veloce, non è vero? O una nuova specifica per un test del controller, ad esempio:

  • rspec: controllore

terminale

rails genera rspec: controller dummy_controller

Produzione

spec / controller / dummy_controller_controller_spec.rb
  • RSpec: view

Le stesse opere per le viste, ovviamente. Non testeremo comunque nessuna vista del genere. Le specifiche per le viste ti danno il minimo contraccolpo, ed è assolutamente sufficiente probabilmente in quasi tutti gli scenari per testare indirettamente le tue visualizzazioni tramite test delle funzionalità. 

I test di funzionalità non sono una specialità di RSpec e sono più adatti a un altro articolo. Detto questo, se sei curioso, controlla Capybara, che è uno strumento eccellente per quel genere di cose. Ti consente di testare interi flussi che esercitano più parti della tua app, testando funzionalità complete e simulando l'esperienza del browser. Ad esempio, un utente che paga per più articoli in un carrello della spesa.

  • RSpec: Assistente

La stessa strategia di generatore ci consente anche di piazzare un aiutante senza troppi problemi.

terminale

rails genera rspec: helper dummy_helper

Produzione

crea spec / helper / dummy_helper_helper_spec.rb

Il doppio helper_helper parte non è stata un incidente. Quando gli diamo un nome più "significativo", vedrai che RSpec si limita ad attaccare _helper da solo.

terminale

rails genera rspec: helper important_stuff

Produzione

crea spec / helper / important_stuff_helper_spec.rb

Una parola sugli aiutanti

No, questa directory non è un posto dove accumulare i tuoi preziosi metodi di aiuto che emergono durante il refactoring dei tuoi test. Questi andrebbero sotto spec / supporto, in realtà. spec / aiutanti è per i test che dovresti scrivere per i tuoi aiutanti di visualizzazione: un aiutante come impostare la data sarebbe un esempio comune Sì, la copertura completa del test del tuo codice dovrebbe includere anche questi metodi di supporto. Solo perché spesso sembrano piccoli e banali non significa che dovremmo ignorarli o ignorare il loro potenziale di bug che vogliamo catturare. Più l'aiutante risulta davvero complesso, più ragione si dovrebbe dover scrivere a helper_spec per questo!

Nel caso in cui inizi a giocarci subito, tieni presente che devi eseguire i tuoi metodi di supporto su a aiutante oggetto quando scrivi i tuoi test di supporto per lavorare. Quindi possono essere esposti solo usando questo oggetto. Qualcosa come questo:

Alcune Specper Helper

descrivere '#set_date' do ... helper.set_date ... end ... 

È possibile utilizzare lo stesso tipo di generatori per le specifiche delle caratteristiche, le specifiche di integrazione e le specifiche del mailer. Questi sono fuori dal nostro campo di applicazione per oggi, ma puoi affidarli alla memoria per un utilizzo futuro:

  • RSpec: mailer
  • RSpec: caratteristica
  • RSpec: l'integrazione

Uno sguardo alle specifiche generati

Le specifiche che abbiamo creato tramite il generatore qui sopra sono pronte per essere utilizzate e puoi aggiungere i tuoi test immediatamente. Diamo uno sguardo minuscolo a una differenza tra le specifiche, però:

spec / modelli / dummy_model_spec.rb

richiede 'rails_helper' RSpec.describe DummyModel, digita:: model do in sospeso "aggiungi alcuni esempi (o cancella) # __ FILE__" fine

spec / controller / dummy_controller_controller_spec.rb

richiede 'rails_helper' RSpec.describe DummyControllerController, digitare:: controller do end

spec / aiutanti / dummy_helper_helper_spec.rb

richiede 'rails_helper' RSpec.describe DummyHelperHelper, digita:: helper do in sospeso "aggiungi alcuni esempi (o cancella) # __ FILE__" fine

Non ha bisogno di un bambino prodigio per capire che tutti hanno diversi tipi. Questo :genere I metadati RSpec ti offrono l'opportunità di suddividere e applicare i test su strutture di file. È possibile indirizzare questi test un po 'meglio in questo modo. Supponi di voler avere una sorta di helper caricati solo per le specifiche del controller, ad esempio. Un altro esempio potrebbe essere che si desidera utilizzare un'altra struttura di directory per le specifiche che RSpec non si aspetta. Avere questi metadati nei test rende possibile continuare a utilizzare le funzioni di supporto di RSpec e non far scattare la suite di test. Quindi sei libero di usare qualsiasi struttura di directory che funzioni per te se lo aggiungi :genere metadati.

I tuoi test RSpec standard non dipendono da quei metadati, d'altra parte. Quando si usano questi generatori, questi verranno aggiunti gratuitamente, ma è possibile evitarli completamente anche se non ne hai bisogno. 

Puoi anche usare questi metadati per filtrare nelle tue specifiche. Supponiamo che tu abbia un blocco precedente che dovrebbe essere eseguito solo sulle specifiche del modello, ad esempio. Neat! Per le suite di test più grandi, questo potrebbe essere molto utile un giorno. È possibile filtrare il gruppo di test mirato che si desidera eseguire, anziché eseguire l'intera suite, operazione che potrebbe richiedere del tempo. 

Le tue opzioni si estendono oltre le tre opzioni di codifica sopra, ovviamente. Impariamo di più sull'affettare e tagliare a cubetti i test nella prossima sezione.

tag

Quando si accumula una suite di test più grande nel tempo, non sarà sufficiente eseguire test in determinate cartelle per eseguire i test RSpec in modo rapido ed efficiente. Quello che vuoi essere in grado di fare è eseguire test che appartengono insieme ma potrebbero essere distribuiti su più directory. Tagging per il salvataggio! Non fraintendetemi, anche l'organizzazione intelligente dei test nelle vostre cartelle è fondamentale, ma il tagging lo fa un po 'più lontano.

Dai test ai metadati come simboli come ": wip", ": checkout" o qualsiasi altra cosa si adatti alle tue esigenze. Quando si eseguono questi gruppi di test mirati, si specifica semplicemente che RSpec deve ignorare l'esecuzione di altri test questa volta fornendo un flag con il nome dei tag.

Qualche file spec

descrivi l'agente,: wip fai 'è un casino adesso' aspetta (agent.favorite_gadgets). a eq 'Unknown' end end

terminale

rspec --tag wip

Produzione

Errori: 1) L'agente è un guaio in questo momento Errore / Errore: aspettarsi (agente.favorite_gadgets) .per eq 'Sconosciuto' ... 

Puoi anche eseguire tutti i tipi di test e ignorare un gruppo di gruppi contrassegnati in un determinato modo. Basta fornire una tilde (~) davanti al nome del tag e RSpec è felice di ignorare questi test.

terminale

rspec --tag ~ wip

L'esecuzione di più tag in modo sincrono non è un problema:

terminale

rspec --tag wip --tag checkout rspec --tag ~ wip --tag checkout

Come puoi vedere sopra, puoi combinarli a piacimento. La sintassi non si ripete perfettamente --etichetta forse non è l'ideale, ma hey, non è neanche un problema! Sì, tutto questo è un po 'più di lavoro extra e sovraccarico mentale quando si compongono le specifiche, ma il rovescio della medaglia, in realtà fornisce una potente capacità di dividere la vostra suite di test su richiesta. Su progetti più grandi, può farti risparmiare un sacco di tempo in quel modo.

Pensieri finali

Quello che hai imparato fino ad ora dovrebbe essere dotato delle basi assolute per giocare con i test da solo: un kit di sopravvivenza per principianti. E davvero giocare e commettere errori il più possibile. Porta RSpec e tutto il gioco guidato al test per un giro e non aspettarti di scrivere subito test di qualità. Ci sono ancora un paio di pezzi mancanti prima che ti senta a tuo agio e prima di essere efficace con esso. 

Per me, all'inizio era un po 'frustrante perché era difficile vedere come testare qualcosa quando non l'avevo ancora implementato e non capivo appieno come si sarebbe comportato. 

I test dimostrano davvero se comprendi un framework come Rails e sai come si integrano i pezzi. Quando scrivi dei test, dovrai essere in grado di scrivere delle aspettative su come dovrebbe comportarsi un framework. 

Non è facile se stai appena iniziando con tutto questo. Occuparsi di più linguaggi specifici del dominio, ad esempio RSpec e Rails, ad esempio, oltre all'apprendimento dell'API Ruby può essere fonte di confusione. Non sentirti male se la curva di apprendimento sembra scoraggiante; diventerà più facile se ti infili. Far spegnere questa lampadina non accadrà per tutta la notte, ma per me è valsa davvero la pena.