문제

RSPEC를 사용하기 시작한 이래로 고정구 개념에 문제가있었습니다. 나의 주요 관심사는 이것입니다.

  1. 나는 테스트를 사용하여 놀라운 행동을 드러냅니다. 나는 내가 테스트하는 예제에 대해 가능한 모든 엣지 케이스를 열거 할만 큼 항상 영리하지는 않습니다. 하드 코딩 된 비품을 사용하는 것은 내가 상상했던 매우 구체적인 사례로 내 코드 만 테스트하기 때문에 제한적인 것 같습니다. (분명히, 내 상상력도 내가 테스트하는 경우와 관련하여 제한적입니다.)

  2. 테스트를 코드의 문서 형식으로 사용합니다. 하드 코딩 된 고정 값이 있다면 특정 테스트가 무엇을 보여 주려고하는지 밝히기가 어렵습니다. 예를 들어:

    describe Item do
      describe '#most_expensive' do
        it 'should return the most expensive item' do
          Item.most_expensive.price.should == 100
          # OR
          #Item.most_expensive.price.should == Item.find(:expensive).price
          # OR
          #Item.most_expensive.id.should == Item.find(:expensive).id
        end
      end
    end
    

    첫 번째 방법을 사용하면 독자에게 가장 비싼 품목이 무엇인지, 가격이 100이라는 점을 표시하지 않습니다. 세 가지 방법 모두 독자에게 고정에 대한 믿음을 요구합니다. :expensive 가장 비싼 것입니다 fixtures/items.yml. 부주의 한 프로그래머는 An을 만들어 테스트를 중단 할 수 있습니다 Item 안에 before(:all), 또는 다른 고정물을 삽입하여 fixtures/items.yml. 그것이 큰 파일이라면 문제가 무엇인지 알아내는 데 시간이 오래 걸릴 수 있습니다.

내가 시작한 한 가지는 추가입니다 #generate_random 내 모든 모델에 대한 방법. 이 방법은 사양을 실행할 때만 사용할 수 있습니다. 예를 들어:

class Item
  def self.generate_random(params={})
    Item.create(
      :name => params[:name] || String.generate_random,
      :price => params[:price] || rand(100)
    )
  end
end

(내가이 작업을 수행하는 방법에 대한 구체적인 세부 사항은 실제로 약간 깨끗합니다. 모든 모델의 생성과 정리를 처리하는 클래스가 있지만이 코드는 예를 들어 충분히 명확합니다.) 위의 예에서는 다음과 같이 테스트 할 수 있습니다. 다음. 심장의 성가에 대한 경고 : 내 코드는 크게 의존합니다. before(:all):

describe Item do
  describe '#most_expensive' do
    before(:all) do
      @items = []
      3.times { @items << Item.generate_random }
      @items << Item.generate_random({:price => 50})
    end

    it 'should return the most expensive item' do
      sorted = @items.sort { |a, b| b.price <=> a.price }
      expensive = Item.most_expensive
      expensive.should be(sorted[0])
      expensive.price.should >= 50      
    end
  end
end

이런 식으로, 내 시험은 놀라운 행동을 더 잘 보여줍니다. 이런 식으로 데이터를 생성 할 때, 나는 때때로 코드가 예상대로 작동하지 않는 에지 케이스를 우연히 발견했지만, 고정구 만 사용하는 경우 잡지 못했을 것입니다. 예를 들어, 경우 #most_expensive, 여러 품목이 가장 비싼 가격을 공유하는 특별한 케이스를 처리하는 것을 잊어 버렸다면, 테스트는 때때로 처음에 실패합니다. should. Autospec에서 비 결정적 실패를 보면 무언가 잘못되었다는 사실에 대해 단서가 될 것입니다. 비품 만 사용한다면 그러한 버그를 발견하는 데 훨씬 더 오래 걸릴 수 있습니다.

내 테스트는 또한 예상되는 행동이 무엇인지 코드에서 입증하는 데 약간 더 나은 작업을 수행합니다. 내 테스트를 통해 Sorted는 가격별로 내림차순으로 정렬 된 항목 배열임을 분명히합니다. 내가 기대하기 때문에 #most_expensive 해당 배열의 첫 번째 요소와 동일하기 위해서는 예상되는 동작이 무엇을 most_expensive 이다.

그래서 이것은 나쁜 연습입니까? 비품에 대한 나의 두려움은 비이성적 인 것입니까? a generate_random 각 모델에 대한 방법이 너무 많은 작업? 아니면 이것이 효과가 있습니까?

도움이 되었습니까?

해결책

이것은 두 번째 요점에 대한 답입니다.

(2) 테스트를 코드의 문서 형태로 사용합니다. 하드 코딩 된 고정 값이 있다면 특정 테스트가 무엇을 보여 주려고하는지 밝히기가 어렵습니다.

동의한다. 이상적으로 사양 예제는 그 자체로 이해할 수 있어야합니다. 비품을 사용하는 것은 예상 결과에서 예제의 사전 조건을 분할하기 때문에 문제가됩니다.

이로 인해 많은 RSPEC 사용자가 고정구 사용을 중단했습니다. 대신, 사양 예제 자체에 필요한 개체를 구성하십시오.

describe Item, "#most_expensive" do
  it 'should return the most expensive item' do
    items = [
      Item.create!(:price => 100),
      Item.create!(:price => 50)
    ]

    Item.most_expensive.price.should == 100
  end
end

객체 생성을위한 많은 보일러 플레이트 코드가 끝나면 많은 테스트 객체 공장 라이브러리를 살펴 봐야합니다. factory_girl, 기계공, 또는 FixTurerEplacement.

다른 팁

나는이 주제 나 하나에 아무도 놀랐다. Jason Baker가 연결했습니다 말하는몬테 카를로 테스트. 그것이 내가 무작위 테스트 입력을 광범위하게 사용한 유일한 시간입니다. 그러나 각 테스트 사례에 대해 임의의 숫자 생성기에 대한 일정한 시드를함으로써 테스트를 재현 할 수있게하는 것이 매우 중요했습니다.

우리는 최근의 프로젝트에서 이것에 대해 많이 생각했습니다. 결국, 우리는 두 가지 점에 정착했습니다.

  • 테스트 사례의 반복성이 가장 중요합니다. 무작위 테스트를 작성 해야하는 경우, 실패한 경우 정확히 이유를 알아야하므로 광범위하게 문서화 할 준비를하십시오.
  • Code Coverage의 목발로 임의성을 사용한다는 것은 적용 범위가 좋지 않거나 대표적인 테스트 사례를 구성하는 항목을 알 수있는 도메인을 이해하지 못한다는 것을 의미합니다. 어느 것이 참인 지 알아 내고 그에 따라 수정하십시오.

요컨대, 임의성은 종종 가치보다 더 어려울 수 있습니다. 트리거를 당기기 전에 올바르게 사용할 것인지 신중하게 고려하십시오. 우리는 궁극적으로 임의의 테스트 사례가 일반적으로 나쁜 생각이며 전혀 드물게 사용하기로 결정했습니다.

많은 좋은 정보가 이미 게시되었지만 다음을 참조하십시오. 퍼즈 테스트. 길거리에있는 단어는 Microsoft가 많은 프로젝트 에서이 접근법을 사용한다는 것입니다.

테스트에 대한 나의 경험은 대부분 C/Python/Java로 작성된 간단한 프로그램으로 주로 이루어 지므로 이것이 전적으로 적용 가능한지 확실하지 않지만 모든 종류의 사용자 입력을 수락 할 수있는 프로그램이있을 때마다 항상 테스트를 포함합니다. 무작위 입력 데이터 또는 최소한 사용자가 입력 할 내용에 대한 가정을 할 수 없기 때문에 예측할 수없는 방식으로 컴퓨터가 생성 한 최소한 입력 데이터. 또는, 당신, 당신 ~할 수 있다, 그러나 만약 당신이 그렇게한다면, 그 가정을하지 않는 일부 해커는 당신이 완전히 간과 한 버그를 찾을 수 있습니다. 기계적 생성 입력은 테스트 절차에서 인간의 편견을 완전히 막기 위해 내가 아는 최선의 방법입니다. 물론, 실패한 테스트를 재현하려면 테스트 입력을 파일에 저장하거나 테스트를 실행하기 전에 (텍스트 인 경우) 인쇄하는 것과 같은 작업을 수행해야합니다.

무작위 테스트는 솔루션이없는 한 나쁜 연습입니다. 오라클 문제, 즉, 입력이 주어진 소프트웨어의 예상 결과를 결정합니다.

Oracle 문제를 해결하면 단순한 랜덤 입력 생성보다 한 걸음 더 나아갈 수 있습니다. 소프트웨어의 특정 부분이 간단한 무작위보다 더 많이 운동 할 수 있도록 입력 분포를 선택할 수 있습니다.

그런 다음 무작위 테스트에서 통계 테스트로 전환합니다.

if (a > 0)
    // Do Foo
else (if b < 0)
    // Do Bar
else
    // Do Foobar

선택한 경우 a 그리고 b 무작위로 int 범위, 운동 Foo 시간의 50%, Bar 시간의 25% Foobar 시간의 25%. 더 많은 버그를 찾을 수 있습니다. Foo 보다 Bar 또는 Foobar.

선택한 경우 a 시간의 66.66% 음의 부정적인 Bar 그리고 Foobar 첫 번째 배포보다 더 많은 운동을하십시오. 실제로 세 가지 지점은 각각 33.33%의 시간을 행사합니다.

물론 관찰 된 결과가 예상 결과와는 다른 경우 버그를 재현하는 데 유용 할 수있는 모든 것을 기록해야합니다.

나는 기계공을 보는 것이 좋습니다 :

http://github.com/notahat/machinist/tree/master

Machinist는 귀하를 위해 데이터를 생성하지만 반복 가능하므로 각 테스트 운영에는 동일한 임의 데이터가 있습니다.

임의의 숫자 생성기를 지속적으로 시드하여 비슷한 일을 할 수 있습니다.

무작위로 생성 된 테스트 사례의 한 가지 문제는 답을 검증하는 것이 코드별로 계산되어야하며 버그가 없다는 것을 확신 할 수 없다는 것입니다. :)

이 주제를 볼 수도 있습니다. 임의의 입력 모범 사례로 테스트.

이러한 테스트의 효과는 주로 사용하는 임의 번호 생성기의 품질과 RNG의 출력을 테스트 데이터로 변환하는 코드가 얼마나 올바른지에 달려 있습니다.

RNG가 코드가 일부 에지 케이스 조건으로 들어가게하는 값을 생성하지 않으면이 케이스가 포함되지 않습니다. RNG의 출력을 코드의 입력으로 변환하는 코드가 테스트 한 코드에 결함이있는 경우 좋은 생성기를 사용하더라도 여전히 모든 에지 케이스에 도달하지 않아도됩니다.

그것에 대해 어떻게 테스트 하시겠습니까?

테스트 사례에서 무작위성 문제는 출력이 무작위라는 것입니다.

테스트 (특히 회귀 테스트)의 아이디어는 아무것도 깨지지 않았는지 확인하는 것입니다.

깨진 것을 발견하면 그때부터 매번 해당 테스트를 포함시켜야합니다. 그렇지 않으면 일관된 테스트 세트가 없습니다. 또한 작동하는 임의의 테스트를 실행하는 경우 테스트가 실패 할 수 있도록 코드를 중단 할 수 있기 때문에 해당 테스트를 포함시켜야합니다.

다시 말해, 즉시 생성 된 임의의 데이터를 사용하는 테스트가 있다면 이것이 나쁜 생각이라고 생각합니다. 그러나 랜덤 데이터 세트를 사용하여 저장하고 재사용하는 것이 좋습니다. 좋은 생각 일 수 있습니다. 이것은 임의의 숫자 생성기를위한 씨앗 세트의 형태를 취할 수 있습니다.

생성 된 데이터를 저장하면이 데이터에 대한 '올바른'응답을 찾을 수 있습니다.

따라서 임의의 데이터를 사용하여 시스템을 탐색하는 것이 좋습니다. 그러나 테스트에서 정의 된 데이터를 사용합니다 (원래 무작위로 생성 된 데이터).

무작위 테스트 데이터를 사용하는 것은 훌륭한 관행입니다. 하드 코딩 된 테스트 데이터는 명시 적으로 생각한 경우 만 테스트하는 반면, 임의의 데이터는 잘못된 가정을 세척합니다.

이를 위해 Factory Girl과 FFAKER를 사용하는 것이 좋습니다. (어떤 상황에서도 아무것도 Rails 비품을 사용하지 마십시오.)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top