RSpec: ความแตกต่างระหว่าง let และ a before block คืออะไร?


90

อะไรคือความแตกต่างระหว่างletและbeforeบล็อกใน RSpec?

และควรใช้เมื่อใด?

แนวทางที่ดีจะเป็นอย่างไร (let หรือ before) ในตัวอย่างด้านล่างนี้?

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

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

ฉันศึกษาโพสต์ stackoverflowนี้

แต่เป็นการดีหรือไม่ที่จะกำหนด let สำหรับการเชื่อมโยงเช่นด้านบน


1
โดยทั่วไปผู้ที่ไม่ชอบตัวแปรอินสแตนซ์จะใช้ "let" โปรดทราบว่าคุณควรพิจารณาใช้ FactoryGirl หรือเครื่องมือที่คล้ายกัน
หยุดหายใจ

คำตอบ:


147

ดูเหมือนว่าผู้คนจะอธิบายวิธีพื้นฐานบางอย่างที่แตกต่างกันออกไป แต่ก็before(:all)ไม่ได้อธิบายว่าทำไมจึงควรใช้

ฉันเชื่อว่าตัวแปรอินสแตนซ์ไม่มีสถานที่ที่ใช้ในข้อกำหนดส่วนใหญ่ส่วนหนึ่งมาจากเหตุผลที่กล่าวถึงในคำตอบนี้ดังนั้นฉันจะไม่พูดถึงตัวแปรเหล่านี้เป็นตัวเลือกที่นี่

ปล่อยให้บล็อก

โค้ดภายในletบล็อกจะถูกเรียกใช้เมื่อมีการอ้างอิงเท่านั้นและขี้เกียจโหลดซึ่งหมายความว่าการสั่งบล็อกเหล่านี้ไม่เกี่ยวข้อง สิ่งนี้ช่วยให้คุณมีพลังงานจำนวนมากในการลดการตั้งค่าซ้ำ ๆ ผ่านข้อกำหนดของคุณ

ตัวอย่างหนึ่ง (ที่เล็กมากและสร้างขึ้น) คือ:

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

คุณสามารถเห็นได้ว่าhas_moustacheมีการกำหนดไว้แตกต่างกันในแต่ละกรณี แต่ไม่จำเป็นต้องทำซ้ำsubjectคำจำกัดความ สิ่งสำคัญที่ควรทราบก็คือletบล็อกสุดท้ายที่กำหนดในบริบทปัจจุบันจะถูกใช้ นี่เป็นสิ่งที่ดีสำหรับการตั้งค่าเริ่มต้นที่จะใช้สำหรับข้อกำหนดส่วนใหญ่ซึ่งสามารถเขียนทับได้หากจำเป็น

ตัวอย่างเช่นการตรวจสอบค่าส่งคืนcalculate_awesomeว่าส่งผ่านpersonโมเดลด้วยการtop_hatตั้งค่าเป็นจริง แต่ไม่มีหนวดจะเป็น:

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

สิ่งที่ควรทราบอีกประการหนึ่งเกี่ยวกับการบล็อกให้ไม่ควรใช้หากคุณกำลังค้นหาบางสิ่งที่บันทึกไว้ในฐานข้อมูล (กล่าวคือLibrary.find_awesome_people(search_criteria)) เนื่องจากจะไม่ถูกบันทึกลงในฐานข้อมูลเว้นแต่จะได้รับการอ้างอิงแล้ว let!หรือbeforeบล็อกคือสิ่งที่ควรใช้ที่นี่

นอกจากนี้ไม่เคยใช้beforeเพื่อเรียกการทำงานของletบล็อกนี่คือสิ่งที่let!สร้างขึ้นเพื่อ!

ปล่อย! บล็อก

let!บล็อกจะถูกดำเนินการตามลำดับที่กำหนดไว้ (เหมือนกับบล็อกก่อนหน้า) ความแตกต่างหลักอย่างหนึ่งของก่อนบล็อกคือคุณได้รับการอ้างอิงอย่างชัดเจนถึงตัวแปรนี้แทนที่จะต้องถอยกลับไปที่ตัวแปรอินสแตนซ์

เช่นเดียวกับletบล็อกหากlet!กำหนดหลายบล็อกด้วยชื่อเดียวกันล่าสุดคือสิ่งที่จะใช้ในการดำเนินการ ความแตกต่างหลักคือการlet!บล็อกจะถูกดำเนินการหลายครั้งหากใช้เช่นนี้ในขณะที่letบล็อกจะดำเนินการครั้งสุดท้ายเท่านั้น

ก่อน (: each) บล็อก

before(:each)เป็นค่าดีฟอลต์ก่อนบล็อกดังนั้นจึงสามารถอ้างอิงได้before {}แทนที่จะระบุเต็มbefore(:each) {}ในแต่ละครั้ง

เป็นความชอบส่วนตัวของฉันที่จะใช้beforeบล็อกในสถานการณ์หลักบางอย่าง ฉันจะใช้ก่อนบล็อกถ้า:

  • ฉันใช้การเยาะเย้ยถากถางหรือดับเบิล
  • มีการตั้งค่าขนาดที่เหมาะสม (โดยทั่วไปนี่เป็นสัญญาณว่าลักษณะโรงงานของคุณยังไม่ได้ตั้งค่าอย่างถูกต้อง)
  • มีตัวแปรหลายตัวที่ฉันไม่จำเป็นต้องอ้างอิงโดยตรง แต่จำเป็นสำหรับการตั้งค่า
  • ฉันกำลังเขียนการทดสอบตัวควบคุมการทำงานในรางและฉันต้องการดำเนินการตามคำขอเฉพาะเพื่อทดสอบ (เช่นbefore { get :index }) แม้ว่าคุณจะสามารถใช้subjectสิ่งนี้ได้ในหลาย ๆ กรณี แต่บางครั้งก็รู้สึกชัดเจนกว่าหากคุณไม่ต้องการข้อมูลอ้างอิง

หากคุณพบว่าตัวเองเขียนbeforeบล็อกขนาดใหญ่สำหรับข้อมูลจำเพาะของคุณให้ตรวจสอบโรงงานของคุณและตรวจสอบให้แน่ใจว่าคุณเข้าใจลักษณะและความยืดหยุ่นของพวกเขาอย่างถ่องแท้

ก่อน (: ทั้งหมด) บล็อก

สิ่งเหล่านี้จะถูกดำเนินการเพียงครั้งเดียวก่อนข้อกำหนดในบริบทปัจจุบัน (และลูก ๆ ) สิ่งเหล่านี้สามารถใช้เพื่อประโยชน์อย่างมากหากเขียนอย่างถูกต้องเนื่องจากมีบางสถานการณ์ที่สามารถลดการดำเนินการและความพยายามลงได้

ตัวอย่างหนึ่ง (ซึ่งแทบจะไม่มีผลต่อเวลาดำเนินการเลย) คือการเยาะเย้ยตัวแปร ENV สำหรับการทดสอบซึ่งคุณควรทำเพียงครั้งเดียว

หวังว่าจะช่วยได้ :)


สำหรับผู้ใช้ที่มีขนาดเล็กที่สุดซึ่งฉันเป็น แต่ฉันไม่สังเกตเห็นแท็ก RSpec ของคำถาม & คำตอบนี้before(:all)ไม่มีตัวเลือกใน Minitest นี่คือวิธีแก้ปัญหาบางประการในความคิดเห็น: github.com/seattlerb/minitest/issues/61
MrYoshiji

บทความที่อ้างถึงเป็นลิงค์ที่ตายแล้ว: \
Dylan Pierce

ขอบคุณ @DylanPierce ฉันไม่พบสำเนาของบทความนั้นเลยฉันจึงได้อ้างถึงคำตอบ SO ซึ่งกล่าวถึงสิ่งนี้แทน :)
Jay

ขอบคุณ :) ชื่นชมมาก
Dylan Pierce

1
itsไม่อยู่ใน rspec-core อีกต่อไป it { is_expected.to be_awesome }ที่ทันสมัยมากขึ้นวิธีสำนวนคือ
Zubin

26

เกือบตลอดเวลาฉันชอบletมากกว่า โพสต์ที่คุณลิงก์ระบุว่าletเร็วกว่าด้วย อย่างไรก็ตามในบางครั้งเมื่อต้องเรียกใช้คำสั่งจำนวนมากฉันสามารถใช้ได้before(:each)เพราะไวยากรณ์ของมันชัดเจนมากขึ้นเมื่อมีหลายคำสั่ง

ในตัวอย่างของคุณฉันต้องการใช้letแทนbefore(:each). letโดยทั่วไปเมื่อเพียงบางส่วนตัวแปรเริ่มต้นจะทำผมมักจะชอบใช้


15

ความแตกต่างอย่างมากที่ยังไม่ได้กล่าวถึงคือตัวแปรที่กำหนดไว้letจะไม่ได้รับการสร้างอินสแตนซ์จนกว่าคุณจะเรียกมันในครั้งแรก ดังนั้นในขณะที่before(:each)บล็อกจะสร้างอินสแตนซ์ของตัวแปรทั้งหมดletให้คุณกำหนดตัวแปรจำนวนหนึ่งที่คุณอาจใช้ในการทดสอบหลายครั้ง แต่ก็ไม่ได้สร้างอินสแตนซ์ให้โดยอัตโนมัติ หากไม่ทราบสิ่งนี้การทดสอบของคุณอาจกลับมากัดตัวเองได้หากคุณคาดหวังว่าข้อมูลทั้งหมดจะถูกโหลดไว้ล่วงหน้า ในบางกรณีคุณอาจต้องการกำหนดletตัวแปรหลายตัวจากนั้นใช้before(:each)บล็อกเพื่อเรียกแต่ละletอินสแตนซ์เพื่อให้แน่ใจว่าข้อมูลพร้อมใช้งานสำหรับการเริ่มต้น


20
คุณสามารถใช้let!เพื่อกำหนดวิธีการที่เรียกก่อนแต่ละตัวอย่าง ดูเอกสาร RSpec
Jim Stewart

3

ดูเหมือนว่าคุณกำลังใช้ Machinist ระวังคุณอาจพบปัญหาเกี่ยวกับ make! ภายในของ let (เวอร์ชันที่ไม่เกิดปัญหา) ที่เกิดขึ้นนอกธุรกรรมการติดตั้งทั่วโลก (หากคุณใช้การติดตั้งแบบทรานแซกชันเช่นกัน) ทำให้ข้อมูลสำหรับการทดสอบอื่น ๆ ของคุณเสียหาย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.