TDD는 방어 프로그래밍을 중복으로 만들나요?


104

오늘 저는 동료와 흥미로운 토론을했습니다.

나는 방어적인 프로그래머입니다. " 클래스는 클래스 외부에서 상호 작용할 때 클래스의 객체가 유효한 상태를 갖도록해야합니다 "규칙을 항상 준수해야한다고 생각합니다. 이 규칙의 이유는 클래스가 사용자가 누구인지 알지 못하고 불법적 인 방식으로 상호 작용할 때 예상치 못하게 실패하기 때문입니다. 제 생각에 그 규칙은 모든 수업에 적용됩니다.

오늘 토론 한 특정 상황에서, 생성자에 대한 인수가 올바른지 확인하는 코드를 작성했습니다 (예 : 정수 매개 변수는 0보다 커야 함). 전제 조건이 충족되지 않으면 예외가 발생합니다. 반면에 제 동료는 단위 테스트가 클래스의 잘못된 사용을 잡아야하기 때문에 그러한 검사가 중복 적이라고 생각합니다. 또한 방어 프로그래밍 유효성 검사도 단위로 테스트해야하므로 방어 프로그래밍은 많은 작업을 추가하므로 TDD에 적합하지 않습니다.

TDD가 방어 프로그래밍을 대체 할 수 있다는 것이 사실입니까? 결과적으로 매개 변수 유효성 검사 (및 사용자 입력을 의미하지는 않음)가 필요하지 않습니까? 아니면 두 기술이 서로 보완됩니까?


120
생성자 확인없이 완전한 단위 테스트 라이브러리를 클라이언트에 전달하여 클래스 계약을 위반합니다. 그 단위 테스트는 지금 어떤 장점이 있습니까?
Robert Harvey

42
IMO 그것은 다른 길입니다. 방어적인 프로그래밍, 적절한 전제 조건 및 풍부한 유형의 시스템으로 인해 테스트가 중복됩니다.
gardenhead

37
"좋은 슬픔"이라는 답변을 게시 할 수 있습니까? 방어 프로그래밍은 런타임시 시스템을 보호합니다. 테스트는 생성자와 다른 메소드에 전달 된 유효하지 않은 인수를 포함하여 테스터가 생각할 수있는 모든 런타임 조건을 검사합니다. 테스트는 완료되면 적절한 예외 발생 또는 잘못된 인수가 전달 될 때 다른 의도적 인 동작이 발생합니다. 그러나 테스트는 런타임에 시스템을 보호하기 위해 대단한 일을하지 않습니다.
Craig

16
"단위 테스트는 클래스의 잘못된 사용을 잡아야합니다"-어, 어떻게? 단위 테스트는 올바른 인수와 잘못된 인수가 제공된 경우의 동작을 보여줍니다. 그들은 당신에게 주어진 모든 주장을 보여줄 수는 없습니다.
OJFord

34
소프트웨어 개발에 대한 독단적 인 사고가 어떻게 해로운 결론을 이끌어 낼 수 있는지에 대한 더 나은 예를 보지 못했다고 생각합니다.
sdenham

답변:


196

말도 안돼. TDD는 코드를 강제로 테스트에 통과시키고 모든 코드에 대해 테스트를 수행하도록합니다. 소비자가 코드를 잘못 호출하는 것을 막지 않으며 프로그래머가 테스트 사례를 빠뜨리는 것을 마술처럼 막지 않습니다.

방법론으로 사용자가 코드를 올바르게 사용하도록 강요 할 수는 없습니다.

거기 이다 당신이 검사를 추가 아마로 - 당신이 완벽하게 TDD를 한 경우 이전을 구현하는, 테스트 케이스에> 0 수표를 적발하고이 문제를 해결 한 것이라고 할 수있는 약간의 인수. 그러나 TDD를 수행 한 경우 요구 사항 (생성자에서> 0)이 먼저 실패한 테스트 사례로 나타납니다. 따라서 수표를 추가 한 후 테스트를 제공합니다.

방어 조건 중 일부를 테스트하는 것도 합리적입니다 (논리를 추가 한 이유는 무엇입니까? 왜 당신이 이것에 동의하지 않는지 모르겠습니다.

아니면 두 기술이 서로 보완됩니까?

TDD는 테스트를 개발할 것입니다. 매개 변수 유효성 검사를 구현하면 통과합니다.


7
전제 조건 검증을 테스트해야한다는 신념에 동의하지 않지만, 전제 조건 검증을 테스트해야하는 추가 작업이 전제 조건 검증을 작성하지 않는다는 주장이라는 동료의 의견에 동의하지 않습니다. 장소. 명확히하기 위해 게시물을 편집했습니다.
user2180613

20
@ user2180613 전제 조건 실패가 적절하게 처리되는지 테스트하는 테스트를 작성하십시오. 이제 점검을 추가하는 것이 "추가"작업이 아니라 테스트를 녹색으로 만들기 위해 TDD에 필요한 작업입니다. 동료의 의견에 따르면 테스트를 수행하고 실패한 것을 관찰 한 다음 전제 조건 점검 구현하면 TDD-purist 관점에서 볼 수 있습니다. 그가 수표를 완전히 무시한다고 말하면 어리석은 것입니다. TDD에는 잠재적 인 실패 모드에 대한 테스트 작성에 적극적으로 참여할 수 없다고하는 내용이 없습니다.
RM

4
@RM 사전 조건 점검을 테스트하기위한 테스트를 작성하고 있지 않습니다. 호출 된 코드의 올바른 동작을 테스트하기위한 테스트를 작성 중입니다. 전제 조건 점검은 테스트 관점에서 올바른 동작을 보장하는 불투명 한 구현 세부 사항입니다. 호출 된 코드에서 올바른 상태를 유지하는 더 좋은 방법을 생각한다면 전통적인 전제 조건 검사를 사용하는 대신 그렇게하십시오. 테스트는 당신이 성공했다 여부를 알아 부담합니다, 아직도 모르거나 상관하지 않습니다 어떻게 당신이 그것을했다.
Craig

@ user2180613 정말 멋진 정당성입니다. : D 소프트웨어 작성의 목표가 작성하고 실행해야하는 테스트 수를 줄이면 소프트웨어를 작성하지 마십시오.
Gusdor

3
이 대답의 마지막 문장이 그것을 못 박았습니다.
Robert Grant

32

방어 프로그래밍과 단위 테스트는 오류를 잡는 두 가지 방법이며 각각 다른 강점을 가지고 있습니다. 한 가지 방법으로 오류를 감지하면 오류 감지 메커니즘이 취약 해집니다. 두 가지를 모두 사용하면 공개 API가 아닌 코드에서도 누락 될 수있는 오류가 발생합니다. 예를 들어, 누군가 공개 API에 전달 된 유효하지 않은 데이터에 대한 단위 테스트를 추가하지 않았을 수 있습니다. 적절한 장소에서 모든 것을 확인하면 오류가 발생할 가능성이 높아집니다.

정보 보안에서는이를 심층 방어라고합니다. 여러 계층의 방어 기능을 사용하면 하나의 장애가 발생하더라도 다른 방어 계층을 확보 할 수 있습니다.

당신은 : 귀하의 동료가 한 가지에 대한 권리 해야 당신의 검증을 테스트하지만, 이것은 "불필요한 작업 '이 아니다. 다른 코드를 테스트하는 것과 동일합니다. 유효하지 않은 코드라도 모든 결과가 예상 한 결과를 갖기를 원합니다.


매개 변수 유효성 검사는 전제 조건 유효성 검사의 한 형태이며 단위 테스트는 사후 조건 유효성 검사라고 말하는 것이 맞습니까?
user2180613

1
"다른 코드를 테스트하는 것과 동일합니다. 유효하지 않은 코드라도 모든 결과가 예상 한 결과를 갖기를 원합니다." 이. 전달 된 입력이 처리하도록 설계되지 않았을 때 코드를 통과해서는 안됩니다. 이것은 "실패 (fail fast)"원칙을 위반하며 디버깅을 악몽으로 만들 수 있습니다.
jpmc26

@ user2180613-실제로는 아니지만 단위 테스트는 개발자가 기대하는 실패 조건을 확인하는 반면 방어 프로그래밍 기술은 개발자가 기대하지 않는 조건을 확인합니다. 단위 테스트 사용하여 사전 조건을 확인할 있습니다 (전제 조건을 확인하는 호출자에게 주입 된 모의 객체를 사용하여).
Periata Breatta

1
@ jpmc26 그렇습니다. 실패 테스트의“예상 결과”입니다. 정의되지 않은 (예기치 않은) 동작을 자동으로 표시하는 대신 실패한 것으로 표시되는지 테스트합니다.
KRyan

6
TDD는 자신의 코드에서 오류를 포착하고 방어 프로그래밍은 다른 사람들의 코드에서 오류를 포착합니다. 따라서 TDD는 당신이 충분히 방어
적임을

30

TDD는 방어 프로그래밍을 절대적으로 대체하지 않습니다. 대신 TDD를 사용하여 모든 방어가 제대로 작동하고 예상대로 작동하는지 확인할 수 있습니다.

TDD에서는 먼저 테스트를 작성하지 않고 코드를 작성해서는 안됩니다. 종교적으로 빨강 녹색 리 팩터주기를 따르십시오. 즉, 유효성 검사를 추가하려면 먼저이 유효성 검사가 필요한 테스트를 작성하십시오. 음수와 0을 사용하여 문제의 메소드를 호출하고 예외가 발생할 것으로 예상하십시오.

또한“리 팩터”단계를 잊지 마십시오. TDD는 테스트 중심 이지만 테스트 만을 의미하지는 않습니다 . 여전히 올바른 디자인을 적용하고 현명한 코드를 작성해야합니다. 방어 코드를 작성하는 것은 현명한 코드입니다. 기대치를보다 명확하게하고 코드 전체를보다 강력하게 만들 수 있기 때문에 가능한 오류를 조기에 발견하면 쉽게 디버깅 할 수 있습니다.

그러나 우리는 오류를 찾기 위해 테스트를 사용하지 않습니까? 주장과 테스트는 상호 보완 적입니다. 좋은 테스트 전략은 다양한 접근 방식혼합 하여 소프트웨어가 견고하다는 것을 보장합니다. 단위 테스트 또는 통합 테스트 또는 코드의 주장 만 만족스럽지 않으므로 적절한 노력으로 소프트웨어에 대한 충분한 신뢰를 얻으려면 적절한 조합이 필요합니다.

그러면 동료에 대한 개념적 오해가 있습니다. 단위 테스트는 클래스의 사용 을 테스트 할 수 없으며 클래스 자체 가 예상대로 작동합니다. 통합 테스트를 사용하여 다양한 구성 요소 간의 상호 작용이 작동하는지 확인할 수 있지만 가능한 테스트 사례의 조합 폭발로 모든 것을 테스트 할 수는 없습니다. 그러므로 통합 테스트는 몇 가지 중요한 경우로 제한되어야합니다. 에지 사례 및 오류 사례를 포함하는보다 자세한 테스트는 단위 테스트에 더 적합합니다.


16

방어 프로그래밍을 지원하고 보장하기위한 테스트가 있습니다.

방어 프로그래밍은 런타임시 시스템의 무결성을 보호합니다.

테스트는 (대부분 정적) 진단 도구입니다. 런타임에는 테스트가 보이지 않습니다. 그들은 높은 벽돌 벽이나 바위 돔을 세우는 데 사용되는 비계와 같습니다. 시공 중에 비계를 지탱하기 때문에 중요한 부품을 구조물 밖으로 두지 마십시오. 모든 중요한 조각 을 쉽게 넣을 수 있도록 비계를 제작하는 동안 비계가 있습니다 .

편집 : 유추

코드의 주석과 유사합니까?

의견은 목적이 있지만 중복되거나 해로울 수 있습니다. 예를 들어, 코드에 대한 본질적인 지식을 comment 에 넣은 다음 코드를 변경하면 주석이 전혀 관련이없고 최악의 경우 유해하게됩니다.

따라서 MethodA는 null을 취할 수 없으며 MethodB의 인수는이어야 > 0합니다. 와 같이 코드베이스에 대한 많은 기본 지식을 테스트에 넣었다고 가정 해보십시오 . 그런 다음 코드가 변경됩니다. A는 지금 Null이면 괜찮으며 B는 -10만큼 작은 값을 가질 수 있습니다. 기존 테스트는 이제 기능상 잘못되었지만 계속 통과합니다.

예, 코드를 업데이트하는 동시에 테스트를 업데이트해야합니다. 또한 코드를 업데이트 할 때 주석을 업데이트하거나 제거해야합니다. 그러나 우리는 이러한 일이 항상 일어나는 것은 아니며 실수가 있다는 것을 알고 있습니다.

테스트는 시스템의 동작을 확인합니다. 실제 행동은 테스트 자체가 아니라 시스템 자체에 내재되어 있습니다.

무엇이 잘못 될 수 있습니까?

테스트와 관련된 목표는 잘못 될 수있는 모든 것을 생각하고 올바른 동작을 확인하는 테스트를 작성한 다음 런타임 코드를 작성하여 모든 테스트를 통과시키는 것입니다.

방어 적 프로그래밍이 핵심 이라는 것을 의미한다 .

테스트가 포괄적 인 경우 TDD 방어 프로그래밍을 주도합니다 .

더 많은 테스트, 더 방어적인 프로그래밍

버그가 불가피하게 발견되면 버그를 나타내는 조건을 모델링하기 위해 더 많은 테스트가 작성됩니다. 그런 다음 해당 테스트를 통과시키는 코드로 코드가 수정 되고 새 테스트는 테스트 스위트에 남아 있습니다.

좋은 테스트 세트는 좋은 인수와 나쁜 인수를 모두 함수 / 방법에 전달하고 일관된 결과를 기대합니다. 이는 테스트 대상 구성 요소가 사전 조건 검사 (방어 프로그래밍)를 사용하여 전달 된 인수를 확인한다는 의미입니다.

일반적으로 말하면 ...

예를 들어, 특정 프로 시저에 대한 널 인수가 유효하지 않은 경우 하나 이상의 테스트가 널을 전달하고 "유효하지 않은 널 인수"예외 / 오류가 예상됩니다.

적어도 하나의 다른 테스트는 물론 유효한 인수 를 전달 하거나 큰 배열을 통해 루프하고 유효한 인수를 전달하여 결과 상태가 적절한 지 확인합니다.

테스트 해당 null 인수를 전달 하지 않고 예상 예외와 함께 스랩되는 경우 (코드가 전달 된 상태를 방어 적으로 확인했기 때문에 예외가 발생 함) null은 클래스의 속성에 할당되거나 묻힐 수 있습니다. 어떤 종류의 컬렉션에 있어서는 안됩니다.

이로 인해 소프트웨어가 배송 된 후 멀리 떨어진 지리적 로캘에서 클래스 인스턴스가 전달되는 시스템의 완전히 다른 부분에서 예기치 않은 동작이 발생할 수 있습니다 . 그리고 우리가 실제로 피하려고하는 것입니다. 맞습니까?

더 나빠질 수도 있습니다. 유효하지 않은 상태의 클래스 인스턴스는 직렬화 및 저장 될 수 있으며 나중에 사용하기 위해 재구성 될 때만 실패 할 수 있습니다. 잘 모르겠습니다. 어쩌면 그것은 자체의 지속적인 구성 상태를 직렬화 해제 할 수 없기 때문에 종료 후 다시 시작할 수없는 일종의 기계 제어 시스템 일 것입니다. 또는 클래스 인스턴스를 직렬화하여 다른 엔터티가 만든 완전히 다른 시스템으로 전달하면 해당 시스템이 중단 될 수 있습니다.

특히 다른 시스템의 프로그래머가 방어 적으로 코딩하지 않은 경우.


2
재미 있어요. downvote가 너무 빨라서 downvoter가 첫 번째 단락을 넘어 읽을 수있는 방법이 절대적으로있었습니다.
Craig

1
:-) 첫 번째 단락을 넘어서 읽지 않고 투표를했기 때문에 균형을 잡을 수 있기를 바랍니다.
SusanW

1
내가 :-) 할 수있는 최소한 듯 (사실을, 나는 않았다 단지 확인하기 위해 나머지 부분을 읽어 실수이 아니어야합니다 -.! 특히이 같은 주제에)
SusanW

1
아마 네가 가진 것 같아 :)
Craig

코드 계약과 같은 도구를 사용하여 컴파일 타임에 방어 검사를 수행 할 수 있습니다.
Matthew Whited

9

TDD 대신 일반적으로 "소프트웨어 테스팅"에 대해 이야기하고 일반적인 "방어 프로그래밍"대신에 어설 션을 사용하는 방어 프로그래밍을하는 가장 좋아하는 방법에 대해 이야기 해 봅시다.


따라서 소프트웨어 테스트를 수행하므로 프로덕션 코드에 어설 션 설명을 종료해야합니까? 이것이 잘못된 방식을 세어 보도록하겠습니다.

  1. 어설 션은 선택 사항이므로 마음에 들지 않으면 어설 션을 비활성화하고 시스템을 실행하십시오.

  2. 어설 션은 테스트가 수행 할 수없는 (및하지 말아야 할) 사항을 검사합니다. 테스트는 시스템에 대한 블랙 박스보기가 있어야하고, 어설 션에는 흰색 상자보기가 있기 때문입니다. (물론 그들이 살고 있기 때문에)

  3. 어설 션은 훌륭한 문서 도구입니다. 같은 내용을 주장하는 코드처럼 모호한 의견은 없었습니다. 또한 코드가 발전함에 따라 문서가 구식이되는 경향이 있으며 컴파일러가 강제로 시행 할 수는 없습니다.

  4. 어설 션은 테스트 코드에서 오류를 잡을 수 있습니다. 테스트에 실패한 상황에 처한 적이 있습니까, 누가 생산 코드 또는 테스트에 문제가 있는지 모르십니까?

  5. 어설 션은 테스트보다 더 적절할 수 있습니다. 테스트는 기능 요구 사항에 의해 규정 된 내용을 점검하지만 코드는 종종 그보다 훨씬 기술적 인 특정 가정을해야합니다. 기능 요구 사항 문서를 작성하는 사람들은 0으로 나누는 것을 거의 생각하지 않습니다.

  6. 어설 션은 광범위한 힌트 만 제공하는 오류를 정확하게 찾아냅니다. 따라서 테스트는 광범위한 전제 조건을 설정하고 긴 코드를 호출하며 결과를 수집하며 예상과 다른 결과를 찾습니다. 충분한 문제 해결이 이루어지면 결국 문제가 발생한 위치를 정확하게 찾을 수 있지만 일반적으로 어설 션이 먼저 찾습니다.

  7. 어설 션은 프로그램 복잡성을 줄입니다. 작성하는 모든 코드는 프로그램 복잡성을 증가시킵니다. 어설 션과 final( readonly) 키워드는 실제로 프로그램 복잡성을 감소시키는 것으로 알고있는 유일한 두 가지 구문입니다. 귀중합니다.

  8. 어설 션은 컴파일러가 코드를 더 잘 이해할 수 있도록 도와줍니다. 집에서 이것을 시도하십시오 : void foo( Object x ) { assert x != null; if( x == null ) { } }컴파일러는 조건 x == null이 항상 거짓 이라는 경고를 발행해야합니다 . 매우 유용 할 수 있습니다.

위의 내용은 내 블로그 ( 2014-09-21 "어설 션 및 테스트") 의 게시물 요약입니다.


나는 대부분이 대답에 동의하지 않는다고 생각합니다. (5) TDD에서 테스트 스위트는 사양입니다. 테스트를 통과시키는 가장 간단한 코드를 작성해야합니다. (4) 적록 워크 플로는 의도 한 기능이있을 때 테스트가 실패하고 통과하는지 확인합니다. 주장은 여기에별로 도움이되지 않습니다. (3,7) 문서는 문서이며 어설 션은 없습니다. 그러나 가정을 명시 적으로 작성하면 코드가보다 자체 문서화됩니다. 나는 그것들을 실행 가능한 주석으로 생각할 것이다. (2) 화이트 박스 테스트는 유효한 테스트 전략의 일부가 될 수 있습니다.
amon

5
"TDD에서 테스트 스위트는 사양입니다. 테스트를 통과시키는 가장 간단한 코드를 작성해야합니다. 더 이상 아무것도 아닙니다.": 이것이 항상 좋은 생각이라고 생각하지 않습니다. 코드에서 확인하려는 추가 내부 가정. 서로 상쇄하는 내부 버그는 어떻습니까? 테스트는 통과했지만 코드 내부의 몇 가지 가정이 잘못되어 나중에 교활한 버그가 발생할 수 있습니다.
조르지오

5

대부분의 답변에 중요한 차이점이 없다고 생각합니다. 코드 사용 방법에 따라 다릅니다.

테스트중인 응용 프로그램에 독립적 인 다른 클라이언트가 해당 모듈을 사용할 예정입니까? 타사에서 사용할 라이브러리 또는 API를 제공하는 경우 유효한 입력으로 코드 만 호출하도록 보장 할 방법이 없습니다. 모든 입력을 확인해야합니다.

그러나 문제의 모듈이 사용자가 제어하는 ​​코드로만 사용되는 경우 친구가 지적 할 수 있습니다. 단위 테스트를 사용하여 해당 모듈이 유효한 입력으로 만 호출되는지 확인할 수 있습니다. 전제 조건 검사가 여전히 좋은 연습으로 간주 될 수 있지만,이 트레이드 오프입니다 : 나는 당신이 쓰레기는 상태를 확인하는 코드를 알고 발생할 수 없다, 그냥 코드의 의도를 가린다.

전제 조건 검사에 더 많은 단위 테스트가 필요하다는 데 동의하지 않습니다. 어떤 형식의 유효하지 않은 입력을 테스트 할 필요가 없다고 결정하면 함수에 전제 조건 검사가 포함되어 있는지 여부는 중요하지 않습니다. 테스트는 구현 세부 정보가 아닌 동작을 확인해야합니다.


4
는 IF 라는 절차는 (원래 논쟁이되는) 입력의 유효성을 확인하지 않습니다 다음 단위 테스트 할 수 있도록 해당 모듈 만 유효한 입력으로 호출된다. 특히 유효하지 않은 입력으로 호출 될 수 있지만 테스트 된 경우 어쨌든 올바른 결과를 반환합니다. 다양한 유형의 정의되지 않은 동작, 오버플로 처리 등이 있습니다. 최적화되지 않은 테스트 환경에서 예상되는 결과를 반환 할 수는 있지만 생산 실패.
Peteris

@ Petetes : C와 같은 정의되지 않은 동작을 생각하고 있습니까? 다른 환경에서 다른 결과를 갖는 정의되지 않은 동작을 호출하는 것은 분명히 버그이지만 전제 조건 검사로도 막을 수는 없습니다. 예를 들어 포인터 인수가 유효한 메모리를 가리키는 지 어떻게 확인합니까?
JacquesB

3
이것은 가장 작은 상점에서만 작동합니다. 팀이 6 명을 넘어 서면 어쨌든 유효성 검사가 필요합니다.
Robert Harvey

1
@RobertHarvey :이 경우 시스템은 잘 정의 된 인터페이스가있는 서브 시스템으로 분할되고 인터페이스에서 입력 검증이 수행됩니다.
JacquesB

이. 코드에 따라 다릅니다.이 코드는 팀에서 사용합니까? 팀이 소스 코드에 액세스 할 수 있습니까? 순전히 내부 코드 인 경우 인수를 확인하는 것이 부담이 될 수 있습니다. 예를 들어 0을 확인한 다음 예외를 던지고 호출자가 코드를 들여다 보면이 클래스는 예외 등을 던질 수 있습니다.이 경우에는 객체는 2 lvl 전에 필터링되므로 0을받지 않습니다. 그것이 제 3자가 사용하는 라이브러리 코드라면 다른 이야기입니다. 모든 코드가 전 세계에서 사용되도록 작성된 것은 아닙니다.
Aleksander Fular

3

이 인수는 TDD 연습을 시작했을 때 "<유효하지 않은 입력> 일 때 <객체가 ​​<확실한 방식으로> 응답합니다"라는 형식의 단위 테스트가 2 ~ 3 번 증가했기 때문에 당황 스럽습니다. 동료가 기능을 검증하지 않고 이러한 단위 테스트를 성공적으로 통과하기 위해 어떻게 관리하고 있는지 궁금합니다.

반대의 경우, 단위 테스트 에서 다른 함수의 인수로 전달되는 나쁜 출력 을 생성하지 않는다는 것을 증명하는 것이 훨씬 어렵습니다. 첫 번째 경우와 마찬가지로 엣지 케이스의 철저한 적용 범위에 크게 의존하지만 모든 기능 입력은 사용자가 입력하거나 출력하지 않은 출력을 테스트 한 다른 기능의 출력에서 ​​가져와야한다는 추가 요구 사항이 있습니다. 타사 모듈.

다시 말해서, TDD가하는 일은 그것을 잊어 버리지 않도록 도와주는 것만 큼 유효성 검사 코드 가 필요 하지 않다는 것을 의미하지는 않습니다 .


2

나는 당신의 동료의 말을 나머지 답변의 대부분과 다르게 해석한다고 생각합니다.

논쟁은 다음과 같습니다.

  • 우리의 모든 코드는 단위 테스트를 거쳤습니다.
  • 귀하의 구성 요소를 사용하는 모든 코드는 우리의 코드이거나 다른 사람이 단위 테스트를하지 않은 경우 (명시 적으로 언급되지는 않았지만 "단위 테스트는 클래스의 잘못된 사용을 잡아야합니다"에서 이해합니다).
  • 따라서 함수의 각 호출자에 대해 구성 요소를 조롱하는 어딘가에 단위 테스트가 있으며 호출자가 해당 모의에 유효하지 않은 값을 전달하면 테스트가 실패합니다.
  • 따라서 테스트 결과에 따르면 유효하지 않은 값을 전달하면 함수의 기능이 중요하지 않습니다.

나 에게이 논쟁에는 논리가 있지만 가능한 모든 상황을 다루기 위해 단위 테스트에 너무 의존합니다. 간단한 사실은 100 % 라인 / 분기 / 경로 범위가 호출자가 전달할 수있는 모든 을 반드시 행사할 필요는 없지만 , 호출자의 가능한 모든 상태 (즉, 입력의 모든 가능한 값)를 100 % 적용하는 것입니다 변수) 계산으로는 불가능합니다.

(테스트가 가서 지금까지) 그러므로 나는 그들이 나쁜 값을 전달하지 않으며, 결코을 보장하기 위해 단위 테스트에 발신자을 선호하는 경향이 추가 (잘못된 값이 전달 될 때 구성 요소가 몇 가지 인식 방법으로 실패 할 것을 요구하는 적어도 선택한 언어로 잘못된 값을 인식 할 수있는 한). 이것은 통합 테스트에서 문제가 발생할 때 디버깅을 지원하고, 클래스 클래스의 사용자가 해당 종속성에서 코드 단위를 분리하는 데 덜 엄격한 모든 사용자를 지원합니다.

당신이 문서화하고 값이 <= 0이 전달 될 때 함수의 동작을 테스트하는 경우, 그,하지만 알고 마 음의 값이 더 이상 유효하지 않습니다 모든의 인수가보다 적어도 더 이상 유효하지 않은 ( throw그 이후, 예외도 발생하도록 문서화되어 있습니다!). 발신자는 그 방어 적 행동에 의존 할 권리가 있습니다. 언어가 허락한다면, 이것은 어떤 경우에도 가장 좋은 시나리오 일 수 있습니다-함수 에는 "유효하지 않은 입력"이 없지만 함수를 예외로 던지도록 기대하지 않는 호출자는 단위 테스트를 거쳐야합니다. t 그 원인이되는 값을 전달합니다.

동료가 대부분의 답변보다 다소 덜 잘못되었다고 생각하지만 두 가지 기술이 서로 보완된다는 동일한 결론에 도달합니다. 방어 적으로 프로그램하고 방어 적 수표를 문서화 한 후 테스트하십시오. 코드 사용자가 실수를 할 때 유용한 오류 메시지의 이점을 얻을 수없는 경우에만 작업이 "불필요"합니다. 이론적으로 코드를 통합하기 전에 모든 코드를 철저히 단위 테스트하고 테스트에 오류가 없으면 오류 메시지가 표시되지 않습니다. 실제로 TDD 및 전체 의존성 주입을 수행하는 경우에도 개발 중에 탐색을 수행하거나 테스트가 중단 될 수 있습니다. 결과적으로 코드가 완벽하기 전에 코드를 호출합니다!


호출자가 나쁜 값을 전달하지 않도록 테스트하는 데 중점을 둔 비즈니스는 많은베이스 ackwards 종속성이 있고 취약한 코드를 쉽게 구분할 수있는 것처럼 보입니다. 나는 그 접근 방식에 대한 사고로 인한 코드가 마음에 들지 않는다고 생각합니다.
Craig

@Craig : 의존성을 조롱하여 테스트 할 컴포넌트를 분리했다면 왜 의존성에 올바른 값만 전달 하는지 테스트 하지 않습니까? 구성 요소를 분리 할 수없는 경우 실제로 우려 사항을 분리 했습니까? 방어 코딩에 동의하지 않지만 방어 검사가 호출 코드의 정확성을 테스트하는 수단이라면 엉망입니다. 그래서 질문자의 동료는 수표가 중복되어 있지만 이것을 작성하지 않는 이유로 잘못 보는 것이 맞다고 생각합니다. :-)
Steve Jessop

내가 볼 수있는 유일한 단점은 여전히 ​​내 구성 요소가 해당 종속성에 유효하지 않은 값을 전달할 수 없다는 것을 테스트하고 있다는 것입니다. 전적으로 동의해야하지만 비즈니스 관리자가 개인을 결정하는 데 얼마나 많은 결정이 내려지는가 파트너가 호출 할 수 있도록 구성 요소 공개? 이것은 실제로 데이터베이스 디자인과 ORM에 대한 현재의 모든 사랑을 상기 시켜서 많은 (대부분의 젊은 사람들) 데이터베이스가 바보 같은 네트워크 스토리지이며 제약, 외래 키 및 저장 프로 시저로 자신을 보호해서는 안된다고 선언합니다.
Craig

내가 본 다른 시나리오는 물론 실제 종속성이 아닌 모의 호출 만 테스트한다는 것입니다. 궁극적으로 호출자의 코드가 아닌 특정 전달 값으로 적절하게 작동하거나 작동하지 않는 종속성의 코드입니다. 따라서 종속성은 올바른 일을 수행해야하며, 종속성을 확인하려면 종속성에 대한 독립적 인 테스트 범위가 충분해야합니다. 우리가 이야기하는이 테스트를 "단위"테스트라고합니다. 각 의존성은 하나의 단위입니다. :)
Craig

1

공용 인터페이스는 오용 될 수 있습니다

개인이 아닌 인터페이스에 대해서는 동료의 "단위 테스트에서 클래스의 잘못된 사용을 파악해야합니다"라는 주장이 엄격하게 허위입니다. 공용 함수는 정수 인수로 호출 할 수 있다면, 그것은과 함께 호출 될 수 있는 정수 인수 및 코드를 적절하게 행동해야한다. 공용 함수 시그니처가 예를 들어 Java Double type을 허용하는 경우 null, NaN, MAX_VALUE, -Inf는 모두 가능한 값입니다. 단위 테스트는 테스트가이 클래스를 사용하는 코드를 테스트 할 수 없기 때문에 그 코드가 아직 작성되지 않기 때문에, 클래스의 잘못된 사용을 잡을 수 없어 당신에 의해 작성되지 않을 수 있습니다, 확실히의 범위에있을 것이다 당신의 단위 테스트 .

다른 한편으로,이 접근법은 (아마도 훨씬 더 많은) 개인 속성에 유효 할 수 있습니다-만약 클래스 가 어떤 사실이 항상 사실임을 보장 할 수 있다면 (예를 들어, 속성 X는 절대 null 일 수없고, 정수 위치는 최대 길이를 초과하지 않습니다) 함수 A가 호출되면 모든 전제 조건 데이터 구조가 올바르게 구성됩니다.) 성능상의 이유로이를 다시 확인하지 않고 대신 단위 테스트에 의존하는 것이 적절할 수 있습니다.


이것의 첫 번째 단락은 런타임에 코드를 실행하는 단위 테스트가 아니기 때문에 사실입니다. 그것은 어떤의 다른 런타임 코드와 실제 상황과 나쁜 사용자 입력을 변경하고 해킹 시도가 코드와 상호 작용합니다.
Craig

1

오용 방지는 요구 사항으로 인해 개발 된 기능 입니다. (모든 인터페이스에서 오용에 대한 엄격한 검사가 필요한 것은 아닙니다 (예 : 매우 좁게 사용되는 내부 인터페이스).)

이 기능에는 테스트가 필요합니다. 오용 방지가 실제로 작동합니까? 이 기능을 테스트하는 목적은 다음과 같은 점을 보여주지 않는 것입니다. 모듈이 잘못 사용하여 검사에 걸리지 않도록하는 것.

특정 검사가 필요한 기능인 경우 실제로 일부 검사가 있으면 검사가 불필요하다고 주장하는 것은 무의미합니다. 매개 변수 3이 음수 일 때 예외를 발생시키는 것이 어떤 함수의 특징이라면, 그것은 협상 할 수 없습니다. 그렇게 할 것입니다.

그러나 동료가 실제로 입력에 대한 특정 점검이 필요하지 않은 상황 , 잘못된 입력에 대한 특정 응답 이있는 상황의 관점에서 실제로 의미가 있다고 생각합니다 . 견고성.

일부 최상위 기능의 진입 점검은 부분적으로 약하거나 잘못 테스트 된 내부 코드를 예기치 않은 매개 변수 조합으로부터 보호하기 위해 있습니다 (예 : 코드가 잘 테스트 된 경우 점검이 필요하지 않습니다. 코드는 " 날씨 "잘못된 매개 변수).

동료의 아이디어에는 진실이 있으며 그가 의미하는 바는 이것입니다. 우리가 방어 적으로 코딩되고 모든 오용에 대해 개별적으로 테스트 된 매우 견고한 하위 레벨 조각으로 함수를 구축하면 상위 레벨 기능이 가능할 수 있습니다 자체적 인 자체 점검없이 견고합니다.

계약을 위반하면 예외를 던지거나 다른 방법으로 하위 수준 기능을 잘못 사용하게됩니다.

이것의 유일한 문제는 하위 수준 예외가 상위 수준 인터페이스에만 국한되지 않는다는 것입니다. 이것이 문제인지 여부는 요구 사항에 따라 다릅니다. 요구 사항이 단순히 "함수에 대한 기능이 강력하지 않고 충돌이 아닌 어떤 종류의 예외를 던지거나 가비지 데이터로 계산을 계속해야한다"라면 실제로는 하위 레벨의 모든 견고성에 의해 다룰 수 있습니다. 세워짐.

기능에 매개 변수와 관련된 매우 상세하고 자세한 오류보고가 필요한 경우 하위 레벨 검사는 해당 요구 사항을 완전히 충족시키지 못합니다. 그것들은 어떻게 든 함수가 어떻게 든 터져 버리는 것을 보장합니다 (잘못된 매개 변수 조합을 계속하지 않으면 쓰레기 결과가 나타납니다). 클라이언트 코드가 특정 오류를 구체적으로 잡아서 처리하도록 작성되면 올바르게 작동하지 않을 수 있습니다. 클라이언트 코드 자체는 매개 변수의 기반이되는 데이터를 입력으로 확보하고있을 수 있으며이를 확인하고 문서화 된대로 특정 값으로 잘못된 값을 변환하는 기능을 기대할 수 있습니다 ( 처리되지 않은 다른 오류보다는 소프트웨어 이미지가 중지 될 수 있습니다.

TL; DR : 동료는 바보가 아닐 수도 있습니다. 요구 사항이 완전히 정해져 있지 않고 "서명되지 않은 요구 사항"이 무엇인지에 대한 아이디어가 서로 다르기 때문에 같은 일에 대해 다른 관점으로 서로 과거를 이야기하고 있습니다. 매개 변수 검사에 대한 특정 요구 사항이없는 경우 세부 검사를 코딩해야한다고 생각합니다. 동료들은 매개 변수가 잘못되었을 때 강력한 하위 레벨 코드를 날려 버린다고 생각합니다. 코드를 통해 작성되지 않은 요구 사항에 대해 논쟁하는 것은 다소 비생산적입니다. 코드가 아니라 요구 사항에 동의하지 않는 것을 인식하십시오. 코딩 방식은 요구 사항이 무엇이라고 생각하는지 반영합니다. 동료의 방식은 요구 사항에 대한 그의 견해를 나타냅니다. 그런 식으로 보면 옳고 그른 것이 ' 코드 자체에서 t; 코드는 사양이 무엇인지에 대한 귀하의 의견을 제시하는 프록시 일뿐입니다.


이것은 느슨한 요구 사항을 다루는 일반적인 철학적 어려움과 관련이 있습니다. 잘못된 입력이 주어 졌을 때 (예를 들어, 이미지 디코더가 여가 시간에 임의의 픽셀 조합을 생성하거나 비정상적으로 종료하도록 보장 할 수있는 경우 이미지 디코더가 요구 사항을 충족 할 수있는 경우) 기능이 허용 되지만 총 여유 공간이 임의로 작동하는 경우 , 그러나 악의적으로 제작 된 입력이 임의 코드를 실행할 수있는 경우에는 허용되지 않습니다.
supercat

1

테스트는 클래스의 계약을 정의합니다.

결과적으로 테스트 가 없으면 정의 되지 않은 동작 이 포함 된 계약이 정의 됩니다. 따라서 당신이 통과 null하고 Foo::Frobnicate(Widget widget)런타임에 혼란을 겪을 때, 당신은 여전히 ​​수업의 계약 안에 있습니다.

나중에 "우리는 정의되지 않은 행동의 가능성을 원하지 않습니다"를 결정합니다. 이는 합리적인 선택입니다. 즉, 전달을위한 예상 된 동작이 있어야한다는 것을 의미 null에를 Foo::Frobnicate(Widget widget).

그리고 그 결정을 문서화하여

[Test]
void Foo_FrobnicatesANullWidget_ThrowsInvalidArgument() 
{
    Given(Foo foo);
    When(foo.Frobnicate(null));
    Then(Expect_Exception(InvalidArgument));
}

1

좋은 테스트 세트는 클래스 의 외부 인터페이스를 연습하고 이러한 오용이 올바른 응답 (예외 또는 "정확한"으로 정의 된 것)을 생성하는지 확인합니다. 실제로 클래스를 작성하는 첫 번째 테스트 사례는 범위를 벗어난 인수로 생성자를 호출하는 것입니다.

완전히 단위 테스트 된 접근 방식으로 제거되는 방어 프로그래밍의 종류는 외부 코드에 의해 위반 될 수없는 내부 불변량 의 불필요한 검증입니다 .

내가 가끔 사용하는 유용한 아이디어는 객체의 불변량을 테스트하는 방법을 제공하는 것입니다. 테어 다운 메소드는 객체에 대한 외부 액션이 변하지 않는 것을 확인하기 위해 호출 할 수 있습니다.


0

TDD 테스트 는 코드 개발 과정 에서 실수 를 발견 할 수 있습니다 .

방어 프로그래밍의 일부로 설명하는 범위 검사 는 코드 사용 중에 실수를 잡을 수 있습니다 .

두 도메인이 동일하다면, 즉 작성중인 코드 가이 특정 프로젝트에서 내부적으로 만 사용되는 경우 TDD가 설명하는 방어 프로그래밍 범위의 필요성을 배제하지만 해당 유형의 경우에만 범위 검사는 TDD 테스트에서 구체적으로 수행됩니다 .


특정 예로, TDD를 사용하여 재무 코드 라이브러리를 개발했다고 가정하십시오. 테스트 중 하나는 특정 값이 음수 일 수 없다고 주장 할 수 있습니다. 이를 통해 라이브러리 개발자는 기능을 구현할 때 실수로 클래스를 오용하지 않습니다.

그러나 라이브러리가 릴리스되고 내 프로그램에서 라이브러리를 사용하면 TDD 테스트로 인해 음수 값을 할당하지 못하게됩니다 (노출한다고 가정). 경계 검사가 가능합니다.

내 요점은 코드가 TDD가 없는 다른 프로그래머가 사용하는 라이브러리가 될 경우 코드가 더 큰 응용 프로그램 개발의 일부로 내부적으로 만 사용되는 경우 TDD 어설 션이 음수 문제를 해결할 수 있다는 것입니다. 프레임 워크 및 테스트 , 경계 점검 문제.


1
나는 downvote하지 않았지만, 이런 종류의 논쟁에 미묘한 차이를 추가하면 물을 진흙으로 만든다는 전제에 대한 downvotes에 동의합니다.
Craig

@Craig 추가 한 특정 예제에 대한 의견에 관심이 있습니다.
Blackhawk

나는 예제의 특이성을 좋아한다. 내가 여전히 가지고있는 유일한 관심사는 전체 논쟁의 일반적인 것입니다. 예를 들어; 함께 팀에 새로운 개발자가 와서 해당 재무 모듈을 사용하는 새로운 구성 요소를 작성합니다. 새로운 사람은 시스템의 작동 방식에 대한 모든 종류의 전문 지식이 테스트되는 코드가 아니라 테스트에 포함되어 있다는 사실을 제외하고 시스템의 모든 복잡성을 인식하지 못합니다.
Craig

따라서 새로운 사람 / gal은 몇 가지 중요한 테스트 생성을 놓치게되고 테스트에서 중복성을 갖게됩니다. 시스템의 다른 부분에서 테스트를 수행하면 동일한 조건을 확인하고 적절한 주장을하는 대신 시간이 지남에 따라 일관성이 없어집니다. 사전 조건은 조치가있는 코드를 체크인합니다.
Craig

1
그런 것. 여기서 많은 주장은 호출 코드에 대한 테스트가 모든 검사를 수행하도록하는 것에 관한 것입니다. 그러나 어느 정도의 팬인 (fan-in)이있는 경우 여러 장소에서 동일한 검사를 수행하게되는데 이는 유지 보수 문제입니다. 프로 시저에 대한 유효한 입력 범위가 변경되었지만 다른 구성 요소를 시험하는 테스트에 내장 된 해당 범위에 대한 도메인 지식이있는 경우 어떻게해야합니까? 나는 여전히 방어 프로그래밍을 선호하고 프로파일 링을 사용하여 성능 문제가 있는지 언제 해결할지 결정합니다.
Craig

0

TDD와 방어 프로그래밍은 서로 밀접한 관련이 있습니다. 둘 다 사용하는 것은 중복되지 않지만 실제로는 보완 적입니다. 함수가 있으면 함수가 설명대로 작동하는지 확인하고 테스트를 작성하십시오. 잘못된 입력, 나쁜 반환, 나쁜 상태 등의 경우에 발생하는 일을 다루지 않으면 테스트를 충분히 작성하지 않고 모든 테스트가 통과 되더라도 코드가 약해집니다.

임베디드 엔지니어로서 저는 단순히 2 바이트를 더하고 다음과 같은 결과를 반환하는 함수 작성 예제를 사용하고 싶습니다.

uint8_t AddTwoBytes(uint8_t a, uint8_t b, uint8_t *sum); 

이제 단순히 방금 수행 *(sum) = a + b했다면 일부 입력 에서만 작동 합니다. a = 1그리고 b = 2만들 것이다 sum = 3; 합계의 크기는 바이트이다, 그러나 때문에, a = 100그리고 b = 200만들 것 sum = 44오버플로 인해. C에서는이 경우 오류가 발생하여 함수가 실패했음을 나타냅니다. 예외를 던지는 것은 코드에서 동일합니다. 이러한 조건이 발생하면 처리되지 않고 여러 가지 문제가 발생할 수 있으므로 실패를 고려하거나 처리 방법을 테스트하지 않으면 장기적으로 작동하지 않습니다.


좋은 면접 질문 예처럼 보입니다 (반환 값과 "out"매개 변수가있는 이유와 sum널 포인터는 어떻게됩니까 ?).
Toby Speight
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.