RSpec let () ne zaman kullanılır?


448

Örnek değişkenleri ayarlamak için bloklardan önce kullanma eğilimindeyim. Daha sonra bu değişkenleri örneklerim boyunca kullanırım. Geçenlerde geldim let(). RSpec belgelerine göre,

... not edilmiş bir yardımcı yöntem tanımlamak için. Değer, aynı örnekte birden çok çağrıda önbelleğe alınır, ancak örneklerde önbelleğe alınmaz.

Bu, bloklardan önce örnek değişkenleri kullanmaktan nasıl farklıdır? Ayrıca ne zaman let()vs kullanmalısınız before()?


1
Bloklar tembel olarak değerlendirilirken, bloklar her örnekten önce çalışır (genel olarak daha yavaştır). Bloklardan önce kullanmak kişisel tercihe bağlıdır (kodlama stili, alaylar / saplamalar ...). Let blokları genellikle tercih edilir. Let
Nesha Zoric

Bir kancadan önce örnek değişkenleri ayarlamak iyi bir uygulama değildir. Check out betterspecs.org
Allison

Yanıtlar:


605

Her zaman letbirkaç nedenden dolayı bir örnek değişkeni tercih ederim :

  • Örnek değişkenleri referans verildiğinde ortaya çıkar. Bu, örnek değişkeninin yazımını yağlatırsanız, nilince hatalara ve yanlış pozitiflere yol açabilecek yeni bir tane oluşturulacak ve başlatılacaktır . Yana letbir yöntem oluşturur, bir alırsınız NameErrorben tercih bulmak, hangi bunu yanlış yazarlar zaman. Özellikleri yeniden düzenlemeyi de kolaylaştırır.
  • before(:each)Örnek, kancada tanımlanan örnek değişkenlerinden herhangi birini kullanmasa bile, bir kanca her örnekten önce çalışır. Bu genellikle büyük bir sorun değildir, ancak örnek değişkeninin kurulumu uzun zaman alıyorsa, döngüleri boşa harcıyorsunuz demektir. Tarafından tanımlanan yöntem letiçin başlatma kodu yalnızca örnek çağırdığında çalışır.
  • Bir örnekteki yerel bir değişkenden, örnekteki başvuru sözdizimini değiştirmeden doğrudan izin verebilirsiniz. Bir örnek değişkenine yeniden başvuruda bulunursanız, örnekteki nesneye nasıl başvuruda bulunacağınızı değiştirmeniz gerekir (örn @. Bir ekleme ).
  • Bu biraz öznel, ama Mike Lewis'in işaret ettiği gibi, spesifikasyonun okunmasını kolaylaştırdığını düşünüyorum. Bağımlı nesnelerimi tanımlamayı letve bloğumu itgüzel ve kısa tutmayı seviyorum .

İlgili bir bağlantıyı burada bulabilirsiniz: http://www.betterspecs.org/#let


2
Bahsettiğiniz ilk avantajı gerçekten seviyorum, ancak üçüncü avantajı biraz daha açıklayabilir misiniz? Şimdiye kadar gördüğüm örnekler (mongoid özellikleri: github.com/mongoid/mongoid/blob/master/spec/functional/mongoid/… ) tek satır blokları kullanıyor ve "@" kolay okunur.
gönderileceği-hil

6
Dediğim gibi, biraz öznel, ama lettüm bağımlı nesneleri tanımlamak için kullanmak before(:each)ve gerekli yapılandırma veya örnekler tarafından gerekli herhangi bir alay / saplamalar kurmak için kullanmak yararlı buluyorum . Tüm bunları içeren kancadan önce bunu büyük bir tercih ederim. Ayrıca, let(:foo) { Foo.new }daha az gürültülü (ve daha fazla noktaya) sonra before(:each) { @foo = Foo.new }. İşte nasıl kullandığımın bir örneği: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…
Myron Marston

Örnek için teşekkürler, bu gerçekten yardımcı oldu.
gönderilmiş hil

3
Andrew Grimm: doğru, ancak uyarılar tonlarca gürültü üretebilir (yani kullanımınızdan uyarı içermeyen taşlardan). Artı, NoMethodErrorbir uyarı almak için bir almak istiyorum, ama YMMV.
Myron Marston

1
@ Jwan622: bir satır yazarak başlayabilirsiniz , daha sonra satırlarda foo = Foo.new(...)kullanıcılar ve sonra kullanıcılar foo. Daha sonra, aynı örnek grubuna Foo, aynı şekilde somutlaştırılması gereken yeni bir örnek yazarsınız . Bu noktada, çoğaltmayı ortadan kaldırmak için yeniden düzenleme yapmak istersiniz. foo = Foo.new(...)Çizgileri örneklerinizden kaldırabilir let(:foo) { Foo.new(...) }ve örneklerin kullanım şeklini değiştirmeden w / o ile değiştirebilirsiniz foo. Ancak size refactor ise before { @foo = Foo.new(...) }, ile arasındaki örneklerde referansları güncellemeniz foogerekir @foo.
Myron Marston

82

Örnekleri değişkenleri kullanarak arasındaki fark let()olduğunu let()olan yavaş değerlendirilir . Bu let(), tanımladığı yöntem ilk kez çalıştırılıncaya kadar değerlendirilmediği anlamına gelir .

Arasındaki fark beforeve letolmasıdır let()size bir 'basamaklı' tarzında değişkenler grubunun tanımlanması güzel bir yol verir. Bunu yaparak, spesifikasyon kodu basitleştirerek biraz daha iyi görünüyor.


1
Anladım, bu gerçekten bir avantaj mı? Ne olursa olsun, kod her örnek için çalıştırılıyor.
gönderileceği-hil

2
IMO'yu okumak daha kolaydır ve okunabilirlik programlama dillerinde büyük bir faktördür.
Mike Lewis

4
Senthil - let () kullandığınızda aslında her örnekte çalıştırılamaz. Tembel, bu yüzden sadece referans verildiğinde çalıştırılır. Genel olarak konuşmak, bunun bir önemi yoktur, çünkü örnek bir grubun amacı, ortak bir bağlamda birkaç örnek çalıştırmaktır.
David Chelimsky

1
Bu, lether seferinde değerlendirilecek bir şeye ihtiyacınız varsa kullanmamanız gerektiği anlamına mı geliyor ? Örneğin, üst modelde bazı davranışlar tetiklenmeden önce veritabanında bulunmak için bir alt model gerekiyor. Üst model davranışını test ettiğim için testte bu alt modelden bahsetmiyorum. Şu anda let!yöntemi kullanıyorum, ama belki bu kurulumu koymak daha açık olur before(:each)?
gar

2
@gar - Üst öğeyi başlatırken gerekli alt ilişkilendirmeleri oluşturmanıza olanak tanıyan bir Factory (FactoryGirl gibi) kullanırım. Bu şekilde yaparsanız, let () veya bir kurulum bloğu kullanmanız gerçekten önemli değildir. Alt bağlamlarınızdaki her test için HER ŞEY'i kullanmanız gerekmiyorsa let () iyi. Kurulum yalnızca her biri için gereken özelliklere sahip olmalıdır.
Harmon

17

Let () kullanmak için rspec testlerimde örnek değişkenlerin tüm kullanımlarını tamamen değiştirdim. Küçük bir Rspec sınıfını öğretmek için kullanılan bir arkadaşım için kısa bir örnek yazdım: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

Buradaki diğer cevapların bazılarının söylediği gibi, () tembel olarak değerlendirilir, böylece sadece yükleme gerektirenleri yükler. Spesifikasyonu KURUYOR ve daha okunabilir hale getiriyor. Aslında inherited_resource gem tarzında, benim denetleyicileri kullanmak için Rspec let () kodu taşındık. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

Tembel değerlendirmenin yanı sıra, diğer avantajı ActiveSupport :: Concern ve load-everything-in spec / support / behavio ile birlikte uygulamanıza özgü kendi spec mini DSL'nizi oluşturabilmenizdir. Rack ve RESTful kaynaklarına karşı test etmek için yazdım.

Kullandığım strateji Factory-everything (Machinist + Forgery / Faker aracılığıyla). Bununla birlikte, özelliklerin daha hızlı çalışmasına izin vererek, tüm örnek gruplar için fabrikaları önceden yüklemek için önce (: her) bloklarla birlikte kullanmak mümkündür: http://makandra.com/notes/770-taking-advantage -of-RSpec-s-let-in-öncesi-blokları


Hey Ho-Sheng, aslında bu soruyu sormadan önce blog yazılarınızın birkaçını okudum. Sizin # spec/friendship_spec.rbve # spec/comment_spec.rbörneğinizle ilgili olarak, onu daha az okunabilir hale getirmediklerini düşünmüyor musunuz? Nereden usersgeldiği hakkında bir fikrim yok ve daha derine inmek gerekecek.
gönderileceği-hil

İlk düzine kadar insan, daha okunaklı bulmak için formatı gösterdim ve birkaç tanesi onunla yazmaya başladı. Şimdi de bu sorunların bazılarına çalıştırmak let () kullanarak yeterli spec kodu var. Kendimi örneğe giderken buldum ve en içteki örnek grubundan başlayarak kendimi tekrar çalışıyorum. Yüksek derecede programlanabilir bir ortam kullanmakla aynı beceridir.
Ho-Sheng Hsiao

2
Karşılaştığım en büyük sorun yanlışlıkla {} yerine let (: Subject) {} kullanmak. Subject (), let (: Subject) 'dan farklı bir şekilde ayarlanır, ancak let (: Subject) onu geçersiz kılar.
Ho-Sheng Hsiao

1
Eğer koda "detaya inmek" için izin verebilirsiniz, o zaman let () bildirimleri ile bir kodu tarama çok daha hızlı bulacaksınız. Kodu tararken let () bildirimlerini seçmek, koda gömülü @ değişkenleri bulmaktan daha kolaydır. @ Değişkenleri kullanarak, hangi satırların değişkenlere atamayı, hangi satırların değişkenlerin test edilmesini ifade ettiği iyi bir "şekle" sahip değilim. Let () kullanarak tüm atamalar let () ile yapılır, böylece bildirimlerinizin bulunduğu harflerin şekliyle "anında" bilirsiniz.
Ho-Sheng Hsiao

1
Özellikle benimki (gedit) gibi bazı editörler örnek değişkenlerini vurguladığından, örnek değişkenlerinin seçilmesinin daha kolay olduğu konusunda aynı argümanı yapabilirsiniz. Ben kullanıyorum let()son birkaç gün ve şahsen Myron sözü ilk menfaatleri dışında, bir fark görmüyorum. Ve belki de tembel olduğum için ve başka bir dosyayı açmak zorunda kalmadan kodu açık bir şekilde görmeyi sevdiğim için gitmesine ve ne yapmamaya emin değilim. Yorumlarınız için teşekkürler.
sent-hil

13

Akılda tutmak önemlidir let içinde tembel değerlendirilir ve koyarak yan etki yöntemleri aksi takdirde değiştirmek için mümkün olmaz olan let için (: her) önce kolayca. Let kullanabilirsiniz ! yerine let her senaryoya önce değerlendirilir, böylece.


8

Genel olarak, let()daha güzel bir sözdizimidir ve @nameher yere semboller yazmanızı sağlar . Ancak, uyarı emptor! Ben let()de ince böcek (veya en azından baş çizilmeye) tanıtıyor bulduk çünkü değişken bunu kullanmaya çalışana kadar gerçekten mevcut değil ... Masal işareti söyle: eğer bir ekleme putssonra let()değişkenin doğru olduğunu görmek için bir spec sağlar geçmek, ancak putsspec başarısız olmadan - bu inceliği bulduk.

Ben de let()her koşulda önbellek görünmüyor bulduk ! Blogumda yazdım: http://technicaldebt.com/?p=1242

Belki de sadece benim?


9
lether zaman tek bir örnek süresince değeri hatırlar. Birden çok örnekte değeri hatırlamaz. before(:all)aksine, başlatılmış bir değişkeni birden fazla örnekte yeniden kullanmanızı sağlar.
Myron Marston

2
let'i kullanmak istiyorsanız (şimdi en iyi uygulama olarak görülüyor), ancak hemen somutlaştırılmak için belirli bir değişkene ihtiyacınız varsa, bunun let!için tasarlanmıştır. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…
Jacob

6

let, temelde bir Proc olarak işlevseldir. Ayrıca önbelleğe alınmış.

Bir tane olsun hemen buldum let ... Değişikliği değerlendiren bir Spec blok içinde.

let(:object) {FactoryGirl.create :object}

expect {
  post :destroy, id: review.id
}.to change(Object, :count).by(-1)

letBekleme bloğunuzun dışında arama yaptığınızdan emin olmanız gerekir . yani FactoryGirl.createizin bloğunu çağırıyorsun . Bunu genellikle nesnenin kalıcı olduğunu doğrulayarak yaparım.

object.persisted?.should eq true

Aksi takdirde, letblok ilk kez çağrıldığında, tembel örnekleme nedeniyle veritabanında bir değişiklik gerçekleşir.

Güncelleme

Sadece bir not ekliyorum. Kod golf oynarken veya bu durumda bu cevapla rspec golf oynarken dikkatli olun .

Bu durumda, sadece nesnenin yanıt verdiği bazı yöntemi çağırmak zorundayım. Bu yüzden _.persisted?nesne üzerindeki _ yöntemini gerçeği olarak çağırıyorum. Yapmaya çalıştığım tek şey nesneyi somutlaştırmak. Boş diyebilir misin? ya da nil? çok. Mesele test değil, nesneyi arayarak hayata getiriyor.

Yani refactor olamazsın

object.persisted?.should eq true

olmak

object.should be_persisted 

çünkü nesne somutlaştırılmadı ... tembel. :)

Güncelleme 2

izin kaldıraç ! bu sorunu tamamen önlemek için anında nesne oluşturma sözdizimi . Olsa da çarptım izin tembellik amacı bir çok yenilgi unutmayın.

Ayrıca bazı durumlarda , size ek seçenekler sunabileceğinden, izin vermek yerine konu sözdizimini kullanmak isteyebilirsiniz.

subject(:object) {FactoryGirl.create :object}

2

Joseph için not - eğer bir nesne içinde veritabanı nesneleri oluşturuyorsanız, before(:all)bunlar bir işlemde yakalanmayacaktır ve test veritabanınızda daha fazla bilgi bırakma olasılığınız daha yüksektir. before(:each)Bunun yerine kullanın .

Let ve tembel değerlendirmesinin kullanılmasının diğer nedeni, karmaşık bir nesneyi alabilir ve bu çok tutarlı örnekte olduğu gibi, bağlamlarda izinleri geçersiz kılarak tek tek parçaları test edebilmenizdir:

context "foo" do
  let(:params) do
     { :foo => foo,  :bar => "bar" }
  end
  let(:foo) { "foo" }
  it "is set to foo" do
    params[:foo].should eq("foo")
  end
  context "when foo is bar" do
    let(:foo) { "bar" }
    # NOTE we didn't have to redefine params entirely!
    it "is set to bar" do
      params[:foo].should eq("bar")
    end
  end
end

1
Önce +1: (tüm) hatalar geliştiricilerimizin zamanının birçok gününü boşa harcadı.
Michael Durrant

1

Varsayılan olarak "önce" anlamına gelir before(:each). Ref Rspec Kitabı, telif hakkı 2010, sayfa 228.

before(scope = :each, options={}, &block)

"İt" bloğunda veri oluşturmak için yöntemi before(:each)çağırmak zorunda kalmadan her örnek grup için bazı verileri tohum için kullanın let. Bu durumda "it" bloğunda daha az kod.

Kullandığım letbazı bazı örneklerde verileri diğerlerini değil istiyorum.

Hem önce hem de izin "it" blokları KURUTMAK için mükemmeldir.

Herhangi bir karışıklığı önlemek için, "let" ile aynı değildir before(:all). "Let", her örnek için ("it") yöntemini ve değerini yeniden değerlendirir, ancak değeri aynı örnekte birden çok çağrıda önbelleğe alır. Bununla ilgili daha fazla bilgiyi buradan edinebilirsiniz: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let


1

Burada ses çıkaran: 5 yıl süren rspec'den sonra letçok sevmiyorum .

1. Tembel değerlendirme genellikle test kurulumunu kafa karıştırıcı yapar

Kurulumda bildirilen bazı şeyler gerçekte durumu etkilemezken, diğerleri ise kurulum hakkında akıl yürütmek zorlaşır.

Sonunda, engellenme birisi dışına sadece değişiklikleri letiçin let!kendi spec çalışma almak için (tembel değerlendirme yapılmadan aynı şey). Bu onlar için işe yararsa, yeni bir alışkanlık doğar: eski bir pakete yeni bir özellik eklendiğinde ve işe yaramadığında, yazarın denediği ilk şey rastgele letçağrılara patlama eklemektir .

Çok yakında tüm performans avantajları ortadan kalktı.

2. Özel sözdizimi rspec olmayan kullanıcılar için alışılmadık bir durumdur

Ruby'e ekibime rspec'in püf noktalarını öğretmeyi tercih ederim. Örnek değişkenleri veya yöntem çağrıları bu projenin her yerinde ve diğerlerinde letyararlıdır , sözdizimi yalnızca rspec'de yararlı olacaktır.

3. "Yararları" kolayca iyi tasarım değişiklikleri görmezden izin

let()defalarca yaratmak istemediğimiz pahalı bağımlılıklar için iyidir. Ayrıca subject, çoklu argüman yöntemlerine tekrarlanan çağrıları kurutmanıza izin vererek iyi eşleşir.

Birçok kez tekrarlanan pahalı bağımlılıklar ve büyük imzalara sahip yöntemler, kodu daha iyi hale getirebileceğimiz her iki noktadır:

  • belki de kodumun geri kalanından bir bağımlılık izole eden yeni bir soyutlama getirebilirim (daha az testin buna ihtiyacı olduğu anlamına gelir)
  • belki test edilen kod çok fazla yapıyor
  • belki uzun bir ilkel listesi yerine daha akıllı nesneler enjekte etmem gerekiyor
  • belki söyleme-sorma ihlali var
  • belki pahalı kod daha hızlı yapılabilir (daha nadir - burada erken optimizasyona dikkat edin)

Tüm bu durumlarda, rspec magic yatıştırıcı bir balsamı ile zor testlerin semptomunu ele alabilirim veya sebebini ele alabilirim. Son birkaç yılın çok üzerinde çok eski geçirdiğimi hissediyorum ve şimdi daha iyi bir kod istiyorum.

Orijinal soruyu cevaplamak için: Yapmamayı tercih ederim, ama hala kullanıyorum let. Ben çoğunlukla takımın geri kalanı stili ile uyum için kullanabilirsiniz (yani çok sık bu yüzden dünyanın en Raylar programcılar kendi RSpec sihirli içine artık derin gibi görünüyor). Bazen kontrol etmediğim bazı kodlara bir test eklediğimde veya daha iyi bir soyutlamaya yeniden odaklanmak için zamanım olmadığında kullanıyorum: yani tek seçenek ağrı kesici olduğunda.


0

Kullandığım letbağlamları kullanarak API özellikleri benim HTTP 404 yanıtlarını test etmek.

Kaynağı oluşturmak için kullanıyorum let!. Ama kaynak tanımlayıcısını saklamak için kullanıyorum let. Nasıl göründüğüne bir göz atın:

let!(:country)   { create(:country) }
let(:country_id) { country.id }
before           { get "api/countries/#{country_id}" }

it 'responds with HTTP 200' { should respond_with(200) }

context 'when the country does not exist' do
  let(:country_id) { -1 }
  it 'responds with HTTP 404' { should respond_with(404) }
end

Bu, özellikleri temiz ve okunabilir tutar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.