테스트 가능한 코드가 더 나은 코드입니까?


103

내 코드로 단위 테스트를 정기적으로 작성하는 습관을들이려고하지만 먼저 테스트 가능한 코드 를 작성하는 것이 중요하다는 것을 읽었습니다 . 이 질문 은 테스트 가능한 코드를 작성하는 SOLID 원칙에 대해 다루지 만 테스트 설계를 전혀 계획하지 않고 이러한 설계 원칙이 유리한지 (또는 적어도 유해하지 않은지) 알고 싶습니다. 명확하게하기 위해-필기 시험의 중요성을 이해합니다. 이것은 그들의 유용성에 대한 질문이 아닙니다.

혼란을 설명하기 위해이 질문에서 영감을 얻은 작품 에서 작가는 현재 시간을 확인하고 시간에 따라 일부 값을 반환하는 함수의 예를 제공합니다. 저자는 내부적으로 사용하는 데이터 (시간)를 생성하여 테스트하기가 어렵 기 때문에이 코드를 잘못된 코드로 지적합니다. 그러나 나에게는 시간을 논쟁으로 넘기는 것이 과잉 인 것 같습니다. 어떤 시점에서 값을 초기화해야하며 소비에 가장 가까운 이유는 무엇입니까? 또한, 내 마음 속에있는 방법의 목적은 현재 시간을 기준으로 일부 값을 반환하는 것입니다.이 값 은이 목적을 변경할 수 있음을 암시하는 매개 변수로 만듭니다. 이 질문과 다른 질문으로, 테스트 가능한 코드가 "더 나은"코드와 동의어인지 궁금해합니다.

테스트가없는 경우에도 테스트 가능한 코드 작성이 여전히 좋은 방법 입니까?


테스트 가능한 코드가 실제로 더 안정적입니까? 중복으로 제안되었습니다. 그러나 그 질문은 코드의 "안정성"에 관한 것이지만 가독성, 성능, 결합 등과 같은 다른 이유로 코드가 우수한 지에 대해 더 광범위하게 묻고 있습니다.


24
dem 등성 (Idempotency) 이라는 시간을 거쳐야하는 특수한 기능 특성이 있습니다 . 이러한 함수는 주어진 인수 값으로 호출 될 때마다 동일한 결과 를 생성 하므로 더 테스트 가능할뿐만 아니라보다 구성 가능하며 추론하기가 더 쉽습니다.
Robert Harvey

4
"더 나은 코드"를 정의 할 수 있습니까? "유지 관리 가능", "IOC 컨테이너 매거진없이 사용하기 쉬움"을 의미합니까?
k3b

7
실제 시스템 시간을 사용한 다음 표준 시간대 오프셋이 변경 되었기 때문에 테스트 실패가 없었습니다.
Andy

5
테스트 할 수없는 코드보다 낫습니다.
Tulains Córdova

14
내가 그 멱등를 호출하지 것이다 @RobertHarvey, 나는 말할 것이다 참조 투명성 : 경우에 func(X)반환 "Morning", 다음의 모든 발행 수 교체 func(X)와 함께 "Morning"프로그램을 변경되지 않습니다 (즉 호출이. func값을 반환 이외의 아무것도하지 않음). Idempotency는 func(func(X)) == X(유형이 정확하지 않음), 또는 func(X); func(X);같은 부작용 을 수행하는 것과 같지만 func(X)(여기서 부작용은 없음)
Warbo

답변:


116

단위 테스트의 일반적인 정의와 관련하여 아니오라고 말할 것입니다. 나는 간단한 코드 때문에 테스트 프레임 워크에 맞게 비틀 필요 (예. 인터페이스와의 복잡한 만들어 본 적이 IOC는이 모든 곳에서 마법에 의해 전달 분명해야 인터페이스 통화 및 데이터 레이어를 통해 따라하기 어려운 일을). 이해하기 쉬운 코드 또는 단위 테스트하기 쉬운 코드 중 하나를 선택할 때마다 유지 관리 가능한 코드를 사용합니다.

이것은 테스트를 의미하는 것이 아니라 다른 방법이 아니라 자신에게 맞는 도구를 갖추는 것을 의미합니다. 테스트하는 다른 방법이 있습니다 (그러나 이해하기 어려운 코드는 항상 나쁜 코드입니다). 예를 들어 덜 세분화 된 단위 테스트를 만들거나 (예 : 단위가 일반적으로 방법이 아니라 클래스라는 Martin Fowler 의 태도) 자동화 된 통합 테스트를 통해 프로그램을 실행할 수 있습니다. 테스트 프레임 워크가 녹색 틱으로 표시되는 것만 큼 예쁘지는 않지만 프로세스의 게임 화가 아닌 코드를 테스트 한 것입니다.

코드 간의 유지 관리가 용이하고 유닛 테스트에 적합한 인터페이스를 정의한 다음 구성 요소의 공용 인터페이스를 테스트하는 테스트를 작성하여 단위 테스트에 적합합니다. 또는 더 나은 테스트 프레임 워크를 얻을 수 있습니다 (모의를 모의 코드로 컴파일하지 않고 런타임에 함수를 모의하여 기능을 모방하는 것). 더 나은 단위 테스트 프레임 워크를 사용하면 런타임에 시스템 GetCurrentTime () 기능을 사용자 고유의 기능으로 대체 할 수 있으므로 테스트 도구에 맞게 인공 래퍼를 도입 할 필요가 없습니다.


3
의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
세계 엔지니어

2
필자는 마지막 단락에서 설명하는 것과 같은 Python을 Mock과 함께 사용할 수있는 적어도 하나의 언어를 알고 있다는 점에 주목할 필요가 있습니다. 모듈 가져 오기가 작동하는 방식 때문에 키워드를 제외한 거의 모든 것을 표준 API 메소드 / 클래스 등 모의로 대체 할 수 있습니다. 따라서 가능하지만 이러한 종류의 유연성을 지원하는 방식으로 언어를 설계해야 할 수도 있습니다.
jpmc26

5
"테스트 가능한 코드"와 "테스트 프레임 워크에 적합한 코드 [트위스트]"에는 차이가 있다고 생각합니다. "트위스트 된"코드가 나쁘고 인터페이스가 좋은 "테스트 가능한"코드가 좋다는 것에 동의하는 것 외에는이 주석으로 어디로 가고 있는지 잘 모르겠습니다.
Bryan Oakley

2
기사의 의견 에 내 생각을 표현했습니다 (확장 의견은 여기에 허용되지 않기 때문에). 확인하십시오! 명확하게 : 나는 언급 된 기사의 저자입니다 :)
Sergey Kolodiy

@BryanOakley에 동의해야합니다. "테스트 가능한 코드"는 문제가 분리되어 있음을 나타냅니다. 다른 측면의 간섭없이 측면 (모듈)을 테스트 할 수 있습니다. 이것이 "프로젝트 지원 특정 테스트 규칙 조정"과 다른 점이라고 생각합니다. 이것은 디자인 패턴과 유사합니다. 강요해서는 안됩니다. 디자인 패턴 을 올바르게 활용 하는 코드는 강력한 코드로 간주됩니다. 테스트 원칙에도 동일하게 적용됩니다. 코드를 "테스트 가능"하게 만들면 프로젝트 코드가 과도하게 비틀어지면 문제가있는 것입니다.
Vince Emigh

68

테스트가없는 경우에도 테스트 가능한 코드 작성이 여전히 좋은 방법입니까?

우선, 테스트가 없다는 것은 코드를 테스트 할 수있는 것보다 더 큰 문제입니다. 단위 테스트가 없다는 것은 코드 / 기능을 수행하지 않았 음을 의미합니다.

그건 그렇고, 테스트 가능한 코드를 작성하는 것이 중요하다고 말하지 않을 것입니다 . 유연한 코드 를 작성하는 것이 중요합니다 . 유연하지 않은 코드는 테스트하기가 어렵 기 때문에 접근 방식과 사람들이 호출하는 내용이 많이 중복됩니다.

따라서 코드 작성에는 항상 우선 순위가 있습니다.

  1. 작동하도록하십시오 -코드가 필요한 것을 수행하지 않으면 가치가 없습니다.
  2. 유지 관리 가능 -코드를 유지 관리 할 수 ​​없으면 작업이 빨리 중단됩니다.
  3. 융통성있게 만들기 -코드가 융통성이 없으면 비즈니스가 필연적으로 들어올 때 작동을 멈추고 코드가 XYZ를 수행 할 수 있는지 묻습니다.
  4. 그것을 빨리 확인 - 기본 허용 수준을 넘어, 성능 그냥 국물입니다.

단위 테스트는 코드 유지 관리에 도움이되지만 특정 시점까지만 가능합니다. 코드를 읽기 어렵게하거나 단위 테스트를 수행하기에 더 취약하게 만들면 생산성이 떨어집니다. "테스트 가능한 코드"는 일반적으로 유연한 코드이므로 기능이나 유지 관리 성만큼 중요하지는 않습니다. 현재 시간과 같이 유연하게 만드는 것은 좋지만 코드를 더 복잡하고 올바르게 사용하기 어렵게함으로써 유지 관리 성이 손상됩니다. 유지 관리가 더 중요하기 때문에 테스트 가능성이 낮은 경우에도 더 간단한 접근 방식을 시도합니다.


4
나는 당신이 테스트 가능한 것과 유연한 것 사이의 관계를 좋아합니다. 그것은 모든 문제를 더 이해하기 쉽게 만듭니다. 유연성을 통해 코드를 조정할 수 있지만 이해하기에는 좀 더 추상적이고 이해하기 쉽지만 이점을 위해 가치있는 희생입니다.
워너비 코더

3
즉, 단위 테스팅 프레임 워크가 직접 접근 할 수 있도록 비공개 또는 공개 된 패키지 수준의 메소드를 자주 봅니다. 이상적인 접근 방식과는 거리가 멀다.
jwenting

4
@WannabeCoder 물론 시간을 절약 할 수있는 유연성을 추가 할 가치가 있습니다. 그렇기 때문에 우리는 인터페이스에 대해 모든 단일 메소드를 작성하지 않습니다. 대부분의 경우 처음부터 너무 많은 유연성을 통합하는 대신 코드를 다시 작성하는 것이 더 쉽습니다. YAGNI는 여전히 매우 강력한 원칙입니다. "필요하지 않은"항목이 무엇이든 소급하여 추가 하면 미리 구현하는 것보다 평균적으로 더 많은 작업 수행 할 수 없습니다 . 내 경험에서 유연성과 관련하여 가장 큰 문제가있는 것은 YAGNI를 따르지 않는 코드 입니다.
Luaan

3
"단위 테스트가 없다는 것은 코드 / 기능을 수행하지 않았다는 의미입니다"-사실이 아닙니다. "완료된 정의"는 팀이 결정하는 것입니다. 어느 정도의 테스트 범위를 포함하거나 포함하지 않을 수 있습니다. 그러나 기능이 테스트되지 않으면 기능을 "완료"할 수 없다는 엄격한 요구 사항은 없습니다. 팀은 테스트를 요구할 수도 있고 그렇지 않을 수도 있습니다.
aroth

3
@Telastyn 10 년이 넘는 개발 기간 동안, 단위 테스트 프레임 워크를 의무화 한 팀은 없었으며, 둘 중 하나 (둘 다 커버리지가 낮음) 만있는 팀이 없었습니다. 한 곳에는 작성중인 기능을 테스트하는 방법에 대한 단어 문서가 필요했습니다. 그게 다야. 아마 내가 운이 좋지 않습니까? 나는 반 단위 테스트가 아닙니다 (진실하게, 나는 SQA.SE 사이트를 수정했습니다, 나는 매우 프로 단위 테스트입니다!). 그러나 당신의 성명 주장만큼 널리 퍼지지 않았습니다.
corsiKa

50

예, 좋습니다. 테스트 가능성은 테스트를위한 것이 아니기 때문입니다. 그것은 명확성과 이해를 돕기 위해 제공됩니다.

아무도 테스트 자체에 신경 쓰지 않습니다. 우리는 항상 기초를 확인하지 않고 완벽한 코드를 작성하기에 충분하지 않기 때문에 큰 회귀 테스트 스위트가 필요하다는 것은 슬픈 사실입니다. 우리가 할 수 있다면, 테스트의 개념을 알 수 없으며,이 모든 것이 문제가되지는 않을 것입니다. 나는 분명히 할 수 있으면 좋겠다. 그러나 경험에 따르면 거의 모든 사람들이 할 수 없으므로 비즈니스 코드 작성에서 시간이 걸리더라도 코드를 테스트하는 것이 좋습니다.

테스트를 수행하면 테스트 자체와 무관하게 비즈니스 코드가 어떻게 향상됩니까? 기능을 쉽게 입증 할 수있는 단위로 세분화함으로써. 이 단위는 또한 우리가 달리 유혹하고있는 것보다 더 쉽게 얻을 수 있습니다.

당신의 시간 예는 좋은 지적입니다. 만큼 당신이 같은 단지 당신이 프로그램을 갖는 아무 소용이 없다는 것을 생각 현재 시간을 반환하는 기능을 가지고있다. 이것을 올바르게 얻는 것이 얼마나 어려울 수 있습니까? 그러나 필연적으로 프로그램은 다른 코드 에서이 기능을 사용 하므로 다른 시간을 포함하여 다른 조건에서 해당 코드 를 테스트하려고합니다 . 따라서 단선 currentMillis()전화 를 불신하지 않고 제어 된 환경에서 해당 전화를 건 사람 을 확인해야하기 때문에 함수가 반환하는 시간을 조작 할 수있는 것이 좋습니다 . 따라서 테스트 가능한 코드를 갖는 것은 그 자체로도 유용하지만 그다지 주목할 가치가없는 것 같습니다.


또 다른 예는 어떤 이유로 든 한 프로젝트의 코드 일부를 다른 장소로 가져 오려는 경우입니다. 기능의 서로 다른 부분이 서로 독립적 일수록 필요한 기능을 정확하게 추출하는 것이 더 쉬워집니다.
valenterry

10
Nobody cares about the tests themselves-- 나는한다. 테스트가 주석이나 readme 파일보다 코드의 기능을 더 잘 설명하는 테스트라는 것을 알았습니다.
jcollum

나는 잠시 동안 테스트 방법에 대해 천천히 읽었고 (어쨌든 아직 단위 테스트를 전혀하지 않는 사람) 제어 된 환경에서 호출을 확인하는 마지막 부분과 함께 제공되는보다 유연한 코드 모든 종류의 것들이 딸깍 소리를 내도록 만들었습니다. 감사합니다.
plast1k

12

어떤 시점에서 값을 초기화해야하며 소비에 가장 가까운 이유는 무엇입니까?

내부적으로 생성 된 것과 다른 값으로 해당 코드를 재사용해야 할 수도 있습니다. 매개 변수로 사용할 값을 삽입하는 기능은 "지금"(코드를 호출 할 때 "지금"의미)뿐만 아니라 원하는 시간을 기준으로 해당 값을 생성 할 수 있도록합니다.

코드를 실제로 테스트 할 수있게한다는 것은 처음부터 두 가지 시나리오 (생산 및 테스트)에서 사용할 수있는 코드를 만드는 것을 의미합니다.

기본적으로 테스트없이 코드를 테스트 할 수있는 인센티브는 없다고 주장 할 수 있지만 재사용 가능한 코드를 작성하는 데 큰 이점이 있으며 두 개는 동의어입니다.

또한 내 마음 속에있는 방법의 목적은 현재 시간을 기준으로 일부 값을 반환하는 것입니다.이 값은이 목적을 변경할 수 있음을 암시하는 매개 변수로 만듭니다.

또한이 방법의 목적은 시간 값을 기준으로 일부 값을 반환하는 것이며 "지금"을 기준으로 값을 생성하는 데 필요합니다. 그중 하나가 더 융통성이 있으며 변형을 선택하는 데 익숙해지면 코드 재 사용률이 높아집니다.


10

이런 식으로 말하는 것은 어리석은 것처럼 보일 수 있지만 코드를 테스트하려면 테스트 가능한 코드를 작성하는 것이 좋습니다. 물어:

어떤 시점에서 값을 초기화해야하며 소비에 가장 가까운 이유는 무엇입니까?

정확하게 당신이 참조하는 예에서, 그것은 그 코드를 테스트 할 수 없기 때문입니다. 하루 중 다른 시간에 테스트의 하위 세트 만 실행하지 않는 한. 또는 시스템 시계를 재설정하십시오. 또는 다른 해결 방법. 단순히 코드를 유연하게 만드는 것보다 나쁜 점이 있습니다.

융통성이 없어서, 그 작은 방법은 (1) 시스템 시간을 얻은 다음 (2) 그것에 기초한 값을 반환하는 두 가지 책임이 있습니다.

public static string GetTimeOfDay()
{
    DateTime time = DateTime.Now;
    if (time.Hour >= 0 && time.Hour < 6)
    {
        return "Night";
    }
    if (time.Hour >= 6 && time.Hour < 12)
    {
        return "Morning";
    }
    if (time.Hour >= 12 && time.Hour < 18)
    {
        return "Afternoon";
    }
    return "Evening";
}

책임을 세분화하여 통제 할 수없는 부분 ( DateTime.Now)이 나머지 코드에 미치는 영향을 최소화 할 수 있습니다. 그렇게하면 체계적으로 테스트 할 수있는 부작용과 함께 위의 코드가 더 단순 해집니다.


1
따라서 아침 일찍 테스트하여 원하는 "Night"결과를 얻었는지 확인해야합니다. 어렵습니다. 이제 2016 년 2 월 29 일에 날짜 처리가 올바른지 확인하고 싶다고 가정 해보십시오. 일부 iOS 프로그래머 (및 아마도 다른 사람들)는 초보자가 실수로 연초 직전이나 직후에 혼란을 겪고 있습니다. 어떻게합니까? 그것을 테스트하십시오. 그리고 경험을 바탕으로 2020 년 2 월 2 일의 날짜 처리를 점검 할 것입니다.
gnasher729

1
@ gnasher729 정확히 내 요점입니다. "이 코드를 테스트 가능하게 만들기"는 많은 (테스트) 문제를 해결할 수있는 간단한 변경입니다. 테스트를 자동화하지 않으려면 코드를 그대로 전달할 수 있습니다. 그러나 일단 "테스트 가능"하면 더 나을 것입니다.
Eric King

9

확실히 비용이 들지만 일부 개발자는 비용을 잊어 버려 비용을 지불하는 데 익숙합니다. 예를 들어, 이제 하나가 아닌 두 개의 단위가 있으며 추가 종속성을 초기화하고 관리하기 위해 호출 코드가 필요했으며 GetTimeOfDay테스트 할 수는 있지만 새로운 보트 테스트와 동일합니다 IDateTimeProvider. 좋은 테스트를 받으면 일반적으로 혜택이 비용보다 중요하다는 것입니다.

또한 어느 정도 테스트 가능한 코드를 작성하면 코드를보다 유지 관리 가능한 방식으로 설계 할 수 있습니다. 새로운 의존성 관리 코드는 성가 시므로 가능한 모든 시간 종속 함수를 그룹화하십시오. 예를 들어, 시간 경계에 페이지를 바로로드 할 때, 일부 요소가 이전 시간을 사용하여 렌더링되고 일부 요소가 이후 시간을 사용하는 경우와 같은 버그를 완화하고 수정하는 데 도움이 될 수 있습니다. 또한 현재 시간을 얻기 위해 반복적 인 시스템 호출을 피함으로써 프로그램 속도를 높일 수 있습니다.

물론 이러한 아키텍처 개선은 기회를 인식하고 구현하는 사람에 따라 크게 달라집니다. 유닛에 매우 집중하는 데 가장 큰 위험 중 하나는 더 큰 그림을 보지 못하는 것입니다.

많은 단위 테스트 프레임 워크를 사용하면 런타임에 모의 객체를 원숭이 패치하여 모든 혼란없이 테스트 가능성의 이점을 얻을 수 있습니다. 심지어 C ++에서 수행되는 것을 보았습니다. 테스트 비용이 가치가없는 것처럼 보이는 상황에서 그 능력을 살펴보십시오.


+1-단위 테스트 작성을보다 쉽게하기 위해 디자인과 아키텍처를 개선해야합니다.
BЈовић

3
+-중요한 코드 아키텍처. 더 쉬운 테스트는 행복한 부작용입니다.
gbjbaanb

8

테스트 가능성에 기여하는 모든 특성이 테스트 가능성의 맥락 밖에서 바람직하지는 않을 수 있습니다. 예를 들어 인용 한 시간 매개 변수에 대해 테스트와 관련이없는 정당성을 제시하는 데 어려움이 있지만 테스트 가능성에 기여하는 특성을 광범위하게 말하면 또한 테스트 가능성에 관계없이 좋은 코드에 기여합니다.

일반적으로 테스트 가능한 코드는 가단 코드입니다. 작고 불 연속적이며 응집력이있는 덩어리로되어있어 개별 비트를 재사용 할 수 있습니다. 잘 구성되고 이름이 잘 지정되어 있습니다 (이름 지정에 더주의를 기울이는 일부 기능을 테스트 할 수 있습니다. 테스트를 작성하지 않은 경우 일회용 기능의 이름은 덜 중요합니다). 그것은 시간 매개 변수와 같이 더 파라 메트릭 인 경향이 있으므로 원래 의도 된 목적 이외의 다른 컨텍스트에서 사용하기에 개방적입니다. 건조하기 때문에 덜 복잡하고 이해하기 쉽습니다.

예. 테스트와 상관없이 테스트 가능한 코드를 작성하는 것이 좋습니다.


MyRYCurrentTime 메소드에 GetCurrentTime을 래핑하는 것은 테스트 툴링을 지원하는 것 외에는 아무런 이점없이 OS 호출을 매우 많이 반복합니다. 그것은 가장 단순한 예일 뿐이며 실제로는 더 나빠집니다.
gbjbaanb

1
"이점없이 OS 호출에 응답하기"-한 시계로 서버에서 실행하고 다른 시간대의 AWS 서버와 통신 할 때까지 코드가 손상 될 때까지 모든 코드와 MyGetCurrentTime을 사용하도록 업데이트하여 대신 UTC를 반환하십시오. ; 시계 왜곡, 일광 절약 및 OS 호출을 맹목적으로 신뢰하는 것이 좋지 않거나 다른 대체품을 사용할 수있는 단일 지점이있는 다른 이유가 있습니다.
Andrew Hill

8

코드가 실제로 작동한다는 것을 증명 하려면 테스트 가능한 코드를 작성하는 것이 중요 합니다.

나는 특정 테스트 프레임 워크에 맞게 코드를 왜곡 된 왜곡으로 변형시키는 것에 대한 부정적인 감정에 동의하는 경향이 있습니다.

다른 한편으로, 여기의 모든 사람들은 어느 시점에서나 다루어야 할 엄청나게 1,000 줄 길이의 마법 기능을 다루어야 만했습니다. 명백한 의존성은 다른 곳 (또는 의존성이 시각화하기가 거의 불가능한 그 자체의 어딘가에 있음)이며 정의 상으로는 거의 테스트 할 수 없습니다. 테스트 프레임 워크가 과장되었다는 개념 (고유가없는 개념)은 제 생각에 품질이 좋지 않고 테스트 할 수없는 코드를 작성하는 무료 라이센스로 간주되어서는 안됩니다.

예를 들어, 테스트 중심 개발 이상은 단일 책임 절차 작성에 집중하는 경향이 있으며 이는 확실히 좋은 일입니다. 개인적으로, 단일 책임, 단일 진실 소스, 통제 범위 (기괴한 전역 변수 없음)를 구입하고 취성 의존성을 최소로 유지 하면 코드 테스트 수 있습니다. 특정 테스트 프레임 워크에서 테스트 할 수 있습니까? 누가 알아. 그러나 아마도 다른 방법이 아닌 좋은 코드에 맞게 조정 해야하는 테스트 프레임 워크 일 수 있습니다.

그러나 명확하게 말하면, 다른 사람이 쉽게 이해할 수 없을 정도로 영리하거나 길거나 상호 의존적 인 코드는 좋은 코드가 아닙니다. 또한 우연히도 쉽게 테스트 할 수있는 코드가 아닙니다.

내 요약에 가까워 지면 테스트 가능한 코드가 더 나은 코드입니까?

모르겠다. 여기 사람들은 몇 가지 유효한 포인트를 가지고 있습니다.

그러나 더 나은 코드는 테스트 가능한 코드 인 경향이 있다고 생각합니다 .

그리고 진지한 노력에 사용하기 위해 진지한 소프트웨어에 대해 이야기하는 경우 테스트되지 않은 코드를 배송하는 것이 고용주 또는 고객의 돈으로 할 수있는 가장 책임있는 일이 아닙니다.

또한 일부 코드는 다른 코드보다 더 엄격한 테스트가 필요하며 그렇지 않은 척하는 것은 약간 바보입니다. 셔틀의 필수 시스템과 사용자를 연결 한 메뉴 시스템을 테스트하지 않은 경우 우주 왕복선에서 우주 비행사가되고 싶습니까? 또는 원자로의 온도를 모니터링하는 소프트웨어 시스템이 테스트되지 않은 원자력 발전소 직원? 반면에 간단한 읽기 전용 보고서를 생성하는 약간의 코드가 문서와 수천 단위 테스트로 가득 찬 컨테이너 트럭을 필요로합니까? 나는 확실히 희망하지 않습니다. 그냥 ...


1
"더 나은 코드는 테스트 가능한 코드 인 경향이 있습니다" 이것이 핵심입니다. 테스트 가능하게 만드는 것이 더 나아지지는 않습니다. 더 잘 만들기 자주하는 것은 그것을 테스트 할 수있게하고, 테스트는 종종 당신에게 당신이 그것을 더 좋게 만드는 데 사용할 수있는 정보를 제공하지만, 시험의 단순한 존재는 품질을 의미하지 않으며, (희귀) 예외가 있습니다.
anaximander

1
바로 그거죠. 반론을 고려하십시오. 테스트 할 수없는 코드 인 경우 테스트되지 않습니다. 테스트를 거치지 않은 경우 실제 상황 이외의 작동 여부를 어떻게 알 수 있습니까?
pjc50

1
모든 테스트는 코드가 테스트를 통과했음을 증명합니다. 그렇지 않으면 단위 테스트 코드는 버그가 없으며 우리는 그렇지 않습니다.
wobbily_col

@anaximander 정확합니다. 적어도 테스트의 존재는 금기 사항 일 가능성이 있으며, 모든 초점이 확인란을 선택하는 것에 만 초점을 둔 경우 품질 코드가 저하 될 수 있습니다. "각 기능마다 최소 7 개의 단위 테스트가 있습니까?" "검사." 그러나 코드가 품질 코드라면 테스트하기가 더 쉬울 것이라고 생각합니다.
Craig

1
...하지만 시험에서 관료주의를 만드는 것은 총 낭비 일 수 있으며 유용한 정보 나 신뢰할만한 결과를 산출하지 못할 수 있습니다. 어쨌든; 누군가 SSL Heartbleed 버그를 테스트했으면 좋겠다 . 아니면 Apple goto fail 버그입니까?
Craig

5

그러나 나에게는 시간을 논쟁으로 넘기는 것이 과잉 인 것 같습니다.

당신이 옳고, 조롱하면 코드를 테스트 하고 시간을 피할 수 있습니다 (pun 의도가 정의되지 않음). 예제 코드 :

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

이제 윤초 동안 일어나는 일을 테스트하고 싶다고 가정 해 봅시다. 당신이 말했듯이, 이것을 과도하게 테스트하려면 (생산) 코드를 변경해야합니다.

def time_of_day(now=None):
    now = now if now is not None else datetime.datetime.utcnow()
    return now.strftime('%H:%M:%S')

파이썬이 윤초를 지원 하면 테스트 코드는 다음과 같습니다.

def test_handle_leap_second(self):
    actual = time_of_day(
        now=datetime.datetime(year=2015, month=6, day=30, hour=23, minute=59, second=60)
    expected = '23:59:60'
    self.assertEquals(actual, expected)

이것을 테스트 할 수는 있지만 코드는 필요 이상으로 복잡하며 테스트는 여전히 대부분의 프로덕션 코드에서 사용할 코드 분기를 안정적으로 실행할 수 없습니다 (즉, 값을 전달하지 않음 now). mock 을 사용하여이 문제를 해결하십시오 . 원래 프로덕션 코드에서 시작 :

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

테스트 코드 :

@unittest.patch('datetime.datetime.utcnow')
def test_handle_leap_second(self, utcnow_mock):
    utcnow_mock.return_value = datetime.datetime(
        year=2015, month=6, day=30, hour=23, minute=59, second=60)
    actual = time_of_day()
    expected = '23:59:60'
    self.assertEquals(actual, expected)

여러 가지 이점이 있습니다.

  • 종속성과 time_of_day 독립적 으로 테스트 하고 있습니다.
  • 프로덕션 코드 와 동일한 코드 경로 를 테스트하고 있습니다.
  • 생산 코드는 가능한 간단합니다.

참고로, 미래의 조롱 프레임 워크가 이와 같은 것들을 더 쉽게 만들 수 있기를 바랍니다. 예를 들어, 조롱 된 함수를 문자열로 참조해야하기 때문에 time_of_day다른 소스를 사용하기 시작할 때 IDE가 자동으로 쉽게 변경하도록 할 수 없습니다 .


참고 : 기본 인수가 잘못되었습니다. 한 번만 정의되므로 함수는 항상 처음 평가 된 시간을 반환합니다.
ahruss

4

잘 작성된 코드의 품질은 변경 하기에 강력 하다는 것입니다 . 즉, 요구 사항이 변경되면 코드 변경이 비례해야합니다. 이것은 이상적인 (항상 달성되는 것은 아님)이지만 테스트 가능한 코드를 작성하면이 목표에 더 가까이 다가 갈 수 있습니다.

왜 우리와 더 가까워 지나요? 프로덕션 환경에서 코드는 다른 모든 코드를 통합하고 상호 작용하는 등 프로덕션 환경 내에서 작동합니다. 단위 테스트에서는이 환경의 많은 부분을 제거합니다. 테스트 변경되었으므로 코드 가 변경 되기 쉬워졌습니다 . 우리는 생산에 사용하는 것과 다른 입력 (모의 물, 실제로는 전달되지 않는 잘못된 입력 등)과 함께 다른 방식으로 단위를 사용하고 있습니다.

시스템에서 변경이 발생하는 날 코드를 준비합니다. 시간대를 기준으로 시간 계산에 다른 시간이 필요하다고 가정 해 봅시다. 이제 우리는 그 시간을 넘길 수 있고 코드를 변경할 필요가 없습니다. 시간을 전달하고 싶지 않고 현재 시간을 사용하고 싶을 때는 기본 인수 만 사용하면됩니다. 우리의 코드는 테스트 가능하기 때문에 변경 하기에 강력합니다 .


4

내 경험에 비추어 볼 때, 프로그램을 빌드 할 때 가장 중요하고 광범위한 결정 중 하나 는 코드를 단위 ( "단위"가 가장 넓은 의미로 사용되는) 로 나누는 방법 입니다. 클래스 기반 OO 언어를 사용하는 경우 프로그램을 구현하는 데 사용되는 모든 내부 메커니즘을 몇 개의 클래스로 분리해야합니다. 그런 다음 각 클래스의 코드를 몇 가지 방법으로 분리해야합니다. 일부 언어에서는 코드를 함수로 나누는 방법이 선택됩니다. 또는 SOA를 수행하는 경우 구축 할 서비스 수와 각 서비스에 어떤 서비스를 제공할지 결정해야합니다.

선택한 분류는 전체 프로세스에 막대한 영향을 미칩니다. 올바른 선택을하면 코드를보다 쉽게 ​​작성할 수 있으며 테스트 및 디버깅을 시작하기 전에도 버그가 줄어 듭니다. 변경하고 유지 관리하기가 더 쉽습니다. 흥미롭게도, 좋은 분류를 찾으면 보통 가난한 것보다 테스트 하는 것이 더 쉽다는 것이 밝혀졌습니다 .

왜 그렇습니까? 나는 모든 이유를 이해하고 설명 할 수 없다고 생각합니다. 그러나 한 가지 이유는 좋은 분류가 구현 단위에 대해 적당한 "그레인 크기"를 선택한다는 것을 의미하기 때문입니다. 너무 많은 기능과 너무 많은 논리를 단일 클래스 / 메소드 / 함수 / 모듈 / 등에 넣기를 원하지 않습니다. 이를 통해 코드를보다 쉽게 ​​읽고 쓸 수 있지만 테스트하기도 더 쉽습니다.

그러나 그것은 단지 그런 것이 아닙니다. 좋은 내부 설계는 각 구현 단위의 예상되는 동작 (입력 / 출력 / 등)을 명확하고 정확하게 정의 할 수 있음을 의미합니다. 이것은 테스트에 중요합니다. 좋은 디자인은 일반적으로 각 구현 단위가 다른 구현 단위에 중간 정도의 종속성을 갖음을 의미합니다. 이를 통해 다른 사람들이 코드를 읽고 이해하기 쉽게 만들뿐만 아니라 테스트하기도 더 쉽습니다. 그 이유는 계속됩니다. 어쩌면 다른 사람들은 내가 할 수없는 더 많은 이유를 말할 수 있습니다.

귀하의 질문에있는 예와 관련하여, "좋은 코드 디자인"은 모든 외부 종속성 (예 : 시스템 시계에 대한 종속성)이 항상 "주입"되어야한다고 말하는 것과 같지 않습니다. 그것은 좋은 생각 일지 모르지만 여기서 설명하는 것과는 별개의 문제이며 장단점을 탐구하지는 않을 것입니다.

또한 현재 시간을 반환하고 파일 시스템에서 작동하는 등 시스템 기능을 직접 호출하더라도 코드를 개별적으로 테스트 할 수는 없습니다. 비결은 시스템 라이브러리의 반환 값을 위조 할 수있는 특수 버전의 표준 라이브러리를 사용하는 것입니다. 다른 사람들이이 기술을 언급 한 것을 본 적이 없지만 많은 언어 및 개발 플랫폼과 함께하는 것은 매우 간단합니다. 희망적으로 언어 런타임은 오픈 소스이며 빌드하기 쉽습니다. 코드를 실행하는 데 링크 단계가 포함되어 있으면 링크되는 라이브러리를 쉽게 제어 할 수 있기를 바랍니다.

요약하면, 테스트 가능한 코드는 반드시 "좋은"코드 일 필요는 없지만 "좋은"코드는 일반적으로 테스트 할 수 있습니다.


1

당신이가는 경우 SOLID 원칙 당신은 이것을 확장 특히, 좋은 측면에있을 것입니다 KISS , DRY , 그리고 YAGNI .

나를위한 하나의 누락 포인트는 방법의 복잡성의 포인트입니다. 간단한 getter / setter 방법입니까? 그런 다음 테스트 프레임 워크를 만족시키기 위해 테스트를 작성하는 것은 시간 낭비입니다.

데이터를 조작하는 더 복잡한 방법이고 내부 논리를 변경해야하더라도 제대로 작동하려면 테스트 방법이 필요합니다. 며칠 / 주 / 월 후에 코드를 변경해야하는 경우가 많았으며 테스트 사례가 정말 기뻤습니다. 방법을 처음 개발할 때 테스트 방법으로 테스트했으며 작동 할 것이라고 확신했습니다. 변경 후 내 기본 테스트 코드가 여전히 작동했습니다. 따라서 변경 사항으로 인해 프로덕션에서 오래된 코드가 손상되지 않았 음을 확신했습니다.

테스트 작성의 또 다른 측면은 다른 개발자에게 방법을 사용하는 방법을 보여주는 것입니다. 개발자는 메소드 사용 방법 및 리턴 값에 대한 예제를 여러 번 검색합니다.

그냥 내 두 센트 .

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