RSpec: Was ist der Unterschied zwischen let und a before block?

90

Was ist der Unterschied zwischen letund einem beforeBlock in RSpec?

Und wann jeweils zu verwenden?

Was ist ein guter Ansatz (lassen oder vorher) im folgenden Beispiel?

let(:user) { User.make !}
let(:account) {user.account.make!}

before(:each) do
 @user = User.make!
 @account = @user.account.make!
end

Ich habe diesen Stackoverflow-Beitrag studiert

Aber ist es gut, let für Assoziationssachen wie oben zu definieren?

Kriysna
quelle
1
Grundsätzlich wird 'let' von Personen verwendet, die Instanzvariablen nicht mögen. Als Randnotiz sollten Sie FactoryGirl oder ein ähnliches Tool verwenden.
Apneadiving

Antworten:

146

Die Leute scheinen einige der grundlegenden Arten, in denen sie sich unterscheiden, erklärt zu haben, haben sie jedoch ausgelassen before(:all)und erklären nicht genau, warum sie verwendet werden sollten.

Ich bin der Meinung, dass Instanzvariablen in der überwiegenden Mehrheit der Spezifikationen keinen Platz haben, was teilweise auf die in dieser Antwort genannten Gründe zurückzuführen ist. Daher werde ich sie hier nicht als Option erwähnen.

Blöcke lassen

Code innerhalb eines letBlocks wird nur ausgeführt, wenn auf ihn verwiesen wird. Das verzögerte Laden bedeutet, dass die Reihenfolge dieser Blöcke irrelevant ist. Dies gibt Ihnen eine große Menge an Leistung, um die wiederholte Einrichtung durch Ihre Spezifikationen zu reduzieren.

Ein (extrem kleines und erfundenes) Beispiel hierfür ist:

let(:person)     { build(:person) }
subject(:result) { Library.calculate_awesome(person, has_moustache) }

context 'with a moustache' do
  let(:has_moustache) { true } 
  its(:awesome?)      { should be_true }
end

context 'without a moustache' do
  let(:has_moustache) { false } 
  its(:awesome?)      { should be_false }
end

Sie können sehen, dass dies has_moustachejeweils unterschiedlich definiert ist, die Definition muss jedoch nicht wiederholt werden subject. Es ist wichtig zu beachten, dass der letzte letim aktuellen Kontext definierte Block verwendet wird. Dies ist gut, um einen Standard festzulegen, der für die meisten Spezifikationen verwendet wird und bei Bedarf überschrieben werden kann.

Zum Beispiel würde das Überprüfen des Rückgabewerts von, calculate_awesomewenn ein personModell mit dem Wert top_hattrue übergeben wurde, aber kein Schnurrbart wäre:

context 'without a moustache but with a top hat' do
  let(:has_moustache) { false } 
  let(:person)        { build(:person, top_hat: true) }
  its(:awesome?)      { should be_true }
end

Eine weitere Anmerkung zu let-Blöcken: Sie sollten nicht verwendet werden, wenn Sie nach etwas suchen, das in der Datenbank gespeichert wurde (dh Library.find_awesome_people(search_criteria)), da sie nicht in der Datenbank gespeichert werden, es sei denn, auf sie wurde bereits verwiesen. let!oder beforeBlöcke sollten hier verwendet werden.

Auch nie jemals verwenden , beforeum Trigger - Ausführung von letBlöcken, das ist , was let!wird für!

Lassen! Blöcke

let!Blöcke werden in der Reihenfolge ausgeführt, in der sie definiert sind (ähnlich wie ein Vorher-Block). Der einzige wesentliche Unterschied zu früheren Blöcken besteht darin, dass Sie einen expliziten Verweis auf diese Variable erhalten, anstatt auf Instanzvariablen zurückgreifen zu müssen.

Wenn wie bei letBlöcken mehrere let!Blöcke mit demselben Namen definiert sind, wird bei der Ausführung der neueste verwendet. Der Hauptunterschied besteht darin, dass let!Blöcke bei dieser Verwendung mehrmals ausgeführt werden, während der letBlock nur das letzte Mal ausgeführt wird.

vor (: jeweils) Blöcken

before(:each)ist die Standardeinstellung vor dem Block und kann daher als jedes Mal angegeben werden, before {}anstatt das vollständige anzugeben before(:each) {}.

Es ist meine persönliche Präferenz, beforeBlöcke in einigen Kernsituationen zu verwenden. Ich werde vor Blöcken verwenden, wenn:

  • Ich benutze Spott, Stubbing oder Double
  • Es gibt ein Setup mit angemessener Größe (im Allgemeinen ist dies ein Zeichen dafür, dass Ihre Werksmerkmale nicht korrekt eingerichtet wurden).
  • Es gibt eine Reihe von Variablen, auf die ich nicht direkt verweisen muss, die aber für die Einrichtung erforderlich sind
  • Ich schreibe Funktionscontrollertests in Schienen und möchte eine bestimmte Testanforderung ausführen (dh before { get :index }). Obwohl Sie subjectdies in vielen Fällen verwenden können, fühlt es sich manchmal expliziter an, wenn Sie keine Referenz benötigen.

Wenn Sie große beforeBlöcke für Ihre Spezifikationen schreiben , überprüfen Sie Ihre Fabriken und stellen Sie sicher, dass Sie die Merkmale und ihre Flexibilität vollständig verstehen.

vor (: allen) Blöcken

Diese werden immer nur einmal vor den Spezifikationen im aktuellen Kontext (und ihren untergeordneten Elementen) ausgeführt. Diese können bei korrekter Schreibweise von großem Vorteil sein, da bestimmte Situationen die Ausführung und den Aufwand verringern können.

Ein Beispiel (das die Ausführungszeit kaum beeinflussen würde) ist das Verspotten einer ENV-Variablen für einen Test, was Sie immer nur einmal tun sollten.

Hoffentlich hilft das :)

Jay
quelle
Für Minitest-Benutzer, die ich bin, aber das RSpec-Tag dieser Fragen und Antworten nicht bemerkt habe, ist die before(:all)Option in Minitest nicht vorhanden. Hier sind einige Problemumgehungen in den Kommentaren: github.com/seattlerb/minitest/issues/61
MrYoshiji
Artikel, auf den verwiesen wird, ist ein toter Link: \
Dylan Pierce
Danke @DylanPierce. Ich kann keine Kopien dieses Artikels finden, daher habe ich auf eine SO-Antwort verwiesen, die dies stattdessen anspricht :)
Jay
Vielen Dank :) sehr geschätzt
Dylan Pierce
1
itsist nicht mehr in rspec-core. Ein moderner, idiomatischer Weg ist it { is_expected.to be_awesome }.
Zubin
25

Fast immer bevorzuge ich let. Der Beitrag, den Sie verlinken, gibt an, dass dies letauch schneller ist. Manchmal, wenn viele Befehle ausgeführt werden müssen, könnte ich sie verwenden, before(:each)da die Syntax klarer ist, wenn viele Befehle beteiligt sind.

In Ihrem Beispiel würde ich definitiv lieber verwenden letals before(:each). Im Allgemeinen neige ich dazu, wenn nur eine variable Initialisierung durchgeführt wird let.

Spyros
quelle
15

Ein großer Unterschied, der nicht erwähnt wurde, besteht darin, dass mit definierte Variablen leterst instanziiert werden, wenn Sie sie zum ersten Mal aufrufen. Während ein before(:each)Block alle Variablen instanziieren würde, können letSie eine Reihe von Variablen definieren, die Sie möglicherweise für mehrere Tests verwenden. Diese werden jedoch nicht automatisch instanziiert. Ohne dies zu wissen, könnten Ihre Tests zurückkehren, um sich selbst zu beißen, wenn Sie erwarten, dass alle Daten im Voraus geladen werden. In einigen Fällen möchten Sie möglicherweise sogar eine Reihe von letVariablen definieren und dann mit einem before(:each)Block jede letInstanz aufrufen , um sicherzustellen, dass die Daten zunächst verfügbar sind.

mikeweber
quelle
20
Sie können let!Methoden definieren, die vor jedem Beispiel aufgerufen werden. Siehe RSpec-Dokumente .
Jim Stewart
3

Es sieht so aus, als würden Sie Machinist verwenden. Achtung, es können Probleme mit make auftreten! Innerhalb von let (der Non-Bang-Version), das außerhalb der globalen Fixture-Transaktion stattfindet (wenn Sie auch Transaktions-Fixtures verwenden), wodurch die Daten für Ihre anderen Tests beschädigt werden.

Tim Connor
quelle