완전한 리팩토링 시간이 없을 때 레거시 코드에 대한 테스트를 작성하는 것이 합리적입니까?


72

나는 보통 레거시 코드 e로 효과적으로 작업하기 책의 조언을 따르려고 노력합니다 . 의존성을 깨고 코드의 일부를 @VisibleForTesting public static메소드와 새로운 클래스로 옮겨서 코드 (또는 적어도 일부)를 테스트 할 수있게 만듭니다. 그리고 새 기능을 수정하거나 추가 할 때 아무 것도 깨지지 않도록 테스트를 작성합니다.

동료는 내가 그렇게해서는 안된다고 말합니다. 그의 추론 :

  • 원래 코드는 처음에는 제대로 작동하지 않을 수 있습니다. 또한 테스트를 작성하면 개발자도 테스트를 이해하고 수정해야하므로 향후 수정 및 수정이 더 어려워집니다.
  • 논리가있는 GUI 코드 (~ 12 줄, 2-3 if / else 블록) 인 경우 코드가 너무 사소하기 때문에 테스트는 문제가되지 않습니다.
  • 코드베이스의 다른 부분에도 비슷한 나쁜 패턴이 존재할 수 있습니다 (아직 보지 못했지만 오히려 새로운 것입니다). 하나의 큰 리팩토링으로 정리하는 것이 더 쉬울 것입니다. 논리를 추출하면이 미래의 가능성이 손상 될 수 있습니다.

완전한 리팩토링 시간이 없다면 테스트 가능한 부품을 추출하고 테스트를 작성하지 않아야합니까? 고려해야 할 단점이 있습니까?


29
동료가 그런 식으로 일하지 않기 때문에 변명을하는 것 같습니다. 사람들은 때때로 자신이 채택한 일을 바꾸는 데 너무 강인 해져서 이렇게 행동합니다.
Doc Brown

3
버그로 분류되어야하는 것은 코드의 다른 부분에 의존하여 기능으로 바뀔 수 있습니다.
ratchet freak

1
내가 생각할 수있는 유일한 좋은 주장은 무언가를 잘못 읽거나 잘못 본다면 리팩토링 자체가 새로운 버그를 일으킬 수 있다는 것입니다. 이런 이유로 나는 현재 개발중인 버전에서 내 마음의 내용을 리팩터링하고 자유롭게 고칠 수 있지만, 과거 버전의 수정 사항은 훨씬 더 높은 장애물에 직면하여 이후에 화장품 / 구조적 정리 만된다면 승인되지 않을 수 있습니다. 위험은 잠재적 이익을 초과하는 것으로 간주됩니다. 소의 아이디어 하나만이 아니라 현지 문화를 알고 다른 일을하기 전에 매우 강력한 이유를 준비하십시오.
keshlam

6
첫 번째 요점은 일종의 재밌습니다.“테스트하지 마십시오. 버그 일 수 있습니다.”글쎄요? 그런 다음이를 고치거나 다른 사람이 실제 동작을 일부 디자인 사양에 따라 변경하지 않기를 바랍니다. 어느 쪽이든, 테스트 (및 자동화 된 시스템에서 테스트를 실행하는 것)가 유리합니다.
Christopher Creutzig

3
너무나 자주 일어나고 모든 병을 치료할 수있는 "하나의 큰 리팩토링 (refactoring)"은 지루하다고 생각하는 것들 (먼저 시험 작성)을 먼 미래에 밀어 붙이고 자하는 사람들에 의해 만들어진 신화입니다. 그리고 그것이 현실이된다면, 그것이 그렇게 커지게 된 것을 진심으로 후회할 것입니다!
Julia Hayward

답변:


100

여기 개인적으로 비과학적인 인상이 있습니다. 세 가지 이유는 모두 널리 퍼져 있지만 허위인지 착각처럼 들립니다.

  1. 물론 기존 코드가 잘못되었을 수 있습니다. 또한 옳을 수도 있습니다. 응용 프로그램 전체가 당신에게 가치가있는 것처럼 보이기 때문에 (그렇지 않으면 단순히 버릴 것입니다) 더 구체적인 정보가 없으면 주로 옳다고 가정해야합니다. "전체적으로 더 많은 코드가 관련되어 있기 때문에 테스트를 작성하면 작업이 더 어려워집니다."는 단순하고 매우 잘못된 태도입니다.
  2. 최소한의 노력으로 최고의 가치를 창출 할 수있는 장소에서 리팩토링, 테스트 및 개선 노력을 기울이십시오. 가치 형식화 GUI 서브 루틴은 종종 첫 번째 우선 순위가 아닙니다. 그러나 "단순하다"는 것은 매우 잘못된 태도이기 때문에 무언가를 테스트하지 않습니다. 사람들이 실제보다 더 잘 이해하고 있다고 생각하기 때문에 거의 모든 심각한 오류가 발생합니다.
  3. "미래에 한 번에 모두 할 것"은 좋은 생각입니다. 일반적으로 큰 급습은 미래에 단단히 머무르고 현재에는 아무 일도 일어나지 않습니다. 나는 "느리고 꾸준한 경주에서 이긴다"는 확신을 갖고있다.

23
+1 "실제로 사람들이 실제로하는 것보다 더 나은 것을 이해했다고 생각하기 때문에 모든 심각한 오류가 발생합니다."
rem

포인트 1- BDD 로 테스트는 자체 문서화입니다.
Robbie Dee

2
@ guillaume31이 지적했듯이, 테스트 작성의 가치 중 일부는 코드가 실제로 어떻게 작동 하는지를 보여주고 있습니다. 그러나 "잘못된"사양 일 수 있습니다. 비즈니스 요구 사항이 변경되었을 수 있으며 코드는 새로운 요구 사항을 반영하지만 사양은 그렇지 않습니다. 코드가 "잘못"되었다고 가정하는 것은 지나치게 단순합니다 (포인트 1 참조). 그리고 다시 테스트는 코드가 실제로 무엇을 생각하고 말하지 않는지 코드가 실제로 무엇을하는지 알려줄 것입니다 (포인트 2 참조).
David

한 번만 쓸어도 코드를 이해해야합니다. 테스트는 리팩토링하지 않고 다시 작성하더라도 예기치 않은 동작을 포착하는 데 도움이됩니다 (리팩터링하는 경우 리팩토링이 레거시 동작을 중단하지 않거나 중단하려는 위치에만 있는지 확인하는 데 도움이 됨). 원하는대로 자유롭게 통합 할 수 있습니다.
프랭크 홉킨스

50

몇 가지 생각 :

레거시 코드를 리팩토링 할 때 작성하는 테스트 중 일부가 이상적인 사양과 모순되는지는 중요하지 않습니다. 중요한 것은 프로그램의 현재 동작 을 테스트한다는 것 입니다. 리팩토링은 코드를 더 깨끗하게 만들기 위해 작은 이소 기능 단계를 수행하는 것입니다. 리팩토링하는 동안 버그 수정에 관여하고 싶지 않습니다. 또한, 뻔뻔스러운 버그를 발견하더라도 잃어 버리지 않습니다. 언제든지 회귀 테스트 를 작성하여 일시적으로 비활성화하거나 나중에 백 로그에 버그 수정 작업을 삽입 할 수 있습니다. 한 번에 한 가지.

순수한 GUI 코드는 테스트하기가 어렵고 " 효과적으로 작업 중 ... "스타일 리팩토링에 적합하지 않다는 데 동의합니다 . 그러나 이것이 GUI 계층에서 수행해야 할 동작을 추출하지 말고 추출 된 코드를 테스트해서는 안된다는 의미는 아닙니다. 그리고 "12 라인, 2-3 if / else 블록"은 사소한 것이 아닙니다. 최소한 조건부 논리가있는 모든 코드를 테스트해야합니다.

내 경험상, 큰 리팩토링은 쉽지 않으며 거의 ​​작동하지 않습니다. 자신을 정확하고 작은 목표로 설정하지 않으면 끝까지 발에 절대로 닿지 않는 끝없이 머리를 다듬는 재 작업을 시작할 위험이 높습니다. 변경이 클수록 무언가를 깨뜨릴 위험이 커지고 실패한 곳을 찾는 데 더 많은 문제가 발생합니다.

임시 소형 리팩토링으로 점진적으로 개선하는 것은 "미래의 가능성을 저해하는"것이 아니라이를 가능하게하여 애플리케이션이있는 늪지대를 공고히합니다. 당신은 분명히해야합니다.


5
+1 "테스트 당신은 프로그램의 테스트 쓰기 현재 동작 "
데이비드

17

또한 "원래 코드가 제대로 작동하지 않을 수 있습니다"라는 말은 영향에 대해 걱정하지 않고 코드의 동작을 변경한다는 의미는 아닙니다. 다른 코드는 현재 구현에서 작동이 중단되거나 부작용으로 보이는 것에 의존 할 수 있습니다. 기존 애플리케이션의 테스트 범위는 나중에 실수로 리팩터링하기 쉽도록해야합니다. 가장 중요한 부분을 먼저 테스트해야합니다.


슬프게도 사실입니다. 우리는 클라이언트가 정확성보다 일관성을 선호하기 때문에 해결할 수없는 엣지 사례에서 나타나는 몇 가지 명백한 버그가 있습니다. (이는 일련의 필드에 하나의 필드를 비워 두는 것과 같이보고 코드가 고려하지 않는 것을 허용하는 데이터 수집 코드로 인해 발생합니다)
Izkata

14

Kilian의 답변은 가장 중요한 측면을 다루지 만 포인트 1과 3을 확장하고 싶습니다.

개발자가 코드를 변경 (리팩터링, 확장, 디버그)하려면 코드를 이해해야합니다. 그녀는 변경 사항이 원하는 동작 (리팩토링의 경우)에 정확히 영향을 미치고 그 밖의 다른 것에 영향을 미치지 않도록해야합니다.

테스트가 있으면 테스트도 이해해야합니다. 동시에, 테스트는 주 코드를 이해하는 데 도움이되며, 테스트가 기능 테스트보다 이해하기 훨씬 쉽습니다 (나쁜 테스트가 아닌 한). 테스트는 이전 코드의 동작에서 변경된 사항을 보여줍니다. 원본 코드가 잘못되어 테스트에서 잘못된 동작을 테스트하더라도 여전히 유리합니다.

그러나이를 위해서는 테스트가 스펙이 아니라 기존 동작을 테스트하는 것으로 문서화되어야합니다.

포인트 3에 대한 일부 생각들 : "큰 급습"이 실제로 거의 일어나지 않는다는 사실 외에도 또 다른 일이 있습니다. 실제로 쉽지 않습니다. 더 쉬워 지려면 몇 가지 조건을 적용해야합니다.

  • 리팩토링 할 반 패턴을 쉽게 찾을 수 있어야합니다. 모든 싱글 톤이 명명 XYZSingleton되었습니까? 인스턴스 게터는 항상 호출 getInstance()됩니까? 과도하게 깊은 계층을 어떻게 찾습니까? 신의 물건을 어떻게 검색합니까? 여기에는 코드 메트릭 분석이 필요하며 수동으로 메트릭을 검사해야합니다. 또는 당신이 한 것처럼 작업하면서 그냥 넘어 질 수 있습니다.
  • 리팩토링은 기계적이어야합니다. 대부분의 경우 리팩토링의 어려운 부분은 기존 코드를 변경하는 방법을 알기에 충분히 이해하는 것입니다. 싱글 톤 다시 : 싱글 톤이 사라지면 사용자에게 필요한 정보를 어떻게 얻습니까? 정보를 얻을 수있는 곳을 알 수 있도록 현지 콜 그래프를 이해하는 것을 의미합니다. 이제 더 쉬운 방법은 앱에서 10 개의 싱글 톤을 검색하고 각각의 사용법을 이해하고 (코드베이스의 60 %를 이해해야 함) 추출하는 것입니다. 아니면 이미 이해하고있는 코드 (지금 작업 중이기 때문에)를 사용하여 싱글 톤을 추출합니까? 리팩토링이 너무 기계적이지 않아 주변 코드에 대한 지식이 거의 또는 전혀 필요하지 않으면 묶는 데 사용되지 않습니다.
  • 리팩토링을 자동화해야합니다. 이것은 다소 의견에 근거한 것이지만 여기에 간다. 약간의 리팩토링은 재미 있고 만족 스럽다. 많은 리팩토링은 지루하고 지루합니다. 더 나은 상태에서 방금 작업 한 코드 조각을 남겨두면 더 흥미로운 것들로 넘어 가기 전에 멋지고 따뜻한 느낌을줍니다. 전체 코드베이스를 리팩토링하려고하면 코드를 작성한 바보 프로그래머에게 좌절감을 느끼게됩니다. 큰 스윕 리팩토링을 수행하려면 좌절을 최소화하기 위해 크게 자동화해야합니다. 이것은 어떤 식 으로든 처음 두 가지 요점을 결합한 것입니다. 잘못된 코드 찾기 (즉, 쉽게 찾을 수 있음)를 자동화하고 변경 (기계적)을 자동화하는 경우에만 리팩토링을 자동화 할 수 있습니다.
  • 점진적인 개선으로 비즈니스 사례가 개선되었습니다. 큰 급상승 리팩토링은 엄청나게 파괴적입니다. 코드 조각을 리팩토링하면 변경하는 방법을 다섯 부분으로 나누기 때문에 다른 사람들과 병합 충돌이 발생합니다. 합리적인 크기의 코드 조각을 리팩터링하면 소수의 사람들과 충돌이 발생합니다 (1-6은 600 라인 메가 함수를 분할 할 때 2-4, God 객체를 분리 할 때 2-4, 모듈에서 싱글 톤을 추출 할 때 5) )이지만 주요 수정 사항으로 인해 충돌이 발생했을 수 있습니다. 코드베이스 전체 리팩토링을 수행하면 모든 사람 과 충돌 합니다. 며칠 동안 몇 명의 개발자를 연결한다는 것은 말할 것도 없습니다. 점진적 개선으로 인해 각 코드 수정에 시간이 조금 더 걸립니다. 이를 통해 예측 가능성이 높아지고 정리 외에는 아무 일도 일어나지 않는 가시적 인 시간이 없습니다.

12

일부 회사에는 개발자가 새로운 기능과 같은 추가 가치를 직접 제공하지 않는 코드를 언제든지 향상시킬 수있는 문화가 있습니다.

아마 여기에서 회심 한 사람들에게 설교하고있을 것입니다. 그러나 그것은 분명히 거짓 경제입니다. 깨끗하고 간결한 코드는 후속 개발자에게 도움이됩니다. 단지 투자금 회수가 명백하지 않다는 것입니다.

나는 보이 스카우트 원칙에 개인적으로 가입 하지만 다른 사람들은 (보신 것처럼) 그렇지 않습니다.

즉, 소프트웨어는 엔트로피를 겪고 기술 부채를 형성합니다. 이전 개발자가 시간이 부족하거나 게 으르거나 경험이 부족한 경우 잘 설계된 솔루션에 비해 차선책의 버그 솔루션을 구현했을 수 있습니다. 이것들을 리팩토링하는 것이 바람직한 것처럼 보이지만, (어쨌든 사용자에게) 작업 코드에 새로운 버그가 발생할 위험이 있습니다.

일부 변경 사항은 다른 변경 사항보다 위험이 적습니다. 예를 들어, 내가 일하는 곳에서는 영향을 최소화하면서 서브 루틴으로 안전하게 팜을 만들 수있는 많은 중복 코드가있는 경향이 있습니다.

궁극적으로 리팩토링을 얼마나 멀리 수행해야하는지 판단해야하지만 자동화 된 테스트가 존재하지 않는 경우에는 추가 할 수없는 가치가 있습니다.


2
나는 원칙적으로 전적으로 동의하지만 많은 회사에서 시간과 돈이 필요합니다. "정돈"부분이 몇 분 밖에 걸리지 않는다면 괜찮지 만, 일단 정돈에 대한 추정치가 커지기 시작하면 (일부 큰 정의의 경우), 코딩 담당자는 해당 결정을 상사 또는 프로젝트 매니저. 그 시간의 가치를 결정하는 것은 당신의 장소가 아닙니다. 버그 수정 X 또는 새로운 기능 Y로 작업하면 프로젝트 / 회사 / 고객에게 훨씬 높은 가치가있을 수 있습니다.
ozz

2
또한 6 개월 후에 프로젝트가 폐기되는 것과 같은 더 큰 문제 나 회사가 귀하의 시간을 더 중요하게 여기는 것과 같은 더 큰 문제를 알지 못할 수도 있습니다 (예 : 더 중요하다고 생각하는 사람이 있고 다른 사람이 배변 작업을 수행하는 경우). 리팩토링 작업도 테스트에 영향을 줄 수 있습니다. 큰 리팩토링이 전체 테스트 회귀를 트리거합니까? 회사에이를 수행하기 위해 배포 할 수있는 리소스가 있습니까?
ozz

예, 주요 코드 수술이 좋은 아이디어 일 수도 있고 그렇지 않을 수도있는 무수한 이유가 있습니다. 다른 개발 우선 순위, 소프트웨어 수명, 테스트 리소스, 개발자 경험, 커플 링, 릴리스주기, 코드 친숙성 기본, 문서, 미션 크리티컬, 회사 문화 등. 그것은 판단 전화입니다
로비 디

4

내 경험상 어떤 종류 의 특성화 테스트 가 잘 작동합니다. 광범위하지만 매우 구체적인 테스트 범위를 비교적 빠르게 제공하지만 GUI 응용 프로그램을 구현하기가 까다로울 수 있습니다.

그런 다음 변경하려는 부품에 대한 단위 테스트를 작성하고 변경할 때마다 변경하여 시간이 지남에 따라 단위 테스트 적용 범위를 늘립니다.

이 접근 방식은 변경 사항이 시스템의 다른 부분에 영향을 미치는지에 대한 좋은 아이디어를 제공하며 필요한 변경을 더 빨리 할 수있는 위치에 도달합시다.


3

"원래 코드가 제대로 작동하지 않을 수 있습니다":

시험은 석재로 작성되지 않았습니다. 그것들은 변경 될 수 있습니다. 그리고 잘못된 기능을 테스트 한 경우 테스트를보다 정확하게 다시 작성하기가 쉬워야합니다. 결국 테스트 된 기능의 예상 결과 만 변경되어야합니다.


1
IMO, 개별 테스트 적어도 테스트 대상 기능이 종료되고 사라질 때까지 석재 작성 해야합니다 . 이들은 기존 시스템의 동작을 확인하는 것이며 유지 관리 담당자가 변경 사항이 해당 동작에 이미 의존 할 수있는 레거시 코드를 위반하지 않도록합니다. 라이브 기능에 대한 테스트를 변경하면 해당 보증이 제거됩니다.
cHao

3

그래 소프트웨어 테스트 엔지니어로 응답합니다. 먼저, 당신은 어쨌든 당신이하는 모든 것을 테스트해야합니다. 그렇지 않으면 작동하는지 여부를 알 수 없기 때문입니다. 이것은 우리에게는 명백해 보일지 모르지만 나는 그것을 다르게 보는 동료들이 있습니다. 프로젝트가 전달되지 않을 수도있는 작은 프로젝트 인 경우에도 사용자를 정면으로보고 테스트했기 때문에 제대로 작동한다고 말해야합니다.

사소한 코드에는 항상 버그가 포함되어 있습니다 (uni의 사람을 인용하십시오. 버그가 없으면 사소합니다). 우리의 임무는 고객이하기 전에 버그를 찾는 것입니다. 레거시 코드에는 레거시 버그가 있습니다. 원래 코드가 정상적으로 작동하지 않으면 알고 싶습니다. 버그에 대해 알고 있다면 버그는 괜찮습니다. 버그를 두려워하지 마십시오. 바로 출시 노트입니다.

리팩토링 서적을 올바르게 기억한다면 어쨌든 끊임없이 테스트하라고 말합니다. 그래서 그것은 과정의 일부입니다.


3

자동화 된 테스트 범위를 수행하십시오.

자신과 고객 및 상사 모두에 의한 희망적인 사고에주의하십시오. 내가 처음으로 변경 한 내용이 정확하고 한 번만 테스트하면된다고 생각하고 싶 듯이, 나이지리아 사기 이메일을 처리하는 것과 같은 방식으로 그런 생각을하는 법을 배웠습니다. 글쎄, 주로; 나는 사기성 전자 우편을 간 적이 없지만 최근에 (고함을 질렀을 때) 모범 사례를 사용하지 않기로했습니다. 계속해서 (비싸게) 끌리는 고통스러운 경험이었습니다. 다시는!

Freefall 웹 코믹에서 가장 좋아하는 인용문이 있습니다. "감독관이 기술적 인 세부 사항에 대한 대략적인 아이디어 만 가지고있는 복잡한 분야에서 일한 적이 있습니까? ... 그렇다면 감독관이 실패하게하는 가장 확실한 방법은 질문없이 그의 모든 명령을 따르십시오. "

투자 시간을 제한하는 것이 좋습니다.


1

현재 테스트되지 않은 많은 양의 레거시 코드를 다루는 경우 미래에 가상의 큰 재 작성을 기다리지 않고 지금 테스트 범위를 얻는 것이 올바른 조치입니다. 단위 테스트를 작성하여 시작하는 것은 아닙니다.

자동 테스트없이 코드를 변경 한 후에는 앱을 테스트하여 수동으로 테스트하여 작동하는지 확인해야합니다. 이를 대체하기 위해 높은 수준의 통합 테스트를 작성하여 시작하십시오. 앱이 파일을 읽고, 유효성을 검사하고, 어떤 방식으로 데이터를 처리하며, 테스트를 원하는 결과를 모두 표시합니다.

이상적으로는 수동 테스트 계획의 데이터가 있거나 사용할 실제 생산 데이터 샘플을 얻을 수 있습니다. 그렇지 않은 경우 앱이 프로덕션 환경에 있기 때문에 대부분의 경우 앱이 수행하는 작업을 수행하므로 모든 높은 점수에 도달하는 데이터를 구성하고 출력이 올바른 것으로 가정하십시오. 이름이나 설명에 따라 수행해야한다고 가정하고 올바르게 작동한다고 가정하고 테스트를 작성한다고 가정하면 작은 기능을 수행하는 것보다 나쁘지 않습니다.

IntegrationTestCase1()
{
    var input = ReadDataFile("path\to\test\data\case1in.ext");
    bool validInput = ValidateData(input);
    Assert.IsTrue(validInput);

    var processedData = ProcessData(input);
    Assert.AreEqual(0, processedData.Errors.Count);

    bool writeError = WriteFile(processedData, "temp\file.ext");
    Assert.IsFalse(writeError);

    bool filesAreEqual = CompareFiles("temp\file.ext", "path\to\test\data\case1out.ext");
    Assert.IsTrue(filesAreEqual);
}

앱의 정상적인 작동과 가장 일반적인 오류 사례를 캡처하기 위해 작성된 높은 수준의 테스트를 충분히 마쳤 으면 키보드에서 두드리는 데 시간을 소비하여 코드에서 다른 것 이외의 작업을 수행하는 데 코드에서 오류를 잡아야합니다. 향후 리팩토링 (또는 대규모 재 작성)을 훨씬 쉽게 수행 할 수 있도록해야한다고 생각했습니다.

단위 테스트 범위를 확장하면 대부분의 통합 테스트를 분석하거나 중단 할 수 있습니다. 앱이 파일을 읽거나 쓰거나 DB에 액세스하는 경우 해당 부분을 따로 테스트하고 파일을 모방하거나 파일 / 데이터베이스에서 읽은 데이터 구조를 만들어 테스트를 시작하는 것이 분명한 시작입니다. 실제로 테스트 인프라를 구축하는 것은 일련의 빠르고 더러운 테스트를 작성하는 것보다 훨씬 오래 걸립니다. 통합 테스트에서 다루는 것의 일부를 수동으로 테스트하는 데 30 분을 소비하는 대신 2 분의 통합 테스트를 실행할 때마다 이미 큰 성과를 거두고 있습니다.

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