RSpec: Jaka jest różnica między let i przed blokiem?

90

Jaka jest różnica między leti a beforeblock w RSpec?

A kiedy używać każdego?

Jakie będzie dobre podejście (niech lub wcześniej) w poniższym przykładzie?

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

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

Przestudiowałem ten post o przepełnieniu stosu

Ale czy dobrze jest zdefiniować let dla rzeczy takich jak powyżej?

kriysna
źródło
1
Zasadniczo „let” jest używane przez osoby, które nie lubią zmiennych instancji. Na marginesie, powinieneś rozważyć użycie FactoryGirl lub podobnego narzędzia.
nurkowanie na bezdechu

Odpowiedzi:

147

Wydaje się, że ludzie wyjaśnili niektóre podstawowe sposoby, w jakie się różnią, ale pominęli before(:all)i nie wyjaśniają dokładnie, dlaczego należy ich używać.

Uważam, że zmienne instancji nie mają miejsca w większości specyfikacji, częściowo z powodów wymienionych w tej odpowiedzi , więc nie wspomnę o nich tutaj jako o opcji.

niech bloki

Kod w letbloku jest wykonywany tylko wtedy, gdy jest przywoływany, leniwe ładowanie oznacza to, że kolejność tych bloków jest nieistotna. Daje to dużą moc do ograniczenia powtarzających się konfiguracji w Twoich specyfikacjach.

Jednym (bardzo małym i wymyślnym) przykładem tego jest:

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

Jak widać, has_moustachew każdym przypadku definiuje się to inaczej, ale nie ma potrzeby powtarzania subjectdefinicji. Należy zauważyć, że letzostanie użyty ostatni blok zdefiniowany w bieżącym kontekście. Jest to dobre przy ustawianiu wartości domyślnych, które mają być używane dla większości specyfikacji, które w razie potrzeby można nadpisać.

Na przykład sprawdzenie zwracanej wartości, calculate_awesomejeśli przekazano personmodel z top_hatustawionym na true, ale bez wąsów, byłoby:

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

Kolejna rzecz, na którą należy zwrócić uwagę w przypadku bloków let, nie należy ich używać, jeśli szukasz czegoś, co zostało zapisane w bazie danych (tj. Library.find_awesome_people(search_criteria)), Ponieważ nie zostaną one zapisane w bazie danych, chyba że zostały już przywołane. let!lub beforebloki są tym, co powinno być tutaj używane.

Ponadto, nigdy nie używaj beforedo wyzwalania wykonywania letbloków, do tego let!jest stworzone!

pozwolić! Bloki

let!bloki są wykonywane w kolejności, w jakiej zostały zdefiniowane (podobnie jak przed blokiem). Jedyną podstawową różnicą w porównaniu z blokami przed blokami jest to, że otrzymujesz jawne odniesienie do tej zmiennej, zamiast konieczności powrotu do zmiennych instancji.

Podobnie jak w przypadku letbloków, jeśli let!zdefiniowano wiele bloków o tej samej nazwie, podczas wykonywania zostanie użyty najnowszy. Podstawowa różnica polega na tym, że let!bloki będą wykonywane wiele razy, jeśli zostaną użyte w ten sposób, podczas gdy letblok będzie wykonywany tylko ostatnim razem.

przed (: każdym) blokami

before(:each)jest ustawieniem domyślnym przed blokiem i dlatego można do niego odwoływać się jako before {}zamiast określania pełnego za before(:each) {}każdym razem.

Osobiście wolę używać beforebloków w kilku podstawowych sytuacjach. Użyję przed blokami, jeśli:

  • Używam kpiny, karczowania lub gry podwójnej
  • Istnieje dowolna konfiguracja o rozsądnych rozmiarach (zazwyczaj jest to znak, że cechy fabryczne nie zostały poprawnie skonfigurowane)
  • Istnieje wiele zmiennych, do których nie muszę się odwoływać bezpośrednio, ale są one wymagane do konfiguracji
  • Piszę testy kontrolerów funkcjonalnych w railsach i chcę wykonać określone żądanie testowania (tj before { get :index }.). Nawet jeśli subjectw wielu przypadkach możesz tego użyć , czasami wydaje się to bardziej wyraźne, jeśli nie potrzebujesz odniesienia.

Jeśli zauważysz, że piszesz duże beforebloki dla swoich specyfikacji, sprawdź swoje fabryki i upewnij się, że w pełni rozumiesz cechy i ich elastyczność.

przed (: wszystkie) bloki

Są one wykonywane tylko raz, przed specyfikacjami w bieżącym kontekście (i jego potomkach). Można je wykorzystać z wielką korzyścią, jeśli zostaną poprawnie napisane, ponieważ w pewnych sytuacjach może to ograniczyć wykonanie i wysiłek.

Jednym z przykładów (który prawie w ogóle nie wpłynąłby na czas wykonania) jest wyśmiewanie zmiennej ENV na potrzeby testu, co należy zrobić tylko raz.

Mam nadzieję, że to pomoże :)

Sójka
źródło
Dla użytkowników minitest, którymi jestem, ale nie zauważyłem tagu RSpec tego pytania i odpowiedzi, before(:all)opcja nie istnieje w programie Minitest. Oto kilka obejść w komentarzach: github.com/seattlerb/minitest/issues/61
MrYoshiji
Odnośnik do artykułu jest martwym linkiem: \
Dylan Pierce,
Dzięki @DylanPierce. Nie mogę znaleźć żadnych kopii tego artykułu, więc odwołałem się do odpowiedzi SO, która zamiast tego dotyczy tego :)
Jay
Dzięki :) bardzo docenione
Dylan Pierce
1
itsnie jest już w rspec-core. Jest to bardziej nowoczesny, idiomatyczny sposób it { is_expected.to be_awesome }.
Zubin
26

Prawie zawsze wolę let. Post, który łączysz, wskazuje, że letjest również szybszy. Jednak czasami, gdy trzeba wykonać wiele poleceń, mogę użyć, before(:each)ponieważ jego składnia jest bardziej przejrzysta, gdy zaangażowanych jest wiele poleceń.

W twoim przykładzie zdecydowanie wolałbym użyć letzamiast before(:each). Ogólnie rzecz biorąc, gdy wykonywana jest inicjalizacja tylko niektórych zmiennych, lubię używać let.

Spyros
źródło
15

Duża różnica, o której nie wspomniano, polega na tym, że zmienne zdefiniowane za pomocą letnie są tworzone, dopóki nie wywołasz ich po raz pierwszy. Tak więc, podczas gdy before(:each)blok utworzyłby wystąpienie wszystkich zmiennych, letpozwólmy Ci zdefiniować liczbę zmiennych, których możesz użyć w wielu testach, nie tworzy ich automatycznie. Nie wiedząc o tym, Twoje testy mogą powrócić i ugryźć się, jeśli spodziewasz się, że wszystkie dane zostaną wcześniej załadowane. W niektórych przypadkach możesz nawet chcieć zdefiniować kilka letzmiennych, a następnie użyć before(:each)bloku do wywołania każdej letinstancji, aby upewnić się, że dane są dostępne na początek.

mikeweber
źródło
20
Możesz użyć let!do zdefiniowania metod, które są wywoływane przed każdym przykładem. Zobacz dokumentację RSpec .
Jim Stewart
3

Wygląda na to, że używasz Machinist. Uwaga, możesz zauważyć pewne problemy z marką! wewnątrz let (wersja non-bang) dzieje się poza globalną transakcją urządzeń (jeśli używasz również urządzeń transakcyjnych), co powoduje uszkodzenie danych do innych testów.

Tim Connor
źródło