Jaka jest różnica między tematem RSpec a let? Kiedy należy ich używać, czy nie?

Odpowiedzi:

191

Podsumowanie: Temat RSpec to specjalna zmienna, która odnosi się do testowanego obiektu. Można na nim niejawnie ustawić oczekiwania, co obsługuje przykłady jednowierszowe. W niektórych idiomatycznych przypadkach jest to jasne dla czytelnika, ale poza tym jest trudne do zrozumienia i należy go unikać. letZmienne RSpec są po prostu zmiennymi, których instancje są leniwie tworzone (zapamiętywane). Nie są tak trudne do naśladowania jak przedmiot, ale nadal mogą prowadzić do splątanych testów, dlatego należy ich używać z rozwagą.

Temat

Jak to działa

Podmiot jest badanym obiektem. RSpec ma wyraźne pojęcie na ten temat. Może być zdefiniowany lub nie. Jeśli tak, RSpec może wywołać na nim metody bez odwoływania się do niego jawnie.

Domyślnie, jeśli pierwszym argumentem najbardziej zewnętrznej przykładowej grupy ( describelub contextbloku) jest klasa, RSpec tworzy instancję tej klasy i przypisuje ją do podmiotu. Na przykład następujące przebiegi:

class A
end

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

Możesz samodzielnie zdefiniować temat za pomocą subject:

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

Możesz nadać tematowi nazwę, kiedy ją zdefiniujesz:

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

Nawet jeśli nazwiesz temat, nadal możesz odwoływać się do niego anonimowo:

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

Możesz zdefiniować więcej niż jeden nazwany temat. Ostatnio zdefiniowany nazwany podmiot to anonimowy subject.

Jednak temat jest zdefiniowany,

  1. Instancja jest uruchamiana leniwie. Oznacza to, że niejawna instancja opisanej klasy lub wykonanie przekazanego bloku subjectnie następuje, dopóki subjectw przykładzie nie zostanie odwołany do nazwanego podmiotu. Jeśli chcesz, aby Twój jawny temat był szybko tworzony (zanim uruchomi się przykład w jego grupie), powiedz subject!zamiast subject.

  2. Oczekiwania można ustawić na nim niejawnie (bez pisania subjectlub nazwy nazwanego podmiotu):

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

    Podmiot istnieje, aby obsługiwać tę jednowierszową składnię.

Kiedy go używać

Niejawny subject(wywnioskowany z przykładowej grupy) jest trudny do zrozumienia, ponieważ

  • Jest uruchamiany za kulisami.
  • Czy jest używany niejawnie (przez wywołanie is_expectedbez jawnego odbiornika), czy jawnie (jakosubject ), nie daje czytelnikowi żadnych informacji o roli lub naturze obiektu, na którym wywoływane jest oczekiwanie.
  • Przykładowa składnia jednowierszowa nie ma przykładowego opisu (argument będący ciągiem itw normalnej składni przykładu), więc jedyną informacją, jaką posiada czytelnik o celu przykładu, są same oczekiwania.

Dlatego użycie niejawnego tematu jest pomocne tylko wtedy, gdy kontekst jest prawdopodobnie dobrze zrozumiany przez wszystkich czytelników i naprawdę nie ma potrzeby przykładowego opisu . Przykładem kanonicznym jest testowanie walidacji ActiveRecord za pomocą dopasowań shoulda:

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

Wyraźnie anonimowy subject(zdefiniowany subjectbez nazwy) jest trochę lepszy, ponieważ czytelnik może zobaczyć, jak jest utworzony, ale

  • nadal może umieścić instancję podmiotu daleko od miejsca, w którym jest używany (np. na początku przykładowej grupy z wieloma przykładami, które go używają), co nadal jest trudne do naśladowania, i
  • ma inne problemy, które ma ukryty podmiot.

Nazwany podmiot podaje nazwę ujawniającą intencje, ale jedynym powodem używania nazwanego podmiotu zamiast letzmiennej jest to, że chcesz czasami używać anonimowego podmiotu, a właśnie wyjaśniliśmy, dlaczego anonimowy podmiot jest trudny do zrozumienia.

Tak więc legalne wykorzystanie wyraźnie anonimowego subjectlub wymienionego podmiotu jest bardzo rzadkie .

let zmienne

Jak oni pracują

let zmienne są podobne do nazwanych przedmiotów, z wyjątkiem dwóch różnic:

  • są zdefiniowane za pomocą let/ let!zamiast subject/subject!
  • nie stawiają na anonimowość subjectani nie pozwalają na to, by w sposób dorozumiany wywoływać oczekiwania.

Kiedy ich używać

Jest to całkowicie uzasadnione, letaby ograniczyć powielanie się przykładów. Jednak rób to tylko wtedy, gdy nie zmniejsza to przejrzystości testu. Najbezpieczniejszym momentem do użycia letjest sytuacja, gdy przeznaczenie letzmiennej jest całkowicie jasne z jej nazwy (aby czytelnik nie musiał szukać definicji, która może być oddalona o wiele linii, aby zrozumieć każdy przykład) i jest używany w ten sam sposób w każdym przykładzie. Jeśli któraś z tych rzeczy nie jest prawdą, rozważ zdefiniowanie obiektu w zwykłej starej zmiennej lokalnej lub wywołanie metody fabrycznej bezpośrednio w przykładzie.

let!jest ryzykowne, ponieważ nie jest leniwe. Jeśli ktoś doda przykład do przykładowej grupy, która zawiera zmienną, let!ale przykład nie potrzebuje let!zmiennej,

  • ten przykład będzie trudny do zrozumienia, ponieważ czytelnik zobaczy let!zmienną i zacznie się zastanawiać, czy i jak wpływa ona na przykład
  • przykład będzie wolniejszy niż powinien, ze względu na czas potrzebny do utworzenia let!zmiennej

Dlatego używaj let!, jeśli w ogóle, tylko w małych, prostych grupach przykładów, w których jest mniej prawdopodobne, że przyszli autorzy przykładów wpadną w tę pułapkę.

Fetysz polegający na pojedynczym oczekiwaniu na przykład

Istnieje częste nadużywanie tematów lub letzmiennych, które warto omówić osobno. Niektórzy ludzie lubią ich używać w ten sposób:

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

(To jest prosty przykład metody, która zwraca liczbę, dla której potrzebujemy dwóch oczekiwań, ale ten styl może mieć znacznie więcej przykładów / oczekiwań, jeśli metoda zwraca bardziej skomplikowaną wartość, która wymaga wielu oczekiwań i / lub ma wiele skutków ubocznych, które wszyscy potrzebują oczekiwań.)

Ludzie robią to, ponieważ słyszeli, że należy mieć tylko jedno oczekiwanie na przykład (co jest pomieszane z obowiązującą zasadą, że należy testować tylko jedno wywołanie metody na przykład) lub dlatego, że są zakochani w sztuczkach RSpec. Nie rób tego, czy to z anonimowym, nazwanym tematem czy letzmienną! Ten styl ma kilka problemów:

  • Podmiot anonimowy nie jest przedmiotem przykładów - metoda jest przedmiotem. Pisanie testu w ten sposób psuje język, utrudniając myślenie.
  • Jak zawsze w przypadku jednowierszowych przykładów, nie ma miejsca na wyjaśnienie znaczenia oczekiwań.
  • Temat musi być skonstruowany dla każdego przykładu, który jest powolny.

Zamiast tego napisz jeden przykład:

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
źródło
17
Łał! + 💯! Można powiedzieć, że „fetysz polegający na jednym oczekiwaniu na przykład” jest na wagę złota. Nigdy nie czułem potrzeby ścisłego przestrzegania tej (błędnej) zasady, ale teraz jestem dobrze uzbrojony przeciwko ludziom, którzy próbują narzucić nam ją reszcie. Dzięki!
ikonoklast
2
Ponadto, jeśli chcesz, aby Twoje bloki wielu oczekiwań uruchamiały wszystkie oczekiwane wiersze (zamiast nie działać, jeśli pierwsza zawiedzie), możesz użyć :aggregate_failurestagu w linii takiej jak it ​"marks a task complete"​, ​:aggregate_failures​ ​do(wzięta z książki Rails 5 Test Prescriptions)
labyrinth
1
Jestem też przeciwny hiper i fetyszom, „pojedyncze oczekiwanie na przykład” jest przeznaczone głównie dla tych, którzy lubią grupować wiele niezwiązanych ze sobą oczekiwań w jednym przykładzie. Mówiąc semantycznie, twój przykład nie jest idealny, ponieważ nie sprawdza poprawności dla liczby jednocyfrowej, ale sprawdza poprawność liczb rzeczywistych w [0, 9] - co, niespodzianka, można zakodować w znacznie bardziej czytelnym pojedynczym oczekiwaniu expect(result).to be_between(0, 9).
Andre Figueiredo
Jeśli chcesz walidować tylko dla liczby jednocyfrowej (Int [0,9]), byłoby to bardziej odpowiednie expect(result.to_s).to match(/^[0-9]$/)- wiem, że jest brzydki, ale naprawdę sprawdza to, co mówisz, lub być może użyj between+ is_a? Integer, ale tutaj testujesz ten typ też. I po prostu let… nie powinno to być przedmiotem troski, a właściwie może być lepiej przewartościować wartości między przykładami. W przeciwnym razie +1 za post
Andre Figueiredo
Uwielbiam twoje wyjaśnienia na temat niebezpieczeństw let!i nie byłem w stanie samodzielnie przekonać moich kolegów z drużyny. Prześlę tę odpowiedź.
Damon Aw,
5

Subjecti letsą tylko narzędziami, które pomogą Ci uporządkować i przyspieszyć testy. Używają ich ludzie ze społeczności rspec, więc nie martwię się, czy mogę ich używać, czy nie. Mogą być używane podobnie, ale służą nieco innym celom

Subjectumożliwia zadeklarowanie podmiotu testowego, a następnie ponowne użycie go w dowolnej liczbie następujących przypadków testowych. Zmniejsza to powtarzanie się kodu (wysychanie kodu)

Letjest alternatywą dla before: eachbloków, które przypisują dane testowe do zmiennych instancji. Letdaje kilka korzyści. Po pierwsze, buforuje wartość bez przypisywania jej do zmiennej instancji. Po drugie, jest oceniany leniwie, co oznacza, że ​​nie jest oceniany, dopóki specyfikacja tego nie zażąda. W ten sposób letpomaga przyspieszyć testy. Myślę też, że letjest łatwiejszy do odczytania

Ren
źródło