중복 된 코드는 단위 테스트에서 더 견딜 수 있습니까?


113

나는 얼마 전 여러 단위 테스트를 망 쳤고 그것들을 더 DRY 로 만들기 위해 리팩토링했습니다. 각 테스트의 의도는 더 이상 명확하지 않았습니다. 테스트의 가독성과 유지 보수성간에 절충안이있는 것 같습니다. 단위 테스트에 중복 된 코드를 남겨두면 더 읽기 쉬워 지지만 SUT를 변경하면 중복 된 코드의 각 사본을 추적하고 변경해야합니다.

이 절충안이 존재한다는 데 동의하십니까? 그렇다면 테스트를 읽을 수 있거나 유지 관리 할 수있는 것을 선호합니까?

답변:


68

중복 된 코드는 다른 코드와 마찬가지로 단위 테스트 코드에서도 냄새가납니다. 테스트에서 중복 된 코드가있는 경우 업데이트 할 테스트 수가 불균형하기 때문에 구현 코드를 리팩토링하기가 더 어려워집니다. 테스트는 테스트중인 코드에 대한 작업을 방해하는 큰 부담이 아니라 자신있게 리팩터링하는 데 도움이됩니다.

복제가 조명기 설정에있는 경우 setUp방법 을 더 많이 사용 하거나 더 많은 (또는 더 유연한) 생성 방법을 제공하는 것을 고려하십시오 .

중복이 SUT를 조작하는 코드에있는 경우 소위 여러 "단위"테스트가 정확히 동일한 기능을 실행하는 이유를 스스로에게 물어보십시오.

중복이 어설 션에있는 경우 사용자 지정 어설 ​​션 이 필요할 수 있습니다 . 예를 들어 여러 테스트에 다음과 같은 어설 션 문자열이있는 경우

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

그렇다면 아마도 단일 assertPersonEqual메소드 가 필요할 것 assertPersonEqual(Person('Joe', 'Bloggs', 23), person)입니다. (또는 단순히에서 같음 연산자를 오버로드해야 할 수도 있습니다 Person.)

언급했듯이 테스트 코드를 읽을 수 있어야합니다. 특히 테스트 의 의도 가 분명한 것이 중요합니다 . 많은 테스트가 거의 동일하게 보이는 경우 (예 : 줄의 3/4이 동일하거나 거의 동일)주의 깊게 읽고 비교하지 않고 중요한 차이점을 찾아 내고 인식하기가 어렵습니다. 따라서 모든 테스트 방법의 모든 행이 테스트 목적과 직접 관련이 있기 때문에 중복을 제거하기위한 리팩토링 가독성에 도움이 된다는 것을 알게되었습니다 . 이것은 독자들에게 직접적으로 관련된 줄과 단지 상용구 인 줄의 무작위 조합보다 훨씬 더 유용합니다.

즉, 때때로 테스트는 유사하지만 여전히 상당히 다른 복잡한 상황을 실행하며 중복을 줄이는 좋은 방법을 찾기가 어렵습니다. 상식 사용 : 테스트를 읽을 수 있고 의도를 명확하게하고 테스트에서 호출 한 코드를 리팩토링 할 때 이론적으로 최소한의 테스트 이상을 업데이트해야하는 것에 익숙하다면 불완전 성을 받아들이고 이동합니다. 좀 더 생산적인 것입니다. 나중에 영감이 떠오르면 언제든지 돌아와서 테스트를 리팩토링 할 수 있습니다!


30
"중복 된 코드는 다른 코드와 마찬가지로 단위 테스트 코드에서도 냄새가납니다." 아니요. "테스트에서 중복 된 코드가있는 경우 업데이트 할 테스트 수가 너무 많기 때문에 구현 코드를 리팩토링하기가 더 어려워집니다." 이는 공개 API 대신 비공개 API를 테스트하기 때문에 발생합니다.

15
그러나 단위 테스트에서 중복 된 코드를 방지하려면 일반적으로 새로운 논리를 도입해야합니다. 단위 테스트의 단위 테스트가 필요하기 때문에 단위 테스트에 논리가 포함되어야한다고 생각하지 않습니다.
Petr Peller 2015

@ user11617 "개인 API"와 "공개 API"를 정의하십시오. 내 이해에 따르면 공개 Api는 외부 세계 / 타사 소비자가 볼 수 있고 SemVer 또는 이와 유사한 것을 통해 명시 적으로 버전이 지정된 Api이며 다른 것은 비공개입니다. 이 정의를 사용하면 거의 모든 단위 테스트가 "비공개 API"를 테스트하므로 코드 복제에 더 민감합니다. 이것이 사실이라고 생각합니다.
KolA 2019

@KolA "공개"는 타사 소비자를 의미하지 않습니다. 이것은 웹 API가 아닙니다. 클래스의 공용 API는 클라이언트 코드 (일반적으로 그렇게 많이 변경되지 않거나 변경되지 않아야 함)에서 사용되는 메서드를 말합니다. 일반적으로 "공용"메서드입니다. 비공개 API는 내부적으로 사용되는 로직과 메소드를 나타냅니다. 클래스 외부에서 액세스해서는 안됩니다. 이것이 사용되는 언어의 액세스 수정 자 또는 규칙을 사용하여 클래스에서 로직을 올바르게 캡슐화하는 것이 중요한 이유 중 하나입니다.
Nathan

@Nathan 모든 라이브러리 / dll / nuget 패키지에는 타사 소비자가 있으므로 웹 API 일 필요는 없습니다. 내가 언급 한 것은 라이브러리 소비자가 직접 사용해서는 안되는 공용 클래스와 멤버를 선언하는 것이 매우 일반적이라는 것입니다 (또는 기껏해야 단위 테스트가 그들에게 직접 도달 할 수 있도록 내부적으로 만들고 InternalsVisibleToAttribute로 어셈블리에 주석을다는 것). 그것은 구현 기호와 결합 시험의 힙에 이르게 및 장점보다 더 많은 부담을하게
콜라

186

가독성은 테스트에서 더 중요합니다. 테스트가 실패하면 문제가 분명해지기를 원합니다. 개발자는 정확히 무엇이 실패했는지 확인하기 위해 많은 요소가 포함 된 테스트 코드를 검토 할 필요가 없습니다. 단위 테스트 테스트를 작성해야 할 정도로 테스트 코드가 너무 복잡 해지는 것을 원하지 않습니다.

그러나 중복을 제거하는 것은 아무것도 모호하게하지 않는 한 일반적으로 좋은 것이며 테스트에서 중복을 제거하면 더 나은 API로 이어질 수 있습니다. 수익이 감소하는 지점을 넘어 가지 않도록하십시오.


xUnit 및 기타는 assert 호출에 'message'인수를 포함합니다. 개발자가 실패한 테스트 결과를 빠르게 찾을 수 있도록 의미있는 문구를 입력하는 것이 좋습니다.
seand

1
@seand 어설 션이 검사하는 내용을 설명 할 수 있지만 실패하고 다소 모호한 코드가 포함 된 경우 개발자는 어쨌든 그것을 풀어야합니다. IMO 거기에서 자체 설명하는 코드를 갖는 것이 더 중요합니다.
IgorK

1
@Kristopher,? 왜 이것이 커뮤니티 위키에 게시됩니까?
Pacerier 2015

@Pacerier 모르겠어요. 자동으로 커뮤니티 위키가되는 것에 대한 복잡한 규칙이있었습니다.
Kristopher Johnson 2015

보고서의 가독성은 테스트보다 더 중요합니다. 특히 통합 또는 종단 간 테스트를 수행 할 때 시나리오는 약간의 탐색을 피할 수있을만큼 복잡 할 수 있습니다. 실패를 찾아도 괜찮지 만 보고서의 실패는 문제를 충분히 설명하십시오.
Anirudh

47

구현 코드와 테스트는 동물이 다르며 인수 분해 규칙이 다르게 적용됩니다.

중복 된 코드 또는 구조는 구현 코드에서 항상 냄새입니다. 구현에 상용구를 사용하기 시작하면 추상화를 수정해야합니다.

반면에 테스트 코드는 중복 수준을 유지해야합니다. 테스트 코드의 복제는 두 가지 목표를 달성합니다.

  • 테스트를 분리 된 상태로 유지합니다. 과도한 테스트 결합은 계약이 변경 되었기 때문에 업데이트가 필요한 단일 실패 테스트를 변경하기 어렵게 만들 수 있습니다.
  • 테스트를 독립적으로 의미있게 유지합니다. 단일 테스트가 실패 할 경우 테스트 대상을 정확히 파악하는 것이 합리적으로 간단해야합니다.

각 테스트 방법이 약 20 줄 미만으로 유지되는 한 테스트 코드에서 사소한 중복을 무시하는 경향이 있습니다. 나는 설정-실행-검증 리듬이 테스트 방법에서 명백 할 때를 좋아한다.

테스트의 "확인"부분에서 중복이 발생하면 사용자 지정 어설 ​​션 메서드를 정의하는 것이 도움이되는 경우가 많습니다. 물론 이러한 메서드는 메서드 이름에서 명확하게 확인할 수있는 명확하게 식별 된 관계를 테스트해야합니다 assertPegFitsInHole.-> good, assertPegIsGood-> bad.

테스트 방법이 길고 반복적으로 늘어날 때 가끔 몇 가지 매개 변수를 사용하는 빈칸 채우기 테스트 템플릿을 정의하는 것이 유용하다는 것을 알게됩니다. 그런 다음 실제 테스트 메서드는 적절한 매개 변수가있는 템플릿 메서드에 대한 호출로 축소됩니다.

프로그래밍과 테스트의 많은 부분에 대해서는 명확한 답이 없습니다. 당신은 취향을 개발할 필요가 있으며 그렇게하는 가장 좋은 방법은 실수를하는 것입니다.


8

나는 동의한다. 트레이드 오프는 존재하지만 장소마다 다릅니다.

상태 설정을 위해 중복 된 코드를 리팩터링 할 가능성이 더 큽니다. 그러나 실제로 코드를 실행하는 테스트 부분을 리팩토링 할 가능성은 적습니다. 즉, 코드를 연습하는 데 항상 여러 줄의 코드가 필요하면 냄새라고 생각하고 테스트중인 실제 코드를 리팩터링 할 수 있습니다. 그리고 그것은 코드와 테스트 모두의 가독성과 유지 보수성을 향상시킬 것입니다.


좋은 생각이라고 생각합니다. 중복이 많은 경우 많은 테스트를 실행할 수있는 공통 "테스트 픽스처"를 만들기 위해 리팩터링 할 수 있는지 확인하십시오. 이렇게하면 중복 설정 / 해체 코드가 제거됩니다.
Outlaw Programmer

8

여러 가지 테스트 유틸리티 방법을 사용하여 반복을 줄일 수 있습니다 .

나는 프로덕션 코드보다 테스트 코드에서 반복에 더 관대하지만 때때로 그것에 대해 좌절감을 느낍니다. 클래스의 디자인을 변경하고 돌아가서 모두 동일한 설정 단계를 수행하는 10 가지 테스트 방법을 조정해야하는 경우 실망 스럽습니다.


6

Jay Fields는 "DSL은 DRY가 아니라 DAMP 여야합니다"라는 문구를 만들었습니다. 여기서 DAMP설명적이고 의미있는 문구를 의미합니다 . 테스트에도 동일하게 적용된다고 생각합니다. 분명히 너무 많은 중복은 나쁘다. 그러나 어떤 대가를 치르더라도 중복을 제거하는 것은 더 나쁩니다. 테스트는 의도를 드러내는 사양으로 작동해야합니다. 예를 들어, 여러 다른 각도에서 동일한 기능을 지정하면 일정량의 중복이 예상됩니다.


3

나는 이것 때문에 rspec을 좋아합니다.

두 가지 도움이 필요합니다.

  • 일반적인 행동을 테스트하기위한 공유 예제 그룹.
    테스트 세트를 정의한 다음 실제 테스트에 설정된 '포함'할 수 있습니다.

  • 중첩 된 컨텍스트.
    클래스의 모든 테스트가 아니라 테스트의 특정 하위 집합에 대해 기본적으로 '설정'및 '해체'방법을 가질 수 있습니다.

.NET / Java / 다른 테스트 프레임 워크가 이러한 방법을 빨리 채택할수록 더 좋습니다 (또는 IronRuby 또는 JRuby를 사용하여 테스트를 작성할 수 있습니다. 개인적으로 더 나은 옵션이라고 생각합니다)


3

테스트 코드에는 일반적으로 프로덕션 코드에 적용되는 유사한 수준의 엔지니어링이 필요하다고 생각합니다. 가독성에 찬성하는 주장이 분명히있을 수 있으며 이것이 중요하다는 데 동의합니다.

그러나 내 경험상 잘 구성된 테스트가 읽고 이해하기 더 쉽다는 것을 알았습니다. 변경된 하나의 변수와 마지막에 어설 션을 제외하고는 각각 동일하게 보이는 5 개의 테스트가있는 경우 해당 단일 항목이 무엇인지 찾기가 매우 어려울 수 있습니다. 유사하게, 변화하는 변수와 주장 만 보이도록 팩토링된다면, 테스트가 즉시 무엇을하고 있는지 쉽게 알아낼 수 있습니다.

테스트 할 때 적절한 수준의 추상화를 찾는 것이 어려울 수 있으며 그럴 가치가 있다고 생각합니다.


2

더 많은 중복 코드와 읽기 쉬운 코드 사이에는 관계가 없다고 생각합니다. 나는 당신의 테스트 코드가 당신의 다른 코드만큼 좋을 것이라고 생각합니다. 반복되지 않는 코드는 잘 수행되면 중복 된 코드보다 더 읽기 쉽습니다.


2

이상적으로는 단위 테스트가 작성된 후에는 많이 변경되어서는 안되므로 가독성에 의지 할 것입니다.

단위 테스트가 가능한 한 분리되어 있으면 테스트가 대상으로하는 특정 기능에 집중하는 데 도움이됩니다.

즉, 일련의 테스트에서 똑같은 설정 코드와 같이 반복해서 사용하는 특정 코드 조각을 다시 사용하는 경향이 있습니다.


2

"더 건조하게 만들기 위해 리팩토링했습니다. 각 테스트의 의도는 더 이상 명확하지 않습니다."

리팩토링을하는 데 문제가있는 것 같습니다. 나는 추측하고 있지만 그것이 명확하지 않다면 그것은 당신이 완벽하게 명확한 합리적으로 우아한 테스트를 할 수 있도록 여전히 할 일이 더 많다는 것을 의미하지 않습니까?

그렇기 때문에 테스트는 UnitTest의 하위 클래스이므로 정확하고 검증하기 쉽고 명확한 좋은 테스트 스위트를 설계 할 수 있습니다.

옛날에는 다른 프로그래밍 언어를 사용하는 테스트 도구가있었습니다. 테스트를 통해 쾌적하고 작업하기 쉬운 디자인을하는 것은 어렵거나 불가능했습니다.

Python, Java, C # 등 어떤 언어를 사용하든 모든 기능을 사용할 수 있으므로 해당 언어를 잘 사용하십시오. 명확하고 중복되지 않는 멋진 테스트 코드를 얻을 수 있습니다. 절충안이 없습니다.

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