RSpec let ()을 언제 사용합니까?


448

인스턴스 변수를 설정하기 위해 before 블록을 사용하는 경향이 있습니다. 그런 다음 예제에서 해당 변수를 사용합니다. 나는 최근에왔다 let(). RSpec 문서에 따르면

... 메모 화 된 헬퍼 메소드를 정의합니다. 값은 동일한 예제에서 여러 호출에 걸쳐 캐시되지만 예제에서는 캐시되지 않습니다.

이전 블록에서 인스턴스 변수를 사용하는 것과 어떻게 다른가요? 또한 언제 let()vs 를 사용해야 before()합니까?


1
각 예제 전에 블록이 실행되기 전에 블록이 느리게 평가되도록합니다 (전체 속도가 느림). 사전 블록 사용은 개인 취향에 따라 다릅니다 (코딩 스타일, 모형 / 스텁 ...). 일반적으로 블록을 선호하십시오. let
Nesha Zoric

이전 후크에서 인스턴스 변수를 설정하는 것은 좋지 않습니다. 확인 betterspecs.org
앨리슨

답변:


605

나는 let두 가지 이유로 항상 인스턴스 변수를 선호 합니다.

  • 인스턴스 변수는 참조 될 때 존재합니다. 즉, 인스턴스 변수의 맞춤법을 세게 두드리면 새로운 변수가 만들어지고 초기화 nil되어 미묘한 버그와 오 탐지가 발생할 수 있습니다. let메소드를 작성하기 때문에 NameError철자가 틀렸을 때 얻을 수 있습니다. 또한 사양을 쉽게 리팩토링 할 수 있습니다.
  • before(:each)의 예는 후크에 정의 된 인스턴스 변수를 사용하지 않는 경우에도 후크, 각각의 예를하기 전에 실행됩니다. 일반적으로 큰 문제는 아니지만 인스턴스 변수 설정에 시간이 오래 걸리면 사이클이 낭비됩니다. 로 정의 된 메소드의 let경우 초기화 코드는 예제에서 호출 한 경우에만 실행됩니다.
  • 예제의 참조 구문을 변경하지 않고 예제의 로컬 변수에서 직접 let으로 리팩토링 할 수 있습니다. 인스턴스 변수를 리팩토링하는 경우 예제에서 객체를 참조하는 방법을 변경해야합니다 (예 :) @.
  • 이것은 약간 주관적이지만 Mike Lewis가 지적했듯이 스펙을 읽기가 더 쉽다고 생각합니다. 나는 모든 종속 객체를 정의하고 블록을 멋지게 let유지 하는 조직을 좋아합니다 it.

관련 링크는 여기에서 찾을 수 있습니다 : http://www.betterspecs.org/#let


2
나는 당신이 언급 한 첫 번째 장점을 정말로 좋아하지만 세 번째 장점에 대해 조금 더 설명 할 수 있습니까? 지금까지 내가 본 예제 (mongoid specs : github.com/mongoid/mongoid/blob/master/spec/functional/mongoid/… )는 단일 행 블록을 사용하며 "@"가없는 방법은 보이지 않습니다. 읽기 쉽다.
sent-hil

6
내가 말했듯이, 약간 주관적이지만 let모든 종속 객체를 정의하고 before(:each)필요한 구성 또는 예제에 필요한 모의 / 스텁을 설정하는 데 사용 하는 것이 도움이됩니다 . 나는 이것을 모두 포함하기 전에 하나의 큰 것을 선호합니다. 또한 let(:foo) { Foo.new }소음이 적습니다 (그리고 그 이상) before(:each) { @foo = Foo.new }. 사용 방법의 예는 다음과 같습니다. github.com/myronmarston/vcr/blob/v1.7.0/spec/vcr/util/…
Myron Marston

실제로 도움이 된 예를 주셔서 감사합니다.
sent-hil

3
Andrew Grimm : 사실이지만 경고로 인해 많은 소음이 발생할 수 있습니다 (예 : 보석을 사용하여 경고없이 실행되지 않음). 또한 NoMethodError경고를받는 것을 선호 하지만 YMMV입니다.
Myron Marston

1
Jwan622 @ : 당신은이 하나의 예, 쓰기에 의해 시작될 수 있습니다 foo = Foo.new(...)다음 사용자와 foo나중에 라인을. 나중에 동일한 예제 그룹 Foo에서 동일한 방법으로 인스턴스화 해야하는 새 예제를 작성합니다 . 이 시점에서 중복을 제거하기 위해 리팩토링하려고합니다. foo = Foo.new(...)예제 에서 줄을 제거 let(:foo) { Foo.new(...) }하고 예제 사용 방식을 바꾸지 않고 바꿀 수 있습니다 foo. 그러나 리팩토링하는 경우 before { @foo = Foo.new(...) }예제의 참조를에서로 업데이트해야 foo합니다 @foo.
Myron Marston

82

인스턴스 변수 및 사용의 차이 let()즉이 let()있다 으르 평가 . 이는 let()정의 된 메소드가 처음 실행될 때까지 평가되지 않음을 의미합니다 .

의 차이 before와는 letlet()당신에게 '계단식'스타일 변수의 그룹을 정의하는 좋은 방법을 제공합니다. 이렇게하면 코드를 단순화하여 사양이 조금 나아 보입니다.


1
알다시피, 정말 장점입니까? 코드는 각 예제에 관계없이 실행됩니다.
sent-hil

2
IMO를 읽는 것이 더 쉬우 며, 가독성은 프로그래밍 언어에서 큰 요소입니다.
Mike Lewis

4
Senthil-let ()을 사용할 때 모든 예제에서 반드시 실행되는 것은 아닙니다. 게 으르므로 참조 된 경우에만 실행됩니다. 예제 그룹의 요점은 몇 개의 예제가 공통 컨텍스트에서 실행되도록하기 때문에 일반적으로 이것은 중요하지 않습니다.
David Chelimsky

1
let매번 평가해야 할 것이 있으면 사용하지 말아야 합니까? 예를 들어 부모 모델에서 일부 동작이 트리거되기 전에 데이터베이스에 자식 모델이 있어야합니다. 부모 모델 동작을 테스트하기 때문에 테스트에서 해당 자식 모델을 참조 할 필요는 없습니다. 현재 let!대신 방법을 사용하고 있지만 해당 설정을 넣는 것이 더 분명 할 수 있습니다 before(:each).
gar

2
@gar-나는 당신이 부모를 인스턴스화 할 때 필요한 자식 연결을 만들 수있는 FactoryGirl과 같은 Factory를 사용합니다. 이렇게하면 let () 또는 setup 블록을 사용하더라도 문제가되지 않습니다. 하위 컨텍스트에서 각 테스트에 대해 모든 것을 사용할 필요가없는 경우 let ()이 좋습니다. 설정에는 각각에 필요한 것만 있어야합니다.
Harmon

17

let ()을 사용하기 위해 rspec 테스트에서 인스턴스 변수의 모든 사용을 완전히 대체했습니다. 나는 작은 Rspec 클래스를 가르치기 위해 그것을 사용했던 친구를 위해 간단한 예제를 작성했습니다 : http://ruby-lambda.blogspot.com/2011/02/agile-rspec-with-let.html

여기에있는 다른 답변 중 일부에서 알 수 있듯이 let ()은 지연 평가되므로로드 해야하는 것만로드합니다. 사양을 건조시키고 더 읽기 쉽게 만듭니다. 실제로 Rspec let () 코드를 inherited_resource gem 스타일로 컨트롤러에서 사용하도록 포팅했습니다. http://ruby-lambda.blogspot.com/2010/06/stealing-let-from-rspec.html

지연 평가와 함께 ActiveSupport :: Concern 및 모든 사양 / 지원 / 동작과 결합 된 다른 장점은 응용 프로그램에 맞는 고유 한 사양 미니 DSL을 만들 수 있다는 것입니다. Rack 및 RESTful 리소스에 대한 테스트를 위해 작성했습니다.

내가 사용하는 전략은 Factory-everything입니다 (Machinist + Forgery / Faker를 통해). 그러나 before (: each) 블록과 함께 사용하여 전체 예제 그룹 세트에 대한 팩토리를 사전로드하여 스펙을 더 빠르게 실행할 수 있습니다. http://makandra.com/notes/770-taking-advantage -rspec-s-let-in-before-blocks


이봐, 나는 실제로이 질문을하기 전에 당신의 블로그 포스트의 몇몇을 읽었다. 당신 # spec/friendship_spec.rb# spec/comment_spec.rb예제 에 관해서는 , 그들이 그것을 읽기 어렵게 생각하지 않습니까? 어디에서 users왔는지 더 깊이 파고들 필요는 없습니다.
sent-hil

처음 십여 명의 사람들이 형식을 보여 주어 훨씬 더 읽기 쉬운 형식을 보여 주었고 그 중 일부는 그 형식으로 쓰기 시작했습니다. let ()을 사용하여 충분한 사양 코드를 얻었습니다. 나는 자신이 모범을 보았고 가장 안쪽의 모범 그룹에서 시작하여 스스로 일하는 것을 발견했다. 메타 프로그래밍이 가능한 환경을 사용하는 것과 같은 기술입니다.
Ho-Sheng Hsiao

2
내가 겪은 가장 큰 문제는 실수로 {} 주제 대신 let (: subject) {}을 사용하는 것입니다. subject ()는 let (: subject)와 다르게 설정되지만 let (: subject)는 그것을 무시합니다.
Ho-Sheng Hsiao

1
코드를 "드릴 다운"할 수 있다면 let () 선언을 사용하여 코드를 훨씬 더 빠르게 스캔 할 수 있습니다. 코드에 포함 된 @variables를 찾는 것보다 코드를 스캔 할 때 let () 선언을 선택하는 것이 더 쉽습니다. @variables를 사용하면 변수에 할당하는 줄과 변수 테스트를 나타내는 줄에 대한 좋은 "모양"이 없습니다. let ()을 사용하면 모든 할당이 let ()으로 수행되므로 선언이있는 글자의 모양으로 "즉시"알 수 있습니다.
Ho-Sheng Hsiao

1
인스턴스 변수에 대해 같은 주장을 할 수 있습니다. 특히 광산과 같은 일부 편집기는 인스턴스 변수를 강조 표시하기 때문입니다. 나는 let()지난 며칠 동안 사용했으며 개인적으로 Myron이 언급 한 첫 번째 이점을 제외하고는 차이가 보이지 않습니다. 그리고 나는 게으르고 무엇을 놓을 지 확신하지 못합니다. 아마도 게으르고 다른 파일을 열지 않고도 코드를 미리 보는 것을 좋아합니다. 귀하의 의견에 감사드립니다.
sent-hil

13

let 이 게으르게 평가되고 부작용 방법을 넣지 말아야 한다는 것을 명심해야 합니다. 그렇지 않으면 let 에서 before (: each)로 쉽게 변경할 수 없습니다 . let 을 사용할 수 있습니다 ! 대신 할 수 는 각 시나리오 전에 평가 될 수 있도록.


8

일반적으로 let()더 좋은 구문이며 @name모든 곳에서 기호 를 입력하는 것을 저장합니다 . 그러나 주의를 기울이십시오! 나는 발견 let()도 소개 미묘한 버그 (또는 머리를 긁적 적어도) 변수가 않기 때문에 당신이 그것을 사용하려고 할 때까지 실제로 존재하지 ...에게 이야기 기호를 다음을 추가하는 경우 puts(가) 후 let()변수가 올바른지 확인은 사양을 수 있습니다 그러나 puts사양이 없으면 실패합니다.이 미묘함을 발견했습니다.

또한 let()모든 상황에서 캐시되지 않는 것으로 나타났습니다 ! 내 블로그에 작성했습니다 : http://technicaldebt.com/?p=1242

어쩌면 나 일까?


9
let항상 단일 예제 기간 동안 값을 기억합니다. 여러 예제에서 값을 기억하지는 않습니다. before(:all)반대로 여러 예제에서 초기화 된 변수를 재사용 할 수 있습니다.
Myron Marston

2
let (현재 모범 사례로 간주 됨)을 사용하고 싶지만 즉시 인스턴스화 할 특정 변수가 필요한 경우 let!설계된 것입니다. relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/…
Jacob

6

let은 본질적으로 Proc로 기능합니다. 또한 캐시되었습니다.

한 가지 문제는 let.

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

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

let예상 블록 외부 로 전화해야합니다 . 즉, 당신은 FactoryGirl.create당신의 let 블록에 전화 하고 있습니다. 나는 보통 객체가 지속되는지 확인함으로써 이것을한다.

object.persisted?.should eq true

그렇지 않으면 let블록이 처음 호출 될 때 지연 인스턴스화로 인해 데이터베이스 변경이 실제로 발생합니다.

최신 정보

메모를 추가하기 만하면됩니다. 코드 골프 또는이 경우 rspec 골프를 조심하십시오 .

이 경우 객체가 응답하는 메소드를 호출하면됩니다. 따라서 _.persisted?객체 에서 _ 메소드를 진실로 호출합니다 . 내가하려는 것은 객체를 인스턴스화하는 것입니다. 당신은 빈 전화 할 수 있습니까? 또는 nil? 너무. 요점은 테스트가 아니라 개체를 호출하여 생명을 얻는 것입니다.

그래서 당신은 리팩토링 할 수 없습니다

object.persisted?.should eq true

되려고

object.should be_persisted 

객체가 인스턴스화되지 않았기 때문에 ... 게으른. :)

업데이트 2

렛을 활용하십시오 ! 이 문제를 완전히 피해야하는 인스턴트 객체 생성 구문 . 그것은 뱅킹되지 않은 let의 게으름의 많은 목적을 물리 칠 것입니다.

또한 경우에 따라 추가 옵션을 제공 할 수 있으므로 실제로는 주제 구문 을 활용하는 것이 좋습니다.

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

2

Joseph에 대한 참고 사항-데이터베이스 객체를 생성 before(:all)하는 경우 트랜잭션에서 캡처되지 않으며 테스트 데이터베이스에 균열이 생길 가능성이 훨씬 큽니다. before(:each)대신 사용하십시오 .

let과 게으른 평가를 사용해야하는 또 다른 이유는이 매우 복잡한 예제 에서처럼 컨텍스트에서 let을 재정 의하여 복잡한 객체를 가져 와서 개별 조각을 테스트 할 수 있기 때문입니다.

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
before (: all) 버그 +1은 개발자들의 많은 시간을 낭비했습니다.
Michael Durrant

1

기본적으로 "이전"은을 의미합니다 before(:each). Rspec Book을 참조하십시오 (저작권 2010, 228 페이지).

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

"it"블록에 데이터를 작성하기 위해 메소드를 before(:each)호출 할 필요없이 각 예제 그룹에 대한 일부 데이터를 시드하는 데 사용 합니다 let. 이 경우 "it"블록의 코드가 줄어 듭니다.

let일부 예제에서는 데이터를 원하지만 다른 데이터는 원하지 않는 경우에 사용 합니다.

"it"블록을 건조시키는 데는 앞뒤가 모두 좋습니다.

혼동을 피하기 위해 "let"은와 동일하지 않습니다 before(:all). "하자"는 각 예에 대한 방법과 값 ( "it")을 다시 평가하지만 동일한 예에서 여러 호출에 걸쳐 값을 캐시합니다. https://www.relishapp.com/rspec/rspec-core/v/2-6/docs/helper-methods/let-and-let 에서 자세한 내용을 확인할 수 있습니다.


1

반대 의견 : 여기에서 5 년간의 rspec 후 나는별로 좋아하지 않습니다 let.

1. 게으른 평가는 종종 테스트 설정을 혼란스럽게 만듭니다.

설정에서 선언 된 일부는 실제로 상태에 영향을 미치지 않는 반면 다른 것들은 상태에 영향을 미치지 않는 경우 설정에 대해 추론하기가 어렵습니다.

결국, 좌절감에서 누군가 스펙을 작동시키기 위해 (게으른 평가가없는 동일한 것)으로 변경 let합니다 let!. 이것이 효과가 있다면 새로운 습관이 생겨납니다. 새로운 사양이 이전 제품군에 추가되고 작동하지 않는 경우, 작가가 가장 먼저 시도하는 것은 임의의 let통화 에 앞머리를 추가하는 것 입니다.

곧 모든 성능상의 이점이 사라졌습니다.

2. rspec이 아닌 사용자에게는 특별한 구문이 일반적이지 않습니다.

rspec의 트릭보다 루비를 팀에 가르치고 싶습니다. 인스턴스 변수 또는 메소드 호출은이 프로젝트 및 다른 곳에서 유용하며 let구문은 rspec에서만 유용합니다.

3. "이점"을 통해 좋은 디자인 변경을 쉽게 무시할 수 있습니다.

let()우리가 계속 만들고 싶지 않은 비싼 의존성에 좋습니다. 또한 subject다중 인수 메소드에 대한 반복 호출을 건조시킬 수 있도록, 와 잘 결합됩니다 .

고가의 의존성은 여러 번 반복되었으며, 서명이 큰 메소드는 코드를 더 잘 만들 수있는 지점입니다.

  • 어쩌면 나머지 코드에서 종속성을 분리하는 새로운 추상화를 도입 할 수 있습니다 (테스트가 덜 필요하다는 것을 의미합니다)
  • 테스트중인 코드가 너무 많이 수행되고있을 수 있습니다.
  • 어쩌면 긴 프리미티브 목록 대신 더 똑똑한 객체를 주입해야 할 수도 있습니다.
  • 어쩌면 나는 묻지 말아야 할 위반이있다
  • 아마도 고가의 코드가 더 빨라질 수 있습니다 (희귀 한-조기 최적화에주의하십시오)

이 모든 경우에, 나는 부드러운 rspec 마법의 밤으로 어려운 테스트의 증상을 해결하거나 원인을 해결할 수 있습니다. 지난 몇 년 동안 전자에 너무 많은 시간을 보냈으며 더 나은 코드가 필요합니다.

원래 질문에 대답하기 위해 : 나는 싫지만을 계속 사용 let합니다. 나는 대부분 다른 팀의 스타일에 맞추기 위해 주로 사용합니다 (세계의 대부분의 Rails 프로그래머는 이제 rspec 마술에 깊이 빠져있는 것처럼 보입니다). 때로는 제어 할 수 없거나 더 나은 추상화로 리팩토링 할 시간이없는 일부 코드에 테스트를 추가 할 때 사용합니다. 즉, 유일한 옵션은 진통제입니다.


0

let컨텍스트를 사용하여 API 사양에서 HTTP 404 응답을 테스트 하는 데 사용합니다.

리소스를 만들려면을 사용 let!합니다. 그러나 리소스 식별자를 저장하려면을 사용 let합니다. 그것이 어떻게 생겼는지 살펴보십시오 :

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

그러면 사양을 깨끗하고 읽을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.