Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Test-Commit-Revert:Ruby에서 레거시 코드를 테스트하기 위한 유용한 워크플로

그것은 우리 모두에게 일어납니다. 소프트웨어 프로젝트가 성장함에 따라 코드베이스의 일부는 종합적인 테스트 제품군 없이 프로덕션 상태가 됩니다. 몇 달 후에 같은 코드 영역을 다시 보면 이해하기 어려울 수 있습니다. 설상가상으로 버그가 있을 수 있으며 어디서부터 수정해야 할지 모르겠습니다.

테스트 없이 코드를 수정하는 것은 주요 과제입니다. 우리는 우리가 그 과정에서 어떤 것을 깨뜨릴지 확신할 수 없으며 모든 것을 수동으로 확인하는 것은 기껏해야 실수하기 쉽습니다. 일반적으로 불가능합니다.

이러한 종류의 코드를 처리하는 것은 우리가 개발자로서 수행하는 가장 일반적인 작업 중 하나이며 이전 기사에서 논의한 특성화 테스트와 같은 많은 기술이 수년 동안 이 문제에 중점을 두었습니다.

오늘 우리는 특성화 테스트를 기반으로 하고 수년 전에 TDD를 현대 프로그래밍 세계에 소개한 Kent Beck이 소개한 또 다른 기술을 다룰 것입니다.

TCR이란 무엇입니까?

TCR은 "테스트, 커밋, 되돌리기"의 약자이지만 "테스트 &&커밋 || 되돌리기"라고 부르는 것이 더 정확합니다. 그 이유를 살펴보겠습니다.

이 기술은 레거시 코드를 테스트하는 워크플로를 설명합니다. 프로젝트 파일을 저장할 때마다 테스트를 실행할 스크립트를 사용할 것입니다. 절차는 다음과 같습니다.

  • 먼저 테스트하려는 레거시 코드 부분에 대해 빈 단위 테스트를 만듭니다.
  • 그런 다음 단일 주장을 추가하고 테스트를 저장합니다.
  • 스크립트가 설정되었으므로 테스트가 자동으로 실행됩니다. 성공하면 변경 사항이 커밋됩니다. 실패하면 변경 사항이 삭제(되돌리기)되고 다시 시도해야 합니다.

테스트가 통과되면 새 테스트 케이스를 추가할 수 있습니다.

기본적으로 TCR은 테스트 주도 개발에서처럼 먼저 실패한 테스트(빨간색)를 작성한 다음 통과(녹색)하는 대신 코드를 "녹색" 상태로 유지하는 것입니다. 실패한 테스트를 작성하면 그냥 사라지고 다시 "녹색" 상태로 돌아갑니다.

목적

이 기술의 주요 목표는 테스트 케이스를 추가할 때마다 코드를 조금 더 잘 이해하는 것입니다. 이렇게 하면 자연스럽게 테스트 범위가 증가하고 그렇지 않으면 불가능했던 많은 리팩토링을 차단 해제할 수 있습니다.

TCR의 장점 중 하나는 많은 시나리오에서 유용하다는 것입니다. 테스트가 전혀 없는 코드나 부분적으로 테스트된 코드와 함께 사용할 수 있습니다. 테스트에 통과하지 못하면 변경 사항을 되돌리고 다시 시도합니다.

어떻게 사용할 수 있나요?

Kent Beck은 다른 기사와 비디오(마지막에 링크됨)에서 프로젝트의 특정 파일이 저장된 후 실행되는 스크립트를 사용하는 것이 좋은 접근 방식임을 보여줍니다.

이것은 테스트하려는 프로젝트에 따라 크게 달라집니다. 편집기에서 플러그인으로 파일을 저장할 때마다 실행되는 다음 스크립트와 같은 것이 좋은 시작입니다.

(rspec && git commit -am "WIP") || git reset --hard

Visual Studio Code를 사용하는 경우 저장할 때마다 실행하기에 좋은 플러그인은 "runonsave"입니다. 프로젝트에 위의 명령 또는 유사한 명령을 포함할 수 있습니다. 이 경우 전체 구성 파일은

{
  "folders": [{ "path": "." }],
  "settings": {
    "emeraldwalk.runonsave": {
      "commands": [
        {
          "match": "*.rb",
          "cmd": "cd ${workspaceRoot} && rspec && git commit -am WIP || git reset --hard"
        }
      ]
    }
  }
}

나중에 명령줄에서 직접 Git을 사용하여 커밋을 스쿼시하거나 Github를 사용하는 경우 PR을 병합할 수 있음을 기억하십시오.

Test-Commit-Revert:Ruby에서 레거시 코드를 테스트하기 위한 유용한 워크플로 .

이것은 우리가 작업하고 있는 브랜치에서 수행한 모든 커밋에 대해 메인 브랜치에서 하나의 커밋만 얻게 된다는 것을 의미합니다. Github의 이 다이어그램은 이를 잘 설명합니다.

Test-Commit-Revert:Ruby에서 레거시 코드를 테스트하기 위한 유용한 워크플로 .

TCR로 첫 번째 테스트 작성

이 기술을 설명하기 위해 간단한 예를 사용합니다. 작동하는 클래스가 있지만 수정해야 합니다.

변경 사항을 적용하고 배포할 수 있습니다. 그러나 우리는 그 과정에서 아무 것도 깨뜨리지 않기를 바랍니다. 이는 항상 좋은 생각입니다.

# worker.rb
class Worker
  def initialize(age, active_years, veteran)
    @age = age
    @active_years = active_years
    @veteran = veteran
  end

  def can_retire?
    return true if @age >= 67
    return true if @active_years >= 30
    return true if @age >= 60 && @active_years >= 25
    return true if @veteran && @active_years > 25

    false
  end
end

첫 번째 단계는 테스트를 위한 새 파일을 만드는 것이므로 거기에 추가를 시작할 수 있습니다. can_retire?의 첫 번째 줄을 보았습니다. 방법

  def can_retire?
    return true if @age >= 67
    ...
    ...
  end

따라서 이 경우를 먼저 테스트할 수 있습니다.

# specs/worker_spec.rb
require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it "should return true if age is higher than 67" do

    end
  end
end

다음은 빠른 팁입니다. TCR로 작업할 때 저장할 때마다 테스트를 통과하지 못하면 최신 변경 사항이 사라집니다. 따라서 우리는 실제로 어설션이 있는 줄을 작성하고 저장하기 전에 테스트를 "설정"할 수 있는 최대한 많은 코드를 갖고 싶습니다.

위와 같이 파일을 저장하면 테스트용 라인을 추가할 수 있습니다.

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it "should return true if age is higher than 67" do
      expect(Worker.new(70, 10, false).can_retire?).to be_true ## This line can disappear when we save now
    end
  end
end

저장할 때 새 줄이 사라지지 않으면 잘 수행한 것입니다. 테스트 통과!

테스트 추가

첫 번째 테스트가 끝나면 잘못된 사례를 고려하면서 더 많은 사례를 계속 추가할 수 있습니다. 작업 후 다음과 같은 결과가 나타납니다.

# frozen_string_literal: true

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it 'should return true if age is higher than 67' do
      expect(Worker.new(70, 10, false).can_retire?).to be true
    end

    it 'should return true if age is 67' do
      expect(Worker.new(67, 10, false).can_retire?).to be true
    end

    it 'should return true if age is less than 67' do
      expect(Worker.new(50, 10, false).can_retire?).to be false
    end

    it 'should return true if active years is higher than 30' do
      expect(Worker.new(60, 31, false).can_retire?).to be true
    end

    it 'should return true if active years is 30' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end
  end
end

모든 경우에 우리는 먼저 "it" 블록을 작성하고 저장한 다음 expect(...)로 주장을 추가합니다. .

평소와 같이 가능한 한 많은 테스트를 추가할 수 있지만 모든 것이 포함되어 있다고 비교적 확신이 들면 너무 많이 추가하지 않는 것이 좋습니다.

아직 다루어야 할 몇 가지 사례가 있으므로 완전성을 위해 추가해야 합니다.

최종 테스트

다음은 최종 형식의 사양 파일입니다. 보시다시피 더 많은 사례를 추가할 수 있지만 TCR의 프로세스를 설명하기에 충분하다고 생각합니다.

# frozen_string_literal: true

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it 'should return true if age is higher than 67' do
      expect(Worker.new(70, 10, false).can_retire?).to be true
    end

    it 'should return true if age is 67' do
      expect(Worker.new(67, 10, false).can_retire?).to be true
    end

    it 'should return true if age is less than 67' do
      expect(Worker.new(50, 10, false).can_retire?).to be false
    end

    it 'should return true if active years is higher than 30' do
      expect(Worker.new(60, 31, false).can_retire?).to be true
    end

    it 'should return true if active years is 30' do
      expect(Worker.new(20, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is higher than 25' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is higher than 25' do
      expect(Worker.new(61, 30, false).can_retire?).to be true
    end

    it 'should return true if age is 60 and active years is higher than 25' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is 25' do
      expect(Worker.new(61, 25, false).can_retire?).to be true
    end

    it 'should return true if age is 60 and active years is 25' do
      expect(Worker.new(60, 25, false).can_retire?).to be true
    end

    it 'should return true if is veteran and active years is higher than 25' do
      expect(Worker.new(60, 25, false).can_retire?).to be true
    end
  end
end

리팩토링 방법

여기까지 읽으셨다면 아마도 코드에서 약간 벗어난 느낌이 드는 부분이 있을 것입니다. 테스트와 Worker 클래스 모두에서 상수로 추출해야 하는 "마법의 숫자"가 많이 있습니다.

또한 기본 can_retire? 공개 방법.

두 가지 잠재적인 리팩토링을 연습으로 남겨두겠습니다. 그러나 지금은 테스트가 있으므로 단계에서 실수를 하면 알려줄 것입니다.

결론

귀하의 프로젝트에 TCR을 시도해 보시기 바랍니다. 외부 서버에서 멋진 지속적인 통합이나 새 라이브러리와의 종속성이 필요하지 않기 때문에 매우 저렴한 실험입니다. 컴퓨터에 특정 파일을 저장할 때마다 명령을 실행하는 방법만 있으면 됩니다.

또한 테스트를 추가할 때 항상 재미있고 흥미로운 "게임" 경험을 제공합니다. 또한 편집기에서 실패한 테스트를 제거하는 원칙은 저장소로 푸시하는 테스트가 통과하는지 확인함으로써 추가 안전망을 제공합니다.

레거시 코드를 다룰 때 이 새로운 기술이 유용하기를 바랍니다. 지난 몇 달 동안 여러 번 사용했는데 항상 즐거웠습니다.

추가 리소스

  • 소개용으로 좋은 동영상입니다.
  • Kent Beck이 VS Code에서 TCR을 사용하는 방법에 대해 설명합니다.
  • 저장 시 스크립트를 실행하는 VS Code의 플러그인입니다.