Was ist der Unterschied zwischen RSpecs Thema und lassen? Wann sollten sie verwendet werden oder nicht?

Antworten:

189

Zusammenfassung: Das Subjekt von RSpec ist eine spezielle Variable, die sich auf das zu testende Objekt bezieht. Es können implizit Erwartungen gesetzt werden, was einzeilige Beispiele unterstützt. Es ist dem Leser in einigen idiomatischen Fällen klar, aber ansonsten schwer zu verstehen und sollte vermieden werden. Die letVariablen von RSpec sind nur träge instanziierte (gespeicherte) Variablen. Sie sind nicht so schwer zu verfolgen wie das Thema, können aber dennoch zu verworrenen Tests führen und sollten daher mit Diskretion verwendet werden.

Das Thema

Wie es funktioniert

Das Subjekt ist das zu testende Objekt. RSpec hat eine explizite Vorstellung von dem Thema. Es kann definiert sein oder nicht. Wenn dies der Fall ist, kann RSpec Methoden aufrufen, ohne explizit darauf zu verweisen.

Wenn das erste Argument für eine äußerste Beispielgruppe ( describeoder einen äußersten contextBlock) eine Klasse ist, erstellt RSpec standardmäßig eine Instanz dieser Klasse und weist sie dem Betreff zu. Zum Beispiel passieren die folgenden:

class A
end

describe A do
  it "is instantiated by RSpec" do
    expect(subject).to be_an(A)
  end
end

Sie können das Thema selbst definieren mit subject:

describe "anonymous subject" do
  subject { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Sie können dem Thema einen Namen geben, wenn Sie es definieren:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(a).to be_an(A)
  end
end

Auch wenn Sie das Thema benennen, können Sie anonym darauf verweisen:

describe "named subject" do
  subject(:a) { A.new }
  it "has been instantiated" do
    expect(subject).to be_an(A)
  end
end

Sie können mehr als ein benanntes Thema definieren. Das zuletzt definierte benannte Thema ist das anonyme subject.

Das Thema ist jedoch definiert,

  1. Es ist träge instanziiert. Das heißt, die implizite Instanziierung der beschriebenen Klasse oder die Ausführung des an übergebenen Blocks subjecterfolgt erst, wenn subjectin einem Beispiel auf das benannte Subjekt verwiesen wird. Wenn Sie möchten, dass Ihr explizites Thema eifrig instanziiert wird (bevor ein Beispiel in seiner Gruppe ausgeführt wird), sagen Sie subject!statt subject.

  2. Erwartungen können implizit darauf gesetzt werden (ohne zu schreiben subjectoder den Namen eines benannten Subjekts):

    describe A do
      it { is_expected.to be_an(A) }
    end
    

    Der Betreff unterstützt diese einzeilige Syntax.

Wann man es benutzt

Ein implizites subject(aus der Beispielgruppe abgeleitet) ist schwer zu verstehen, weil

  • Es wird hinter den Kulissen instanziiert.
  • Unabhängig davon, ob es implizit (durch Aufrufen is_expectedohne expliziten Empfänger) oder explizit (as subject) verwendet wird, gibt es dem Leser keine Informationen über die Rolle oder die Art des Objekts, für das die Erwartung aufgerufen wird.
  • Die einzeilige Beispielsyntax enthält keine Beispielbeschreibung (das Zeichenfolgenargument itin der normalen Beispielsyntax). Die einzige Information, die der Leser über den Zweck des Beispiels hat, ist die Erwartung selbst.

Daher ist es nur dann hilfreich, ein implizites Thema zu verwenden, wenn der Kontext wahrscheinlich von allen Lesern gut verstanden wird und eine Beispielbeschreibung wirklich nicht erforderlich ist . Der kanonische Fall ist das Testen von ActiveRecord-Validierungen mit Shoulda-Matchern:

describe Article do
  it { is_expected.to validate_presence_of(:title) }
end

Eine explizite Erklärung subject(definiert subjectohne Namen) ist etwas besser, da der Leser sehen kann, wie sie instanziiert wird, aber

  • Es kann die Instanziierung des Themas immer noch weit von dem Ort entfernen, an dem es verwendet wird (z. B. am Anfang einer Beispielgruppe mit vielen Beispielen, die es verwenden), was immer noch schwer zu verfolgen ist
  • es hat die anderen Probleme, die das implizite Subjekt hat.

Ein benannter Betreff enthält einen absichtsvollen Namen. Der einzige Grund, einen benannten Betreff anstelle einer letVariablen zu verwenden, besteht darin, dass Sie den anonymen Betreff manchmal verwenden möchten. Wir haben nur erklärt, warum der anonyme Betreff schwer zu verstehen ist.

Daher sind legitime Verwendungen eines expliziten anonymen subjectoder benannten Themas sehr selten .

let Variablen

Wie sie arbeiten

let Variablen sind bis auf zwei Unterschiede genau wie benannte Subjekte:

  • Sie werden mit let/ let!anstelle von subject/ definiertsubject!
  • Sie setzen das Anonyme nicht subjectund lassen nicht zu, dass Erwartungen implizit darauf bezogen werden.

Wann man sie benutzt

Es ist völlig legitim, letDoppelarbeit zwischen Beispielen zu reduzieren. Tun Sie dies jedoch nur, wenn die Testklarheit nicht beeinträchtigt wird. Die sicherste Zeit letist, wenn der letZweck der Variablen vollständig aus ihrem Namen hervorgeht (damit der Leser nicht die Definition finden muss, die viele Zeilen entfernt sein kann, um jedes Beispiel zu verstehen) und sie auf die gleiche Weise verwendet wird in jedem Beispiel. Wenn eines dieser Dinge nicht zutrifft, können Sie das Objekt in einer einfachen alten lokalen Variablen definieren oder direkt im Beispiel eine Factory-Methode aufrufen.

let!ist riskant, weil es nicht faul ist. Wenn jemand der Beispielgruppe, die das enthält let!, ein Beispiel hinzufügt , das Beispiel jedoch die let!Variable nicht benötigt ,

  • Dieses Beispiel ist schwer zu verstehen, da der Leser die let!Variable sieht und sich fragt, ob und wie sie sich auf das Beispiel auswirkt
  • Das Beispiel ist aufgrund der Zeit, die zum Erstellen der let!Variablen benötigt wird, langsamer als erforderlich

Verwenden Sie diese also let!, wenn überhaupt, nur in kleinen, einfachen Beispielgruppen, in denen es weniger wahrscheinlich ist, dass zukünftige Beispielautoren in diese Falle tappen.

Der Single-Expectation-per-Example-Fetisch

Es gibt eine häufige Überbeanspruchung von Themen oder letVariablen, die es wert ist, separat diskutiert zu werden. Einige Leute benutzen sie gerne so:

describe 'Calculator' do
  describe '#calculate' do
    subject { Calculator.calculate }
    it { is_expected.to be >= 0 }
    it { is_expected.to be <= 9 }
  end
end

(Dies ist ein einfaches Beispiel für eine Methode, die eine Zahl zurückgibt, für die wir zwei Erwartungen benötigen. Dieser Stil kann jedoch viel mehr Beispiele / Erwartungen enthalten, wenn die Methode einen komplizierteren Wert zurückgibt, der viele Erwartungen erfordert und / oder viele Nebenwirkungen hat Alle brauchen Erwartungen.)

Die Leute tun dies, weil sie gehört haben, dass man nur eine Erwartung pro Beispiel haben sollte (was mit der gültigen Regel verwechselt wird, dass man nur einen Methodenaufruf pro Beispiel testen sollte) oder weil sie in RSpec-Tricks verliebt sind. Tun Sie es nicht, ob mit einem anonymen oder benannten Betreff oder einer letVariablen! Dieser Stil hat mehrere Probleme:

  • Das anonyme Thema ist nicht das Thema der Beispiele - die Methode ist das Thema. Wenn Sie den Test auf diese Weise schreiben, wird die Sprache durcheinander gebracht, was das Nachdenken erschwert.
  • Wie immer bei einzeiligen Beispielen gibt es keinen Raum, um die Bedeutung der Erwartungen zu erklären.
  • Das Thema muss für jedes Beispiel konstruiert werden, was langsam ist.

Schreiben Sie stattdessen ein einzelnes Beispiel:

describe 'Calculator' do
  describe '#calculate' do
    it "returns a single-digit number" do
      result = Calculator.calculate
      expect(result).to be >= 0
      expect(result).to be <= 9
    end
  end
end
Dave Schweisguth
quelle
17
Beeindruckend! + 💯! "Der Single-Expectation-per-Example-Fetisch" ist sozusagen Gold wert. Ich hatte nie das Bedürfnis, diese (falsche) Regel sehr genau zu befolgen, aber jetzt bin ich gut bewaffnet gegen die Menschen, die versuchen, sie dem Rest von uns aufzuzwingen. Vielen Dank!
Bilderstürmer
2
Wenn Sie möchten, dass Ihre Blöcke mit mehreren Erwartungen alle Erwartungszeilen ausführen (anstatt nicht ausgeführt zu werden, wenn die erste fehlschlägt), können Sie das :aggregate_failuresTag auch in einer Zeile wie it ​"marks a task complete"​, ​:aggregate_failures​ ​do(aus dem Buch Rails 5 Test Prescriptions)
Labyrinth
1
Ich bin auch alle gegen Hypes und Fetische. "Single-Expectation-per-Example" ist hauptsächlich für diejenigen gedacht, die viele unabhängige Erwartungen in einem einzigen Beispiel zusammenfassen möchten. Semantisch gesehen ist Ihr Beispiel nicht das Ideal, da es nicht für eine einstellige Zahl, sondern für reelle Zahlen in [0, 9] validiert wird - was überraschenderweise in einer weitaus besser lesbaren einzigen Erwartung codiert werden könnte expect(result).to be_between(0, 9).
Andre Figueiredo
Wenn Sie nur für eine einstellige Zahl (Int [0,9]) validieren möchten, ist dies besser geeignet expect(result.to_s).to match(/^[0-9]$/)- ich weiß, dass es hässlich ist, aber es testet wirklich, was Sie sagen, oder verwenden Sie vielleicht between+ is_a? Integer, aber hier testen Sie der Typ auch. Und nur let... es sollte kein Gegenstand von Bedenken sein, und es kann tatsächlich besser sein, Werte zwischen Beispielen neu zu bewerten. Ansonsten +1 für die Post
Andre Figueiredo
Ich liebe deine Erklärung über die Gefahren von let!und war es nicht, meine Teamkollegen alleine zu überzeugen. Ich werde diese Antwort senden.
Damon Aw
5

Subjectund letsind nur Werkzeuge, mit denen Sie Ihre Tests aufräumen und beschleunigen können. Die Leute in der rspec-Community verwenden sie, damit ich mir keine Sorgen machen kann, ob es in Ordnung ist, sie zu verwenden oder nicht. Sie können ähnlich verwendet werden, dienen jedoch leicht unterschiedlichen Zwecken

SubjectMit dieser Option können Sie eine Testperson deklarieren und anschließend für eine beliebige Anzahl folgender Testfälle wiederverwenden. Dies reduziert die Code-Wiederholung (TROCKNEN Ihres Codes)

Letist eine Alternative zu before: eachBlöcken, die Instanzvariablen Testdaten zuweisen. Letgibt Ihnen ein paar Vorteile. Zunächst wird der Wert zwischengespeichert, ohne ihn einer Instanzvariablen zuzuweisen. Zweitens wird es träge ausgewertet, was bedeutet, dass es erst ausgewertet wird, wenn eine Spezifikation es erfordert. So können letSie Ihre Tests beschleunigen. Ich denke auch, dass letes leichter zu lesen ist

Ren
quelle
1

subjectist das, was getestet wird, normalerweise eine Instanz oder eine Klasse. letdient zum Zuweisen von Variablen in Ihren Tests, die im Vergleich zur Verwendung von Instanzvariablen träge ausgewertet werden. Es gibt einige schöne Beispiele in diesem Thread.

https://github.com/reachlocal/rspec-style-guide/issues/6

nikkypx
quelle