테스트 대 자신을 반복하지 마십시오 (건조)


11

왜 시험을함으로써 자신을 반복하는 것이 그렇게 장려 되는가?

테스트는 기본적으로 코드와 동일한 것을 표현하는 것으로 보이므로 코드의 복제 (개념이 아닌 구현)입니다. DRY의 궁극적 목표가 모든 테스트 코드의 제거를 포함하지 않습니까?

답변:


25

나는 이것이 내가 생각할 수있는 오해라고 생각합니다.

프로덕션 코드를 테스트하는 테스트 코드는 전혀 비슷하지 않습니다. 나는 파이썬에서 시연 할 것이다 :

def multiply(a, b):
    """Multiply ``a`` by ``b``"""
    return a*b

그런 다음 간단한 테스트는 다음과 같습니다.

def test_multiply():
    assert multiply(4, 5) == 20

두 함수 모두 비슷한 정의를 가지고 있지만 두 가지 기능이 매우 다릅니다. 중복 코드가 없습니다. ;-)

또한 사람들은 본질적으로 테스트 기능 당 하나의 주장을 갖는 중복 테스트를 작성합니다. 이것은 광기이며 사람들이 이것을하는 것을 보았습니다. 이것은 이다 나쁜 관행.

def test_multiply_1_and_3():
    """Assert that a multiplication of 1 and 3 is 3."""
    assert multiply(1, 3) == 3

def test_multiply_1_and_7():
    """Assert that a multiplication of 1 and 7 is 7."""
    assert multiply(1, 7) == 7

def test_multiply_3_and_4():
    """Assert that a multiplication of 3 and 4 is 12."""
    assert multiply(3, 4) == 12

1000 개 이상의 유효 코드 줄에 대해이 작업을 수행한다고 상상해보십시오. 대신 '기능'별로 테스트합니다.

def test_multiply_positive():
    """Assert that positive numbers can be multiplied."""
    assert multiply(1, 3) == 3
    assert multiply(1, 7) == 7
    assert multiply(3, 4) == 12

def test_multiply_negative():
    """Assert that negative numbers can be multiplied."""
    assert multiply(1, -3) == -3
    assert multiply(-1, -7) == 7
    assert multiply(-3, 4) == -12

이제 기능이 추가 / 제거 될 때 하나의 테스트 기능 추가 / 제거 만 고려하면됩니다.

for루프를 적용하지 않은 것을 알 수 있습니다 . 몇 가지 반복하는 것이 좋기 때문입니다. 루프를 적용했을 때 코드가 훨씬 짧습니다. 그러나 어설 션이 실패하면 모호한 메시지를 표시하는 출력이 난독 화 될 수 있습니다. 이런 일이 발생하면 테스트가 유용하지 않으며 문제 발생한 곳을 검사하기 위해 디버거 필요합니다.


8
여러 문제가 단일 실패로 표시되지 않기 때문에 테스트 당 하나의 주장이 기술적으로 권장 됩니다. 그러나 실제로는 어설 션을 신중하게 집계하면 반복되는 코드의 양이 줄어들고 테스트 지침 당 하나의 어설 션을 거의 고수하지 않는다고 생각합니다.
Rob Church

@ pink-diamond-square 어설 션이 실패한 후에 NUnit이 테스트를 중단하지 않는다는 것을 알았습니다 (이상하다고 생각합니다). 이 특정한 경우 테스트 당 하나의 어설 션을 갖는 것이 좋습니다. 어설 션 실패 후 단위 테스트 프레임 워크가 테스트를 중지하면 여러 어설 션이 더 좋습니다.
siebz0r

3
NUnit은 전체 테스트 스위트를 중지하지는 않지만 테스트를 방지하기위한 조치를 취하지 않으면 테스트 하나가 중지됩니다 (예외가 발생하면 예외를 포착 할 수 있음). 그들이 생각하는 포인트는 하나 이상의 주장을 포함하는 테스트를 작성하면 문제를 해결하는 데 필요한 모든 정보를 얻지 못한다는 것입니다. 예제를 살펴 보려면이 곱하기 함수가 숫자 3과 같지 않다고 상상해보십시오.이 경우 assert multiply(1,3)실패하지만에 대한 실패한 테스트 보고서도받지 않습니다 assert multiply(3,4).
Rob Church

테스트 당 하나의 assert가 .net 세계에서 읽은 것 중 "좋은 방법"이고 여러 가지 assert가 "실용적 사용"이기 때문에 나는 그것을 높이겠다고 생각했습니다. 예제가 두 가지 주장을 수행 하는 Python 문서 에서는 약간 다르게 보입니다 def test_shuffle.
Rob Church

동의합니다. D : 여기에는 분명히 반복이 있습니다. assert multiply(*, *) == *따라서 assert_multiply함수를 정의 할 수 있습니다. 현재 시나리오에서는 행 수와 가독성에 따라 중요하지 않지만 더 긴 테스트를 통해 복잡한 어설 션, 픽스처, 픽스처 생성 코드 등을 재사용 할 수 있습니다. 이것이 최선의 방법인지는 모르겠지만 보통은 이.
inf3rno

10

테스트는 기본적으로 코드와 동일한 것을 표현하는 것으로 보이므로 중복입니다.

아니요, 사실이 아닙니다.

테스트는 구현과 다른 목적을 가지고 있습니다.

  • 테스트는 구현이 작동하는지 확인합니다.
  • 그것들은 문서의 역할을합니다 : 테스트를 보면 코드가 이행해야하는 계약, 즉 어떤 입력이 어떤 출력을 반환하는지, 특별한 경우는 무엇인지 등을 확인할 수 있습니다.
  • 또한 테스트를 통해 새 기능을 추가해도 기존 기능이 중단되지 않습니다.

4

DRY는 특정 작업을 수행하기 위해 코드를 한 번만 작성하는 것입니다. 테스트는 작업이 올바르게 수행되고 있는지 확인하는 것입니다. 분명히 동일한 코드를 사용하는 것이 쓸모없는 투표 알고리즘과 다소 유사합니다.


2

DRY의 궁극적 목표가 모든 테스트 코드의 제거를 포함하지 않습니까?

아니요, DRY의 궁극적 인 목표는 실제로 모든 생산 코드를 제거한다는 의미 입니다.

테스트가 시스템이 원하는 것을 완벽하게 규정 할 수 있다면, 해당 생산 코드 (또는 이진)를 자동으로 생성하여 생산 코드 기반 자체를 효과적으로 제거하면됩니다.

이것은 실제로 모델 중심 아키텍처와 같은 접근 방식으로, 모든 것이 계산에 의해 도출되는 인간이 설계 한 단일 소스입니다.

나는 반대로 (모든 테스트를 없애는) 것이 바람직하다고 생각하지 않습니다.

  • 구현과 사양 사이의 임피던스 불일치를 해결해야합니다. 프로덕션 코드는 의도를 어느 정도 전달할 수 있지만 제대로 표현 된 테스트만큼 쉽게 추론 할 수는 없습니다. 우리 인간은 우리가 물건을 짓고 있는지에 대한 높은 시각을 필요로합니다 . DRY로 인해 테스트를 수행하지 않더라도 사양은 어쨌든 문서에 기록되어야 할 것입니다. 필요한 경우 임피던스 불일치 및 코드 비 동기화 측면에서 확실히 더 위험한 짐승입니다.
  • 프로덕션 코드는 정확한 실행 사양 (시간이 충분하다고 가정)에서 쉽게 파생 될 수 있지만 테스트 스위트는 프로그램의 최종 코드에서 재구성하기가 훨씬 더 어렵습니다. 런타임시 코드 단위 간 상호 작용을하기가 어렵 기 때문에 사양은 ​​코드를 보는 것만으로 명확하게 나타나지 않습니다. 이것이 우리가 테스트없는 레거시 애플리케이션을 다루는 데 어려움을 겪는 이유입니다. 다시 말해, 몇 달 이상 응용 프로그램을 유지하려면 테스트 스위트가있는 제품보다 프로덕션 코드베이스를 호스팅하는 하드 드라이브를 잃어 버리는 것이 좋습니다.
  • 테스트 코드보다 프로덕션 코드에서 우연히 버그를 도입하는 것이 훨씬 쉽습니다. 그리고 프로덕션 코드는 자체 검증이 아니기 때문에 (Design by Contract 또는 Richer 시스템으로 접근 할 수 있지만)이를 테스트하고 회귀가 발생하면 경고하는 외부 프로그램이 여전히 필요합니다.

1

때때로 자신을 반복해도 괜찮습니다. 이러한 원칙 중 어느 것도 모든 상황에서 질문이나 맥락없이 취해지지 않아야합니다. 나는 종종 순진하고 느린 버전의 알고리즘에 대해 테스트를 작성했습니다.이 알고리즘은 DRY를 상당히 명확하게 위반하지만 확실히 유리합니다.


1

단위 테스트는 의도하지 않은 변경을 더 어렵게 만드는 것이므로 의도적 인 변경을 더 어렵게 만들 수도 있습니다. 이 사실은 실제로 DRY 원칙과 관련이 있습니다.

예를 들어, MyFunction프로덕션 코드에서 한 곳에만 호출 되는 함수 가 있고이를 위해 20 개의 단위 테스트를 작성하면 해당 함수가 호출되는 코드에 21 개의 위치가있게됩니다. 이제, MyFunction일부 요구 사항이 변경되기 때문에 의 서명 또는 시맨틱 또는 둘 다 의 서명을 변경해야하는 경우 하나의 위치 대신 21 개의 위치를 ​​변경할 수 있습니다. 그리고 그 이유는 실제로 DRY 원칙을 위반하는 것입니다 MyFunction. 21 번 이상 동일한 함수 호출을 반복했습니다.

이러한 경우에 대한 올바른 접근 방식은 테스트 코드에도 DRY 원칙을 적용하는 것입니다. 20 단위 테스트를 작성할 때 단위 테스트에서 호출을 MyFunction몇 가지 도우미 함수 (이상적으로는 하나)로 캡슐화하십시오 . 20 개의 단위 테스트. 이상적으로는 코드 호출 MyFunction에서 프로덕션 코드와 단위 테스트 중 하나만 두 곳으로 끝납니다 . 따라서 MyFunction나중에 서명 을 변경해야 할 경우 테스트에서 변경해야 할 곳이 거의 없습니다.

"몇 군데"는 여전히 "한 곳"( 단위 테스트 없이는 얻을 수 있는 것) 이상이지만 단위 테스트를 사용하면 얻을 수있는 코드가 적을수록 이점이 훨씬 뛰어납니다 (그렇지 않으면 단위 테스트를 완전히 수행해야 함) 잘못된).


0

소프트웨어를 구축하는 데 가장 큰 어려움 중 하나는 요구 사항을 포착하는 것입니다. "이 소프트웨어는 어떻게해야합니까?"라는 질문에 대답하는 것입니다. 소프트웨어는 시스템이해야 할 일을 정확하게 정의하기 위해 정확한 요구 사항이 필요하지만 소프트웨어 시스템 및 프로젝트의 요구를 정의하는 사람들은 종종 소프트웨어 나 공식 (수학) 배경이없는 사람들을 포함합니다. 요구 사항 정의가 엄격하지 않으면 소프트웨어 개발에서 소프트웨어를 요구 사항에 맞게 검증하는 방법을 찾아야했습니다.

개발 팀은 프로젝트에 대한 구어체 설명을보다 엄격한 요구 사항으로 변환하는 것을 발견했습니다. 테스트 분야는 고객이 원하는 것과 소프트웨어가 원하는 것을 이해하는 격차를 해소하기 위해 소프트웨어 개발을위한 체크 포인트로 통합되었습니다. 소프트웨어 개발자와 품질 / 테스트 팀 모두 (비공식) 사양에 대한 이해를 형성하고 각 (독립적으로) 소프트웨어 또는 테스트를 작성하여 이해가 일치하는지 확인합니다. (정확하지 않은) 요구 사항을 이해하기 위해 다른 사람을 추가하면 요구 사항의 정확성을 더욱 높이기 위해 질문과 다른 관점이 추가되었습니다.

승인 테스트는 항상 있었으므로 자동 및 단위 테스트를 작성하기 위해 테스트 역할을 확장하는 것이 당연했습니다. 문제는 프로그래머가 테스트를 수행하도록하는 것을 의미했기 때문에 품질 보증에서 테스트를 수행하는 프로그래머로 관점을 좁혔습니다.

모든 테스트에서 테스트가 실제 프로그램과 거의 다르면 테스트를 잘못하고있을 수 있습니다. Msdy 제안은 테스트의 내용에 초점을 맞추고 방법에 대해서는 덜 집중하는 것입니다.

아이러니 한 점은 구어체 설명에서 요구 사항의 공식 사양을 포착하기보다는 테스트를 자동화하기위한 코드로 포인트 테스트를 구현하기로 선택한 것입니다. 소프트웨어가 대답하기 위해 구축 할 수있는 공식적인 요구 사항을 생성하는 대신, 공식적인 논리를 사용하여 소프트웨어를 구축하는 것이 아니라 몇 가지 점을 테스트하는 방법이 사용되었습니다. 이것은 타협이지만 상당히 효과적이고 상대적으로 성공했습니다.


0

테스트 코드가 구현 코드와 너무 유사하다고 생각되면 모의 프레임 워크를 과도하게 사용하고 있음을 나타냅니다. 너무 낮은 수준의 모의 기반 테스트는 테스트중인 방법과 매우 유사한 테스트 설정으로 끝날 수 있습니다. 구현을 변경하면 깨질 가능성이 적은 높은 수준의 테스트를 작성하십시오 (어려울 수 있지만 관리 할 수 ​​있으면 더 유용한 테스트 스위트가 제공됩니다).


0

단위 테스트에는 이미 언급했듯이 테스트중인 코드의 중복이 포함되어서는 안됩니다.

그러나 단위 테스트는 일반적으로 "프로덕션"코드만큼 건조하지는 않습니다. 특히 테스트에서 설정이 비슷하지만 동일하지는 않기 때문에 특히 모의중인 많은 종속성이있는 경우 / 가짜
물론 이런 종류의 일을 일반적인 설정 방법 (또는 설정 방법 세트)으로 리팩터링하는 것이 가능합니다 ...하지만 이러한 설정 방법은 매개 변수 목록이 길고 취하기 쉽다는 것을 알았습니다.

실용적이 되십시오. 유지 관리 성을 손상시키지 않고 설치 코드를 통합 할 수 있다면 반드시 그렇게하십시오. 그러나 대안이 복잡하고 취하기 쉬운 설정 방법이라면 테스트 방법에서 약간의 반복이 가능합니다.

현지 TDD / BDD 전도자는 이런 식으로 다음과 같이
말합니다 . "생산 코드는 건조해야합니다. 그러나 테스트가 촉촉한 것이 좋습니다."


0

테스트는 기본적으로 코드와 동일한 것을 표현하는 것으로 보이므로 코드의 복제 (개념이 아닌 구현)입니다.

이것은 사실이 아니며 테스트는 유스 케이스를 설명하지만 코드는 유스 케이스를 전달하는 알고리즘을 설명하므로 더 일반적입니다. TDD를 통해 유스 케이스 작성 (아마도 사용자 스토리를 기반으로 작성)으로 시작한 후 이러한 유스 케이스를 전달하는 데 필요한 코드를 구현합니다. 따라서 작은 테스트, 작은 코드 덩어리를 작성한 다음 필요한 경우 반복을 제거하기 위해 리팩터링합니다. 그것이 작동하는 방식입니다.

테스트를 통해 반복 할 수도 있습니다. 예를 들어 픽스쳐, 픽스쳐 생성 코드, 복잡한 어설 션 등을 재사용 할 수 있습니다. 나는 보통 테스트에서 버그를 방지하기 위해 이렇게합니다. 그러나 테스트가 실제로 실패하는지 먼저 테스트하는 것을 잊어 버렸습니다. , 30 분 동안 코드에서 버그를 찾고 테스트가 잘못되었을 때 ... xD

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