RSpec 테스트를 작성하여 디버깅 환경 향상

19348 단어 rspecrubytesting
지난 몇 달 동안 나는 남겨진 코드 라이브러리에서 많은 일을 했다.우리는 우리가 커다란 변화를 이룰 수 있는 광범위한 테스트 세트가 있어서 다행이다.이 같은 테스트를 하는 것도 낙담스럽다.일부 실패한 테스트는 다른 테스트보다 더 좋은 디버깅 체험을 제공하는 것이 분명하다.
우리 팀은 몇 년 동안 개발되지 않은 코드를 사용해 왔다.이제 우리는 그것으로 돌아가야 한다. 우리는 스스로 배에 올라야 한다.이 글을 RSpec 스타일의 대체 지침으로 여기고 이 고고학자 개발자들에게 유익하다고 생각하는 실천을 담고 있다.
하지만 테스트를 최적화할 수 있는 다른 것들이 있기 때문에 다른 결정을 내릴 수도 있으니 괜찮아요.

반드시 테스트 설명을 문자열로 써야 한다
당신은 어떤 유형의 테스트 설명을 좋아합니까?
describe("a boat") do
  context("with a rudder") do
    it("can steer") do ... end
  end
end

it("a boat with a rudder can steer")
테스트 묘사는 미래의 우리 자신에게 매우 중요하다.우리는 우리가 테스트를 어떻게 파괴했는지, 테스트 목적을 어기지 않고 테스트를 어떻게 바꾸었는지, 또는 언제 테스트를 삭제했는지 이해할 필요가 있다.설명이 분리되면 읽기가 더 어렵고, 세션 사이에 다른 코드가 있으면 읽기가 더 어려워집니다.
Rspec에는 테스트 설명 세그먼트를 작성하는 규칙이 있습니다.만약 우리가 이러한 규칙을 따른다면, RSpec는 이 문장의 일부분을 완전한 문장으로 잘 붙일 수 있을 것이다.그러나 이러한 규칙에 따라 생성된 테스트 묘사가 문법적으로 정확하다는 것을 확보할 수 있다. 묘사가 좋다는 것이 아니라.
전체 문장의 방법은 우리의 미래의 자아에 일관된 테스트 설명을 제공할 수 있는 더 큰 기회가 있다.초보자의 입장에서 말하자면, 만약 우리가 테스트 사이의 중용 부분을 어떻게 사용하는지 동시에 책임지지 않는다면, 좋은 테스트 설명을 작성하는 것이 더욱 쉽다.그 다음으로 만약에 우리가 기존의 테스트 설명을 조정한다면, 만약 우리가 문장을 완전하게 읽을 수 있다면, 우리는 더욱 잘 할 수 있을 것이다.
let 바인딩 방지
너는 나에게 이번 시험에 통과할 수 있는지 없는지를 알려줄 수 있니?
describe("taglines") do
  let(:sentence) { "Slugs: #{description}" }
  let(:description) { "the #{adjective} frontier" }
  let(:adjective) { "slimiest" }

  shared_examples_for("TNG intro") do
    let(:adjective) { "final" }
    it("introduces") do
      expect(sentence).to eq("Space, the final frontier.")
    end
  end

  context("sci-fi") do
    let(:sentence) { "Space, #{description}." }
    let(:adjective) { "quietest" }

    it_behaves_like "TNG intro"
  end
end
나는 문제를 설명하는 그다지 좋지 않은 예를 생각해 냈지만, 그렇게 나쁘지는 않았다.실제 세트의 유사한 테스트는 다른 테스트 용례의 코드와 혼합되어 여러 파일을 덮어쓸 수 있습니다.더 나빠!
문제는 let 귀속이 전역 변수라는 점이다.정확히 말하면, 전체적으로 하나의 테스트에 이르지만, 우리가 하나의 테스트를 디버깅할 때, 이것은 같다.테스트 프레임워크를 제외하고는 모든 언어나 프레임워크가 전역 변수를 광범위하게 사용하도록 권장하는지 모르겠습니다.
나는 우리가 테스트 코드와 생산 코드가 같은 품질을 가지기를 갈망해야 한다고 믿는다. 이를 위해 우리는 같은 실천을 응용해야 한다.대부분의 언어는 전역 변수를 완전히 허용하지 않거나, 사용할 때 경고를 보내거나, 사용을 강력히 반대한다.같은 일을 하면 테스트가 더 좋다.
우리는 let 귀속이 아니라 일반적인 루비 변수를 사용할 수 있다. 테스트 주체와 갈고리 사이에서 전달할 수 없는 변수, 예를 들어 beforeafter를 제외하고는.바로 다음 연습을 이끌어냈다.
beforeafter 연결 방지
갈고리를 사용하는 테스트를 봅시다.
let(:door) { Door.new }

before(:each) do
  open_door door
end

after(:each) do
  close_door door
end

it("can go through a door") do
  move_through_door door
end
가령can go through a door 테스트가 실패한다면 우리는 조사하고 있습니다.우선, 우리는 테스트의 작용을 분명히 하려고 한다.이것은 위의 예에서 실행할 수 있지만, 진정한 테스트 세트에서는 더욱 어렵다. 왜냐하면 하나의 테스트를 구성하는 각 부분의 거리가 매우 멀고, 다른 테스트에서 사용하는 코드로 분리되기 때문이다.우리는 더 큰 세트에서 스크롤해서 실패한 테스트에 사용된 코드 위치를 찾아내고, 우리의 머릿속에서 이 부분들을 조립해 보려고 한다.
보통, 내가 이런 방식으로 테스트된 심지 모형을 조립하려고 시도할 때, 나의 뇌는 이 모든 것을 수용할 수 없을 정도로 크지 않다.나는 모든 테스트 세트를 인쇄하고 내가 조사하고 있는 테스트와 관련된 줄에 표시를 사용하고 싶다.다음은 내가 예제에 표시한 몇 줄입니다.
let(:door) { Door.new }
  open_door door
  close_door door
it("can go through a door") do
  move_through_door door
잠깐만, 우리가 눈을 가늘게 뜨고 보면 거의 효과적인 테스트가 될 것 같아.정리합시다.
it("can go through a door") do
  door = Door.new
  open_door door
  move_through_door door
  close_door door
end
나에게 있어서 이것은 이전의 테스트보다 매우 큰 개선이 있었다.이런 식으로 작성된 테스트가 실패했을 때, 나는 수수께끼 풀기 단계를 뛰어넘어 직접 디버깅을 할 수 있다.
문을 만드는 것이 더 복잡하다고 가정하면, 우리는 몇 가지 테스트에서 문을 만드는 논리를 다시 사용하고, 우리 자신을 반복하지 않기를 바란다.이 경우 함수를 사용할 수 있습니다.
it("can go through a door") do
  door = create_test_door
  open_door door
  move_through_door door
  close_door door
end

it("can knock on a door") do
  door = create_test_door
  knock_on door
end

def create_test_door do
  Door.new(
    material: :wood,
    locked: false,
  )
end
그런데 잠깐만요. 분열 테스트 코드예요.우리는 기호펜을 다시 꺼내야 합니까?나는 이렇게 생각하지 않는 데는 두 가지 이유가 있다.우선create_test_door은 테스트 체현식에서 호출된 것이기 때문에 테스트 체현은 테스트가 한 모든 것을 잘 정리할 수 있다.그 다음으로 우리가 만든 함수는 자기 묘사 이름이 있기 때문에 문 생성과 관련된 문제에 직면하기 전에 그 실현을 볼 필요가 없다.

당신의 매칭기가 좋은 오류 메시지를 제공했는지 반드시 테스트해야 합니다
이상적인 상황에서, 우리는 테스트 설명과 오류 메시지만 있으면 코드의 오류를 이해할 수 있다.커다란 오류 보고서가 우리로 하여금 코드가 파괴된 원인을 찾아내고 그것을 복구할 수 있게 했다.
실천에서 잘못된 소식은 신비로울 수도 있으니 우리가 그것을 설명해야 한다.만약 우리가 실패의 테스트에 익숙하다면 해석은 매우 빠를 것이다. 그러나 미래의 자신에게 이런 익숙함을 기대할 수는 없다.
Rspec에서 매칭기의 선택은 오류의 질에 큰 영향을 미치고 좋지 않은 선택을 하기 쉽다.다음과 같은 예를 들 수 있습니다.
it("George III and George IV are the same") do
  Monarch =
    Struct.new(
      :title,
      :first_name,
      :full_name,
      :number,
      :date_of_birth,
      :place_of_birth,
      :date_of_death,
      :place_of_death,
      :buried_at,
    )
  george3 =
    Monarch.new(
      "King of the United Kingdom of Great Britain and Ireland",
      "George",
      "George William Frederich",
      "III",
      "4 June 1738",
      "Norfolk House, St James's Square, London, England",
      "29 January 1820",
      "Windsor Castle, Windsor, Berkshire, England",
      "St George's Chapel, Windsor Castle"
    )
  george4 =
    Monarch.new(
      "King of the United Kingdom of Great Britain and Ireland",
      "George",
      "George Augustus Frederich",
      "IV",
      "12 August 1762",
      "St James's Palace, London, England",
      "26 June 1830",
      "Windsor Castle, Windsor, Berkshire, England",
      "St George's Chapel, Windsor Castle"
    )
  expect(george3).to eq(george4)
end
이 테스트는 실패하고 다음 오류가 발생합니다.
  1) George III and George IV are the same
     Failure/Error: expect(george3).to eq(george4)

       expected: #<struct Monarch title="King of the United Kingdom of Great Britain and Ireland", first_name="George"...death="Windsor Castle, Windsor, Berkshire, England", buried_at="St George's Chapel, Windsor Castle">
            got: #<struct Monarch title="King of the United Kingdom of Great Britain and Ireland", first_name="George"...death="Windsor Castle, Windsor, Berkshire, England", buried_at="St George's Chapel, Windsor Castle">

       (compared using ==)
그것은 그다지 좋지 않다.테스트 실패는 예상치와 단언치가 같지 않지만 보고서가 똑같아 보이기 때문이다.이런 잘못은 나로 하여금 완전히 잘못된 방향으로 문제를 찾게 했다.
그것을 복구하는 것도 작은 일이 아니다.효과적인 방법을 찾기 전에, 나는 개선을 시도해야 한다.
  • 사용have_attributes 대체eq: Structs
  • 에 적용되지 않음
  • 군주를 .to_s에게 전하기 전에 먼저 호출eq: 개선되지 않았다.
  • 임금님을 .to_h에게 전하기 전에 방문eq:🎉 차이!
  • 다른 예를 봅시다.eq 가끔 오류가 발생하지만 contain_exactly 항상 오류가 발생합니다.이 테스트에 참가:
    it("fruit salad contains the right ingredients") do
      Ingredient = Struct.new(:name, :grams)
      fruit_salad = [
        Ingredient.new("mango", 400),
        Ingredient.new("pineapple", 300),
        Ingredient.new("coconut flakes", 50),
      ]
      recipe = [
        Ingredient.new("mango", 400),
        Ingredient.new("pineapple", 200),
        Ingredient.new("coconut flakes", 50),
      ]
      expect(fruit_salad).to contain_exactly(*recipe)
    end
    
    테스트에 장애가 발생했습니다. 오류는 다음과 같습니다.
    1) fruit salad contains the right ingredients
       Failure/Error: expect(fruit_salad).to contain_exactly(*recipe)
    
         expected collection contained:  ["#<struct Ingredient
    name=\"coconut flakes\", grams=50>", "#<struct Ingredient
    name=\"mango\", grams=400>", "#<struct Ingredient
    name=\"pineapple\", grams=200>"]
         actual collection contained:    [#<struct Ingredient
    name="mango", grams=400>, #<struct Ingredient
    name="pineapple", grams=300>, #<struct Ingredient
    name="coconut flakes", grams=50>]
         the missing elements were:      ["#<struct Ingredient
    name=\"pineapple\", grams=200>"]
         the extra elements were:        [#<struct Ingredient
    name="pineapple", grams=300>]
    
    테스트 실패는 우리가 잘못된 수량의 파인애플을 첨가했기 때문이지만, 오류 보고서에서 해석하기 위해서는 약간의 노력이 필요하다.contain_exactly 우리가 비교하고 있는 그룹의 항목의 복잡성이 증가함에 따라 오류는 더욱 심각해질 것이다.
    우리는 contain_exactly를 사용하지 않고 명칭에 따라 성분을 나누어 정확한 성분이 있는지 검사한 다음에 각 성분의 정확한 수량을 검사할 수 있다.테스트에 실패할 때 더 좋은 오류 정보를 얻기 위해 더 많은 초기 작업을 해야 한다는 것은 일종의 균형이다.
    현재 상황에서, 우리는 일부러 테스트를 실패하게 해서 그들의 오류 정보가 어떤 모양인지 알아야 하기 때문에, 양호한 오류를 가진 RSpec 테스트를 작성하는 데는 투입과 실험이 필요하다.나는 tip과 같은 스타일 매뉴얼이 테스트 작성자가 matcher의 부적절한 사용을 방지하는 데 도움을 줄 수 있는 것은 아니지만, 나는 Rspec가 다음과 같은 몇 가지를 개선할 수 있다고 생각한다.
  • 정합기에서 발생하는 오류를 개선합니다.예를 들어 eq에 차
  • 를 발생시킵니다.
  • 정확한 오류 메시지를 생성할 수 없는 일치기를 삭제합니다.예: contain_exactly.
  • 일치기를 사용할 때 경고를 보내면 오류 메시지가 발생합니다.예를 들어 우리는 eq 유형의 값을 전달하지만 이러한 유형에 좋은 차이를 일으킬 수 없다.

  • 결론
    나는 디버깅 RSpec 테스트 체험을 향상시킬 수 있다고 생각하는 실천을 보여 주었다.흥미로운 것은 이들 중 상당수가 RSpec 언어 특성이 아닌 간단한 루비 언어 특성을 사용하는 것으로 귀결된다는 점이다.어떻게 생각하세요?당신은 이런 RSpec 기능을 놓칠 수 있습니까?너는 그들에게 무엇을 좋아하니?듣고 싶어요!

    좋은 웹페이지 즐겨찾기