Kapan menggunakan RSpec let ()?

447

Saya cenderung menggunakan sebelum blok untuk mengatur variabel contoh. Saya kemudian menggunakan variabel-variabel tersebut di seluruh contoh saya. Saya baru-baru ini datang let(). Menurut dokumen RSpec, sudah biasa

... untuk menentukan metode pembantu memoized. Nilai akan di-cache di beberapa panggilan dalam contoh yang sama tetapi tidak di seluruh contoh.

Bagaimana ini berbeda dari menggunakan variabel instan di sebelum blok? Dan juga kapan Anda harus menggunakan let()vs before()?

dikirim-hil
sumber
1
Biarkan blok dievaluasi malas, sementara sebelum blok berjalan sebelum masing-masing contoh (mereka secara keseluruhan lebih lambat). Menggunakan sebelum blok tergantung pada preferensi pribadi (gaya pengkodean, mengejek / bertopik ...). Biarkan blok biasanya lebih disukai. Anda dapat memeriksa info yang
Nesha Zoric
Ini bukan praktik yang baik untuk mengatur variabel instan di hook sebelum Lihatlah betterspecs.org
Allison

Jawaban:

604

Saya selalu lebih suka letvariabel instan karena beberapa alasan:

  • Variabel instance muncul saat direferensikan. Ini berarti bahwa jika Anda mengeja ejaan variabel instan, yang baru akan dibuat dan diinisialisasi nil, yang dapat menyebabkan bug halus dan positif palsu. Karena letmembuat metode, Anda akan mendapatkan NameErrorketika Anda salah mengeja, yang menurut saya lebih disukai. Itu membuatnya lebih mudah untuk refactor spesifikasi juga.
  • Sebuah before(:each)kait akan dijalankan sebelum setiap contoh, bahkan jika contoh tersebut tidak menggunakan variabel instan apa pun yang ditentukan dalam hook. Ini biasanya bukan masalah besar, tetapi jika setup variabel instan membutuhkan waktu lama, maka Anda membuang-buang siklus. Untuk metode yang ditentukan oleh let, kode inisialisasi hanya berjalan jika contoh memanggilnya.
  • Anda bisa refactor dari variabel lokal dalam contoh langsung menjadi let tanpa mengubah sintaks referensi dalam contoh. Jika Anda refactor ke variabel instan, Anda harus mengubah cara Anda mereferensikan objek dalam contoh (misalnya menambahkan sebuah @).
  • Ini agak subyektif, tetapi seperti yang ditunjukkan Mike Lewis, saya pikir itu membuat spec lebih mudah dibaca. Saya suka organisasi mendefinisikan semua objek dependen saya dengan letdan menjaga itblok saya bagus dan pendek.

Tautan terkait dapat ditemukan di sini: http://www.betterspecs.org/#let

Myron Marston
sumber
2
Saya sangat suka keuntungan pertama yang Anda sebutkan, tetapi bisakah Anda menjelaskan lebih banyak tentang yang ketiga? Sejauh ini contoh yang saya lihat (spesifikasi mongoid: github.com/mongoid/mongoid/blob/master/spec/functional/mongoid/… ) menggunakan blok baris tunggal dan saya tidak melihat bagaimana tidak memiliki "@" membuatnya lebih mudah dibaca.
Dikirim-hil
6
Seperti yang saya katakan, ini agak subyektif, tetapi saya merasa terbantu menggunakan letuntuk mendefinisikan semua objek dependen, dan before(:each)untuk mengatur konfigurasi yang diperlukan atau mock / stubs yang dibutuhkan oleh contoh. Saya lebih suka ini daripada yang besar sebelum kait berisi semua ini. Juga, let(:foo) { Foo.new }kurang berisik (dan lebih tepatnya) before(:each) { @foo = Foo.new }. Berikut adalah contoh bagaimana saya menggunakannya: github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…
Myron Marston
Terima kasih untuk contohnya, itu sangat membantu.
dikirim-hil
3
Andrew Grimm: benar, tetapi peringatan dapat menghasilkan banyak kebisingan (yaitu dari permata yang Anda gunakan yang tidak menjalankan bebas peringatan). Plus, saya lebih suka NoMethodErrormenerima peringatan, tapi YMMV.
Myron Marston
1
@ Jwan622: Anda dapat mulai dengan menulis satu contoh, yang memiliki foo = Foo.new(...)dan kemudian pengguna foopada baris selanjutnya. Kemudian, Anda menulis contoh baru dalam grup contoh yang sama yang juga membutuhkan Fooinstantiated dengan cara yang sama. Pada titik ini, Anda ingin refactor untuk menghilangkan duplikasi. Anda dapat menghapus foo = Foo.new(...)garis dari contoh Anda dan menggantinya dengan tanpa let(:foo) { Foo.new(...) }mengubah cara contoh digunakan foo. Tetapi jika Anda refactor ke before { @foo = Foo.new(...) }Anda juga harus memperbarui referensi dalam contoh dari fooke @foo.
Myron Marston
82

Perbedaan antara menggunakan contoh variabel dan let()bahwa let()adalah malas-dievaluasi . Ini berarti bahwa let()tidak dievaluasi sampai metode yang ditetapkan dijalankan untuk pertama kalinya.

Perbedaan antara beforedan letadalah yang let()memberi Anda cara yang bagus untuk mendefinisikan sekelompok variabel dalam gaya 'cascading'. Dengan melakukan ini, spek terlihat sedikit lebih baik dengan menyederhanakan kode.

Mike Lewis
sumber
1
Begitu ya, apakah itu benar-benar suatu keuntungan? Kode sedang dijalankan untuk setiap contoh tanpa memperhatikan.
dikirim-hil
2
Lebih mudah membaca IMO, dan keterbacaan adalah faktor besar dalam bahasa pemrograman.
Mike Lewis
4
Senthil - sebenarnya tidak selalu berjalan di setiap contoh saat Anda menggunakan let (). Ini malas, jadi itu hanya berjalan jika direferensikan. Secara umum, ini tidak terlalu menjadi masalah karena tujuan dari contoh grup adalah menjalankan beberapa contoh dalam konteks yang sama.
David Chelimsky
1
Jadi apakah itu berarti Anda tidak boleh menggunakan letjika Anda perlu sesuatu untuk dievaluasi setiap waktu? misalnya saya memerlukan model anak untuk hadir dalam database sebelum beberapa perilaku dipicu pada model induk. Saya tidak perlu merujuk model anak itu dalam tes, karena saya menguji perilaku model orang tua. Saat ini saya menggunakan let!metode ini, tetapi mungkin akan lebih eksplisit untuk meletakkan pengaturan itu before(:each)?
gar
2
@gar - Saya akan menggunakan Factory (seperti FactoryGirl) yang memungkinkan Anda untuk membuat asosiasi anak yang diperlukan saat Anda membuat instance induk. Jika Anda melakukannya dengan cara ini, maka tidak masalah jika Anda menggunakan let () atau blok pengaturan. Let () bagus jika Anda tidak perlu menggunakan SEMUA untuk setiap tes dalam sub-konteks Anda. Pengaturan seharusnya hanya memiliki apa yang diperlukan untuk masing-masing.
Harmon
17

Saya telah sepenuhnya mengganti semua penggunaan variabel instan dalam tes rspec saya untuk menggunakan let (). Saya telah menulis contoh kilat untuk seorang teman yang menggunakannya untuk mengajar kelas Rspec kecil: http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

Seperti beberapa jawaban lain di sini mengatakan, biarkan () malas dievaluasi sehingga hanya akan memuat yang membutuhkan pemuatan. Ini KERING spesifikasi dan membuatnya lebih mudah dibaca. Sebenarnya saya telah mem-porting kode let () Rspec untuk digunakan di controller saya, dengan style permata inherited_resource. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

Bersamaan dengan evaluasi malas, keuntungan lainnya adalah, dikombinasikan dengan ActiveSupport :: Concern, dan spec-support-behavior / load-everything-in, Anda dapat membuat spec mini-DSL Anda sendiri yang spesifik untuk aplikasi Anda. Saya sudah menulis yang untuk menguji terhadap sumber daya Rack dan RESTful.

Strategi yang saya gunakan adalah Factory-everything (via Machinist + Forgery / Faker). Namun, dimungkinkan untuk menggunakannya dalam kombinasi dengan sebelum (: masing-masing) blok untuk memuat pabrik ke seluruh set kelompok contoh, memungkinkan spesifikasi berjalan lebih cepat: http://makandra.com/notes/770-taking-keuntungan -of-rspec-s-let-in-sebelum-blok

Ho-Sheng Hsiao
sumber
Hai Ho-Sheng, saya sebenarnya membaca beberapa posting blog Anda sebelum mengajukan pertanyaan ini. Mengenai Anda # spec/friendship_spec.rbdan # spec/comment_spec.rbcontohnya, tidakkah menurut Anda mereka membuatnya kurang mudah dibaca? Saya tidak tahu dari mana usersdatangnya dan perlu menggali lebih dalam.
dikirim-hil
Selusin orang pertama yang saya tunjukkan formatnya untuk semua merasa lebih mudah dibaca, dan beberapa dari mereka mulai menulis dengannya. Saya punya kode spec yang cukup sekarang menggunakan let () yang saya jalankan ke beberapa masalah itu juga. Saya menemukan diri saya pergi ke contoh, dan mulai dari kelompok contoh paling dalam, bekerjalah sendiri. Ini adalah keterampilan yang sama dengan menggunakan lingkungan yang sangat terprogram.
Ho-Sheng Hsiao
2
Gotcha terbesar yang saya temui adalah secara tidak sengaja menggunakan let (: subject) {} alih-alih subject {}. subject () diatur secara berbeda dari let (: subject), tetapi let (: subject) akan menimpanya.
Ho-Sheng Hsiao
1
Jika Anda dapat melepaskan "menelusuri" ke dalam kode, maka Anda akan menemukan pemindaian kode dengan pernyataan let () jauh lebih cepat. Lebih mudah untuk memilih membiarkan () mendeklarasikan ketika memindai kode daripada menemukan @variables tertanam ke dalam kode. Dengan menggunakan @variables, saya tidak memiliki "bentuk" yang bagus untuk baris mana yang merujuk pada penetapan ke variabel dan baris mana yang merujuk pada pengujian variabel. Menggunakan let (), semua tugas dilakukan dengan let () sehingga Anda tahu "langsung" dengan bentuk huruf di mana deklarasi Anda berada.
Ho-Sheng Hsiao
1
Anda dapat membuat argumen yang sama tentang variabel instance menjadi lebih mudah untuk dipilih, terutama karena beberapa editor, seperti milikku (gedit) menyoroti variabel instance. Saya telah menggunakan let()beberapa hari terakhir dan secara pribadi saya tidak melihat perbedaan, kecuali untuk keuntungan pertama yang disebutkan Myron. Dan saya tidak begitu yakin tentang melepaskan dan apa yang tidak, mungkin karena saya malas dan saya suka melihat kode dimuka tanpa harus membuka file lain. Terima kasih atas komentar anda
Dikirim-hil
13

Penting untuk diingat bahwa membiarkan malas dievaluasi dan tidak memasukkan metode efek samping di dalamnya jika tidak Anda tidak akan dapat mengubah dari membiarkan ke sebelumnya (: masing-masing) dengan mudah. Anda bisa menggunakan let! alih-alih membiarkannya dievaluasi sebelum setiap skenario.

pisaruk
sumber
8

Secara umum, let()ini adalah sintaks yang lebih bagus, dan ini menghemat Anda mengetik @namesimbol di semua tempat. Tapi, emptor peringatan! Saya telah menemukan let()juga memperkenalkan bug halus (atau setidaknya menggaruk kepala) karena variabel tidak benar-benar ada sampai Anda mencoba menggunakannya ... Katakan tanda dongeng: jika menambahkan putssetelah let()untuk melihat bahwa variabel benar memungkinkan spec untuk lulus, tetapi tanpa putsspesifikasi gagal - Anda telah menemukan kehalusan ini.

Saya juga menemukan bahwa let()sepertinya tidak ada cache dalam semua keadaan! Saya menulisnya di blog saya: http://technicaldebt.com/?p=1242

Mungkin hanya saya?

Jon Kern
sumber
9
letselalu mencatat nilai selama durasi satu contoh. Itu tidak menghafal nilai di beberapa contoh. before(:all), sebaliknya, memungkinkan Anda untuk menggunakan kembali variabel yang diinisialisasi dalam banyak contoh.
Myron Marston
2
jika Anda ingin menggunakan let (seperti yang sekarang dianggap praktik terbaik), tetapi perlu variabel tertentu untuk segera dipakai, itulah yang let!dirancang untuk. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…
Jacob
6

biarkan fungsional karena pada dasarnya Proc. Juga di-cache.

Satu gotcha saya temukan langsung dengan let ... Dalam blok Spec yang mengevaluasi perubahan.

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

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

Anda harus memastikan untuk menelepon di letluar blok harapan Anda. yaitu Anda menelepon FactoryGirl.createdi blok let Anda. Saya biasanya melakukan ini dengan memverifikasi objek tetap ada.

object.persisted?.should eq true

Kalau tidak, ketika letblok disebut pertama kali perubahan dalam database akan benar-benar terjadi karena kemalasan malas.

Memperbarui

Hanya menambahkan catatan. Berhati-hatilah bermain golf kode atau dalam hal ini golf rspec dengan jawaban ini.

Dalam hal ini, saya hanya perlu memanggil beberapa metode yang merespon objek. Jadi saya memanggil _.persisted?metode _ pada objek sebagai kebenarannya. Yang saya coba lakukan adalah instantiate objek. Anda bisa menelepon kosong? atau nihil? terlalu. Intinya bukan ujian tetapi membawa objek kehidupan dengan menyebutnya.

Jadi kamu tidak bisa refactor

object.persisted?.should eq true

menjadi

object.should be_persisted 

sebagai objek belum instantiated ... malas. :)

Perbarui 2

memanfaatkan let! sintaks untuk pembuatan objek instan, yang harus menghindari masalah ini sama sekali. Perhatikan bahwa itu akan mengalahkan banyak tujuan kemalasan dari let non banged.

Juga dalam beberapa kasus Anda mungkin benar-benar ingin memanfaatkan sintaks subjek alih-alih membiarkan karena dapat memberi Anda opsi tambahan.

subject(:object) {FactoryGirl.create :object}
engineerDave
sumber
2

Catatan untuk Joseph - jika Anda membuat objek database di a before(:all)mereka tidak akan ditangkap dalam transaksi dan Anda lebih cenderung meninggalkan cacat di database pengujian Anda. Gunakan before(:each)sebagai gantinya.

Alasan lain untuk menggunakan let dan evaluasi malasnya adalah agar Anda dapat mengambil objek yang rumit dan menguji masing-masing potongan dengan mengesampingkan let dalam konteks, seperti dalam contoh yang sangat dibuat-buat ini:

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
dotdotdotPaul
sumber
1
Memberi +1 sebelum (: semua) bug telah menghabiskan banyak waktu pengembang kami.
Michael Durrant
1

"sebelum" secara default menyiratkan before(:each) . Ref The Rspec Book, hak cipta 2010, halaman 228.

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

saya menggunakan before(:each) untuk seed beberapa data untuk setiap grup contoh tanpa harus memanggil letmetode untuk membuat data di blok "it". Lebih sedikit kode dalam blok "it" dalam kasus ini.

saya menggunakan let jika saya ingin beberapa data dalam beberapa contoh tetapi tidak pada yang lain.

Baik sebelum dan membiarkan sangat bagus untuk KERINGAN blok "itu".

Untuk menghindari kebingungan, "biarkan" tidak sama dengan before(:all). "Biarkan" mengevaluasi kembali metode dan nilainya untuk setiap contoh ("itu"), tetapi cache nilai di beberapa panggilan dalam contoh yang sama. Anda dapat membaca lebih lanjut di sini: https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let

konyak
sumber
1

Suara dissenting di sini: setelah 5 tahun rspec saya tidak terlalu suka let.

1. Evaluasi malas sering membuat pengaturan tes membingungkan

Menjadi sulit untuk berpikir tentang pengaturan ketika beberapa hal yang telah dinyatakan dalam pengaturan tidak benar-benar mempengaruhi keadaan, sementara yang lain.

Akhirnya, karena frustrasi seseorang hanya berubah letke let!(hal yang sama tanpa evaluasi malas) untuk mendapatkan kerja spesifikasi mereka. Jika ini berhasil bagi mereka, sebuah kebiasaan baru lahir: ketika spec baru ditambahkan ke suite yang lebih lama dan itu tidak berhasil, hal pertama yang penulis coba adalah menambahkan poni ke acaklet panggilan .

Segera semua manfaat kinerja hilang.

2. Sintaks khusus tidak biasa bagi pengguna non-rspec

Saya lebih suka mengajar Ruby kepada tim saya daripada trik rspec. Variabel instan atau panggilan metode berguna di mana-mana dalam proyek ini dan lainnya, letsintaks hanya akan berguna di rspec.

3. "Manfaat" memungkinkan kita untuk dengan mudah mengabaikan perubahan desain yang baik

let()bagus untuk dependensi mahal yang tidak ingin kita buat berulang kali. Ini juga berpasangan dengan baik subject, memungkinkan Anda untuk mengeringkan panggilan berulang ke metode multi-argumen

Ketergantungan yang mahal berulang kali, dan metode dengan tanda tangan besar adalah titik di mana kita bisa membuat kode lebih baik:

  • mungkin saya bisa memperkenalkan abstraksi baru yang mengisolasi ketergantungan dari sisa kode saya (yang berarti lebih sedikit tes yang membutuhkannya)
  • mungkin kode yang diuji terlalu banyak
  • mungkin saya perlu menyuntikkan objek yang lebih cerdas daripada daftar panjang primitif
  • mungkin saya memiliki pelanggaran Tell-Don't-Ask
  • mungkin kode mahal dapat dibuat lebih cepat (lebih jarang - waspadalah terhadap pengoptimalan prematur di sini)

Dalam semua kasus ini, saya dapat mengatasi gejala tes sulit dengan balsem sihir rspec yang menenangkan, atau saya dapat mencoba mengatasi penyebabnya. Saya merasa seperti saya menghabiskan terlalu banyak dari beberapa tahun terakhir pada yang pertama dan sekarang saya ingin beberapa kode yang lebih baik.

Untuk menjawab pertanyaan awal: Saya lebih suka tidak melakukannya, tetapi saya masih menggunakan let. Saya sebagian besar menggunakannya untuk menyesuaikan dengan gaya tim lainnya (sepertinya kebanyakan programmer Rails di dunia sekarang jauh ke dalam sihir rspec mereka sehingga sangat sering). Kadang-kadang saya menggunakannya ketika saya menambahkan tes ke beberapa kode yang saya tidak punya kendali, atau tidak punya waktu untuk refactor ke abstraksi yang lebih baik: yaitu ketika satu-satunya pilihan adalah penghilang rasa sakit.

iftheshoefritz
sumber
0

Saya gunakan letuntuk menguji tanggapan HTTP 404 saya di spesifikasi API saya menggunakan konteks.

Untuk membuat sumber daya, saya menggunakan let!. Tetapi untuk menyimpan pengenal sumber daya, saya menggunakan let. Lihatlah seperti apa tampilannya:

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

Itu membuat spesifikasi tetap bersih dan mudah dibaca.

Vinicius Brasil
sumber