Programming

비공개 메소드를 테스트해야합니까, 아니면 공개 메소드 만 테스트해야합니까?

procodes 2020. 3. 6. 07:55
반응형

비공개 메소드를 테스트해야합니까, 아니면 공개 메소드 만 테스트해야합니까?


개인 메소드를 테스트하는 방법에 대한 이 게시물 을 읽었습니다 . 나는 항상 객체 외부에서 호출 될 공용 메소드 만 테스트하는 것이 더 빠르다고 생각하기 때문에 테스트하지 않습니다. 개인 메소드를 테스트합니까? 항상 테스트해야합니까?


개인 메소드를 단위 테스트하지 않습니다. 개인용 메소드는 클래스 사용자에게 숨겨져 야하는 구현 세부 사항입니다. 개인 메서드를 테스트하면 캡슐화가 중단됩니다.

개인 메소드가 거대하거나 복잡하거나 자체 테스트가 필요할 정도로 중요하다는 것을 알게되면 다른 클래스에 넣고 공개합니다 ( Method Object ). 그런 다음 이제는 자체 클래스에 존재하는 이전의 개인용이지만 현재 공용 인 방법을 쉽게 테스트 할 수 있습니다.


테스트의 목적은 무엇입니까?

지금까지 답변의 대부분은 개인 메소드가 퍼블릭 인터페이스가 잘 테스트되고 작동하는 한 중요하지 않은 구현 세부 사항이라고 말합니다. 테스트의 유일한 목적이 공용 인터페이스가 작동하도록 보장하는 것이라면 이것이 맞습니다 .

개인적으로, 코드 테스트의 주된 용도는 향후 코드 변경으로 인해 문제가 발생하지 않도록하고 문제가있는 경우 디버깅 작업을 지원하는 것입니다. 공개 인터페이스 (더 이상 그렇지 않다면!)가 철저히 목적을 달성하는 것처럼 개인 메소드를 테스트하는 것이 그 목적을 달성한다는 것을 알았습니다.

다음을 고려하십시오. 개인 메소드 B를 호출하는 공용 메소드 A가 있습니다. A와 B는 모두 메소드 C를 사용합니다. C는 변경 될 수 있으며 (아마도 벤더에 의해) 벤더가 테스트에 실패합니다. 문제가 A의 C 사용, B의 C 사용 또는 두 가지 모두에 있는지 알 수 있도록 비공개 테스트를 수행해도 B에 대한 테스트를하는 것이 유용하지 않습니까?

공용 메서드의 테스트 범위가 불완전한 경우 개인 메서드를 테스트해도 가치가 높아집니다. 이것이 일반적으로 피하고자하는 상황이지만 효율성 단위 테스트는 버그를 찾는 테스트와 해당 테스트의 개발 및 유지 관리 비용에 따라 달라집니다. 경우에 따라 100 % 테스트 범위의 이점이 해당 테스트 비용을 보증하기에 충분하지 않은 것으로 판단되어 공용 인터페이스의 테스트 범위에 차이가 생길 수 있습니다. 이러한 경우, 개인 메소드의 대상이 명확한 테스트는 코드 기반에 매우 효과적 일 수 있습니다.


나는 그들의 책 Pragmatic Unit Testing 에서 Dave Thomas와 Andy Hunt의 조언을 따르는 경향이 있습니다 .

일반적으로 테스트 목적으로 캡슐화를 깨고 싶지 않습니다 (또는 엄마가 "개인을 노출시키지 마십시오!"라고 말했듯이). 대부분의 경우 공용 메소드를 사용하여 클래스를 테스트 할 수 있어야합니다. 개인 또는 보호 된 액세스 뒤에 숨겨져있는 중요한 기능이있는 경우, 다른 클래스가 나 가려고 애 쓰고 있다는 경고 신호일 수 있습니다.

그러나 때로는 완전히 강력한 프로그램을 작성 하고 있다는 확신을 가지기 때문에 개인적인 방법을 테스트하지 않아도 됩니다.


프로젝트에서 최신 QA 권장 사항을 점점 더 많이 따르면서 개인 기능을 테스트해야한다고 생각합니다.

함수 당 순환 복잡도10 개를 넘지 않아야 합니다.

이 정책을 시행 할 때의 부작용은 매우 큰 공공 기능 중 다수가 더 집중되고 더 잘 명명 된 개인 기능 으로 나뉘어져 있다는 것입니다 .
공공 기능은 여전히 ​​(물론) 있지만 본질적으로 모든 개인 '하위 기능'을 호출하도록 축소되었습니다.

콜 스택을 읽기가 훨씬 쉽기 때문에 실제로 멋지다. '어떻게 도착했는지')

그러나 이제는 이러한 개인 기능을 직접 단위 테스트하는 것이 더 쉬워 보이고 대규모 공용 기능의 테스트는 시나리오를 해결해야하는 일종의 '통합'테스트로 남겨 둡니다.

그냥 내 2 센트.


예, 개인 함수는 공개 메소드로 테스트되었지만 TDD (Test Driven Design)에서는 응용 프로그램의 가장 작은 부분을 테스트하는 것이 좋기 때문에 개인 함수를 테스트합니다. 그러나 테스트 단위 클래스에있을 때는 개인 기능에 액세스 할 수 없습니다. 개인 메소드를 테스트하기 위해 수행하는 작업은 다음과 같습니다.

왜 우리는 개인적인 방법을 가지고 있습니까?

우리는 공용 함수에서 읽을 수있는 코드를 만들고 싶어하기 때문에 개인 함수는 주로 클래스에 존재합니다. 이 클래스의 사용자가 이러한 메소드를 직접 호출하는 것이 아니라 공용 메소드를 통해 호출하는 것을 원하지 않습니다. 또한 클래스를 확장 할 때 (보호 된 경우) 행동을 변경하지 않기를 원하므로 비공개입니다.

코딩 할 때는 TDD (Test-driven-design)를 사용합니다. 이것은 때때로 우리가 개인적인 기능을 테스트하고 싶어한다는 것을 의미합니다. 비공개 함수는 phpUnit에서 테스트 할 수 없습니다. Test 클래스에서는 비공개 함수이므로 액세스 할 수 없기 때문입니다.

우리는 여기에 3 가지 해결책이 있다고 생각합니다.

1. 당신은 당신의 공개 방법을 통해 개인을 테스트 할 수 있습니다

장점

  • 간단한 단위 테스트 ( '해킹'필요 없음)

단점

  • 프로그래머는 공개 방법을 이해하고 개인 메소드 만 테스트하려고합니다.
  • 응용 프로그램의 테스트 가능한 가장 작은 부분을 테스트하지 않습니다

2. 개인이 매우 중요한 경우, 별도의 새로운 클래스를 만드는 것이 코드 스멜 일 수 있습니다.

장점

  • 이 클래스를 새로운 클래스로 리팩토링 할 수 있습니다. 중요한 경우 다른 클래스도이를 필요로하기 때문에
  • 테스트 가능한 단위는 이제 공개 방법이므로 테스트 가능

단점

  • 클래스가 필요하지 않은 경우 클래스를 작성하고 싶지 않으며 메소드가 시작된 클래스에서만 사용됩니다.
  • 추가 된 오버 헤드로 인한 잠재적 성능 손실

3. 액세스 수정자를 (최종) 보호로 변경하십시오.

장점

  • 응용 프로그램의 테스트 가능한 가장 작은 부분을 테스트하고 있습니다. 최종 보호 기능을 사용하는 경우이 기능은 무시할 수 없습니다 (개인과 마찬가지로)
  • 성능 손실 없음
  • 추가 오버 헤드 없음

단점

  • 보호 된 개인 액세스 권한을 변경하고 있으며 이는 자녀가 액세스 할 수 있음을 의미합니다.
  • 테스트 클래스에는 여전히 Mock 클래스가 필요합니다.

class Detective {
  public function investigate() {}
  private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
  public function investigate() {}
  final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {

  public test_sleepWithSuspect($suspect) 
  {
    //this is now accessible, but still not overridable!
    $this->sleepWithSuspect($suspect);
  }
}

따라서 테스트 단위는 이제 test_sleepWithSuspect를 호출하여 이전 개인 함수를 테스트 할 수 있습니다.


몇 가지 이유로 개인 기능 테스트를 좋아하지 않습니다. 다음과 같습니다 (TLDR 사람들의 주요 요점).

  1. 일반적으로 클래스의 개인 메서드를 테스트하려는 경우 디자인 냄새입니다.
  2. 공용 인터페이스를 통해 테스트 할 수 있습니다 (클라이언트가 호출 / 사용하는 방식이므로 테스트하려는 방법). 개인 메소드에 대한 모든 통과 테스트에서 녹색 표시등을 보면서 잘못된 보안 감각을 얻을 수 있습니다. 공용 인터페이스를 통해 개인 기능의 엣지 케이스를 테스트하는 것이 훨씬 좋습니다.
  3. 개인 방법을 테스트하면 심각한 테스트 복제 (유사하게 보이는 느낌)가 발생할 위험이 있습니다. 필요한 것보다 많은 테스트가 중단 될 수 있기 때문에 요구 사항이 변경 될 때 중요한 결과를 초래합니다. 또한 테스트 스위트로 인해 리팩토링하기 어려운 위치에 놓을 수 있습니다. 테스트 스위트는 안전하게 재 설계하고 리팩토링 할 수 있도록 도와주기 때문에 가장 아이러니합니다.

구체적인 예를 통해 이들 각각을 설명하겠습니다. 2)와 3)은 다소 복잡하게 연결되어 있으므로 개인 예제를 테스트해서는 안되는 별도의 이유를 고려하지만 예제는 비슷합니다.

개인 메소드를 테스트하는 것이 적절한 경우가 있습니다. 위에 나열된 단점을 인식하는 것이 중요합니다. 나중에 자세히 설명하겠습니다.

또한 TDD가 결국 개인 메소드를 테스트하는 데 유효한 변명이 아닌 이유에 대해서도 설명합니다.

나쁜 디자인에서 길을 리팩토링

내가 본 가장 일반적인 (반) 패터 중 하나는 Michael Feathers"Iceberg"클래스라고 부르는 것입니다 (Michael Feathers가 누구인지 모르는 경우 "레거시 코드로 효과적으로 작업하기"). 전문 소프트웨어 엔지니어 / 개발자인지 아는 사람). 이 문제를 일으키는 다른 (반) 패턴이 있지만, 이것이 내가 우연히 발견 한 가장 일반적인 패턴입니다. "Iceberg"클래스에는 하나의 공용 메소드가 있으며 나머지는 개인 메소드입니다 (따라서 개인 메소드를 테스트하려고하는 이유입니다). 일반적으로 고독한 공용 메소드가 있기 때문에 "Iceberg"클래스라고하지만 나머지 기능은 전용 메소드의 형태로 수중에 숨겨져 있습니다.

규칙 평가자

예를 들어, GetNextToken()문자열에서 문자열을 연속적으로 호출하여 예상 결과를 반환하는지 확인 하여 테스트 할 수 있습니다. 이와 같은 함수는 테스트를 보증합니다. 특히 토큰 화 규칙이 복잡한 경우에는 그 동작이 쉽지 않습니다. 그것이 그렇게 복잡한 것은 아니라고 가정하고 공간으로 구분 된 토큰을 묶고 싶습니다. 따라서 테스트를 작성하면 다음과 같이 보일 수 있습니다 (일부 언어에 관계없는 의사 코드, 아이디어는 분명합니다).

TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    re = RuleEvaluator(input_string);

    ASSERT re.GetNextToken() IS "1";
    ASSERT re.GetNextToken() IS "2";
    ASSERT re.GetNextToken() IS "test";
    ASSERT re.GetNextToken() IS "bar";
    ASSERT re.HasMoreTokens() IS FALSE;
}

글쎄, 그것은 실제로 꽤 좋아 보인다. 변경시이 동작을 유지하고 싶습니다. 그러나 GetNextToken()A는 개인 기능! 그래서 우리는 컴파일조차 할 수 없기 때문에 이것을 테스트 할 수 없습니다 (파이썬과 같은 스크립팅 언어와는 달리 실제로 공개 / 개인을 강제하는 언어를 사용한다고 가정). 그러나 RuleEvaluator단일 책임 원칙 (Single Responsibility Principle)을 따르도록 수업을 바꾸는 것은 어떻습니까? 예를 들어 파서, 토크 나이저 및 평가자가 하나의 클래스에 걸린 것 같습니다. 그러한 책임을 분리하는 것이 낫지 않습니까? 또한 Tokenizer클래스 를 만들면 공개 메소드는 HasMoreTokens()and GetNextTokens()입니다. RuleEvaluator클래스는있을 수 있습니다Tokenizer멤버로서의 객체. 이제 Tokenizer클래스 대신 클래스를 테스트한다는 점을 제외하고는 위와 동일한 테스트를 유지할 수 있습니다 RuleEvaluator.

UML에서 다음과 같이 보일 수 있습니다.

규칙 평가자가 리팩토링

이 새로운 디자인은 모듈성을 향상 시키므로 잠재적으로 시스템의 다른 부분에서 이러한 클래스를 재사용 할 수 있습니다 (할 수 없었기 전에 개인 메소드는 정의에 의해 재사용 할 수 없음). 이는 이해도 / 지역성을 높이고 RuleEvaluator를 분류 할 때의 주요 이점입니다.

GetNextToken()메소드는 Tokenizer클래스에서 공개 되기 때문에 이번에는 실제로 컴파일한다는 점을 제외하면 테스트는 매우 유사하게 보입니다 .

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS FALSE;
}

공용 인터페이스를 통해 개인 구성 요소 테스트 및 테스트 복제 방지

당신이 문제를 더 적은 수의 모듈 식 구성 요소로 나눌 수 있다고 생각하지 않더라도 ( 공식적으로 시도 하면 95 % 할 수 있음) 공용 인터페이스를 통해 개인 기능을 간단히 테스트 할 수 있습니다. 많은 경우 개인 구성원은 공용 인터페이스를 통해 테스트되므로 테스트 할 가치가 없습니다. 내가 본 것은 매우 유사한 테스트이지만 두 가지 다른 함수 / 방법을 테스트하는 것입니다. 결국 요구 사항이 변경 될 때 (그리고 항상 수행되는 경우) 이제는 1 대신에 2 번의 테스트가 중단되었습니다. 실제로 모든 개인 방법을 테스트 한 경우 1 아닌 10 번의 테스트가 중단 될 수 있습니다. , 개인 기능 테스트 (FRIEND_TEST공개 인터페이스를 통해 테스트 할 수있는 공개 또는 리플렉션 사용) 테스트 복제가 발생할 수 있습니다 . 테스트 스위트가 속도를 늦추는 것보다 아무것도 아프지 않기 때문에 당신은 정말로 이것을 원하지 않습니다. 개발 시간과 유지 보수 비용을 줄여야합니다! 공용 인터페이스를 통해 테스트 된 개인 메소드를 테스트하는 경우 테스트 스위트는 그 반대의 경우를 잘 수행 할 수 있으며 유지 보수 비용을 적극적으로 증가시키고 개발 시간을 늘릴 수 있습니다. 개인 기능을 공개하거나 유사 FRIEND_TEST및 / 또는 리플렉션 을 사용하는 경우 일반적으로 장기적으로 후회하게됩니다.

Tokenizer클래스 의 다음 가능한 구현을 고려하십시오 .

여기에 이미지 설명을 입력하십시오

하자 말 SplitUpByDelimiter()배열의 각 요소는 토큰이되도록 배열을 반환 할 책임이있다. 또한 GetNextToken()이 벡터에 대한 반복 자라고 가정 해 봅시다 . 따라서 공개 테스트는 다음과 같습니다.

TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);

    ASSERT tokenizer.GetNextToken() IS "1";
    ASSERT tokenizer.GetNextToken() IS "2";
    ASSERT tokenizer.GetNextToken() IS "test";
    ASSERT tokenizer.GetNextToken() IS "bar";
    ASSERT tokenizer.HasMoreTokens() IS false;
}

Michael Feather가 모색 도구 라고 부르는 것처럼 가정 해 봅시다 . 다른 사람의 개인 부품을 만질 수있는 도구입니다. 예를 들어 FRIEND_TESTgoogletest 또는 언어가 지원하는 경우 반영입니다.

TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
    input_string = "1 2 test bar"
    tokenizer = Tokenizer(input_string);
    result_array = tokenizer.SplitUpByDelimiter(" ");

    ASSERT result.size() IS 4;
    ASSERT result[0] IS "1";
    ASSERT result[1] IS "2";
    ASSERT result[2] IS "test";
    ASSERT result[3] IS "bar";
}

이제 요구 사항이 변경되고 토큰 화가 훨씬 더 복잡해 졌다고 가정 해 봅시다. 간단한 문자열 구분 기호로 충분하지 않다고 결정 Delimiter하고 작업을 처리하기 위해 클래스가 필요합니다 . 당연히 하나의 테스트가 중단 될 것으로 예상하지만 개인 기능을 테스트하면 통증이 커집니다.

개인 테스트 방법은 언제 적절한가요?

소프트웨어에는 "하나의 크기가 모두 맞는"것은 없습니다. 때때로 "규칙을 어기는 것"(괜찮은)이 좋습니다. 가능한 경우 개인 기능을 테스트하지 않는 것이 좋습니다. 괜찮다고 생각되는 두 가지 주요 상황이 있습니다.

  1. 나는 레거시 시스템 (광활한 Michael Feathers 팬이기 때문에)과 광범위하게 작업했으며 때로는 개인 기능을 테스트하는 것이 가장 안전하다고 말할 수 있습니다. "특성 테스트"를 기준으로 가져 오는 데 특히 유용 할 수 있습니다.

  2. 당신은 서두르고 있으며 지금 여기에서 가능한 가장 빠른 일을해야합니다. 장기적으로는 개인 메소드를 테스트하고 싶지 않습니다. 그러나 설계 문제를 해결하기 위해 리팩토링하는 데 보통 시간이 걸린다고 말하겠습니다. 때로는 일주일 안에 배송해야합니다. 괜찮습니다. 작업을 완료하는 가장 빠르고 안정적인 방법이라고 생각하는 경우 모색 도구를 사용하여 빠르고 더러운 작업을 수행하고 개인 방법을 테스트하십시오. 그러나 당신이 한 일이 장기적으로 차선책이라는 것을 이해하고 다시 돌아 오는 것을 고려하십시오 (또는 잊어 버렸지 만 나중에 볼 경우 수정하십시오).

다른 상황에서는 괜찮을 것입니다. 괜찮다고 생각하고 정당성이 충분하다면 그렇게하십시오. 아무도 당신을 막고 있지 않습니다. 잠재적 인 비용에주의하십시오.

TDD 실례

제쳐두고, 나는 개인 메소드를 테스트하기위한 변명으로 TDD를 사용하는 사람들을 정말로 좋아하지 않습니다. 나는 TDD를 연습하지만 TDD가 당신에게 이것을 강요하지는 않는다고 생각합니다. 먼저 공용 인터페이스 용 테스트를 작성한 다음 해당 인터페이스를 만족시키는 코드를 작성할 수 있습니다. 때로는 공용 인터페이스에 대한 테스트를 작성하고 하나 또는 두 개의 더 작은 개인용 메소드를 작성하여이를 만족시킬 수 있습니다 (그러나 개인용 메소드를 직접 테스트하지는 않지만 작동하거나 공개 테스트에 실패 함을 알고 있음) ). 해당 개인 메소드의 엣지 케이스를 테스트 해야하는 경우 공용 인터페이스를 통해 테스트를 수행하는 전체 테스트를 작성합니다.엣지 케이스에 부딪 치는 방법을 알 수 없다면 이는 각각 고유 한 공개 방법을 사용하여 작은 구성 요소로 리팩토링해야한다는 강력한 신호입니다. 개인 함수가 너무 많은 일을하고 클래스의 범위를 벗어났다는 신호 입니다.

또한 때때로 나는 씹기에는 너무 물린 테스트를 작성한다는 것을 알게 되었기 때문에 "나는 API를 더 사용할 때 나중에 그 테스트로 돌아갈 것"이라고 생각합니다. 의견을 말하고 내 마음 뒤에 보관하십시오.) 이것은 내가 만난 많은 개발자들이 TDD를 희생양으로 사용하여 개인 기능에 대한 테스트를 작성하는 곳입니다. "아, 다른 테스트가 필요하지만이 테스트를 작성하려면 이러한 개인 메소드가 필요합니다. 따라서 테스트를 작성하지 않고는 프로덕션 코드를 작성할 수 없으므로 테스트를 작성해야합니다. 개인적인 방법으로 그러나 그들이 실제로해야 할 일은 현재 클래스에 많은 개인 메소드를 추가 / 테스트하는 대신 더 작고 재사용 가능한 구성 요소로 리팩토링하는 것입니다.

노트 :

얼마 전 GoogleTest를 사용하여 개인 메소드를 테스트 하는 것과 비슷한 질문에 대답했습니다 . 나는 대부분 더 많은 언어에 대해 그 대답을 수정했습니다.

추신 : Michael Feathers의 빙산 강의 및 모색 도구에 대한 관련 강의는 다음과 같습니다. https://www.youtube.com/watch?v=4cVZvoFGJTU


객체의 공용 인터페이스를 테스트하는 것이 가장 좋습니다. 외부 세계의 관점에서, 공개 인터페이스의 행동 만 중요하며 이것이 바로 유닛 테스트를 향한 것입니다.

객체에 대해 견고한 단위 테스트를 작성한 후에는 인터페이스 뒤의 구현이 변경 되었기 때문에 돌아가서 해당 테스트를 변경하지 않아도됩니다. 이 상황에서는 단위 테스트의 일관성을 망쳤습니다.


공개 메소드를 호출하여 개인 메소드를 테스트하지 않으면 어떻게됩니까? 보호되지 않거나 친구가 아닌 비공개로 이야기하고 있습니다.


개인 메소드가 잘 정의 된 경우 (즉, 테스트 가능하고 시간이 지남에 따라 변경되지 않는 기능이있는 경우) yes입니다. 나는 그것이 의미가있는 곳에서 테스트 가능한 모든 것을 테스트합니다.

예를 들어, 암호화 라이브러리는 한 번에 8 바이트 만 암호화하는 개인 방법으로 블록 암호화를 수행한다는 사실을 숨길 수 있습니다. 단위 테스트를 작성하려고합니다. 숨겨져 있어도 변경하려는 것이 아니며 향후 성능 향상으로 인해 중단되는 경우 그것이 개인 기능이 아니라 개인 기능이라는 것을 알고 싶습니다. 공공 기능 중 하나가 고장났습니다.

나중에 디버깅 속도가 빨라집니다.

-아담


테스트 기반 (TDD)을 개발하는 경우 개인 방법을 테스트합니다.


저는이 분야의 전문가는 아니지만 단위 테스트는 구현이 아닌 동작을 테스트해야합니다. 전용 메소드는 구현의 일부이므로 IMHO를 테스트하지 않아야합니다.


우리는 추론으로 개인 메서드를 테스트합니다. 즉, 최소 95 %의 전체 클래스 테스트 범위를 찾지 만 테스트는 공용 또는 내부 메서드 만 호출합니다. 서비스를 받으려면 발생할 수있는 여러 시나리오에 따라 공개 / 내부에게 여러 번 전화를 걸어야합니다. 이를 통해 테스트중인 코드의 목적에 따라 테스트를 더욱 집중적으로 만듭니다.

연결 한 게시물에 대한 Trumpi의 답변이 가장 좋습니다.


내가 생각하는 단위 테스트는 공개 메소드를 테스트하기위한 것입니다. 공개 메소드는 개인 메소드를 사용하므로 간접적으로 테스트되고 있습니다.


나는 특히 TDD에 손을 대면서이 문제를 잠시 동안 조롱했습니다.

TDD의 경우이 문제를 철저히 해결한다고 생각되는 두 개의 게시물을 보았습니다.

  1. 개인 메소드, TDD 및 테스트 주도 리팩토링 테스트
  2. 테스트 주도 개발이 테스트가 아님

요약해서 말하자면:

  • 테스트 주도 개발 (디자인) 기술을 사용할 때 전용 메소드는 이미 작동하고 테스트 된 코드의 리팩토링 프로세스 중에 만 발생해야합니다.

  • 프로세스의 특성상 철저하게 테스트 된 기능에서 추출 된 간단한 구현 기능은 자체 테스트됩니다 (예 : 간접 테스트 범위).

나에게 코딩의 시작 부분에서 대부분의 메소드는 디자인을 캡슐화 / 설명하기 때문에 더 높은 수준의 함수가 될 것입니다.

따라서 이러한 방법은 공개적이며 테스트하기에 충분할 것입니다.

모든 것이 잘 작동하고 나면 가독성청결을 위해 리팩토링을 고려하고있는 개인 방법이 나중에 나옵니다 .


위에서 인용 한 것처럼 "개인 메소드를 테스트하지 않으면 메소드가 중단되지 않는지 어떻게 알 수 있습니까?"

이것은 중요한 문제입니다. 단위 테스트의 가장 큰 요점 중 하나는 어디서, 언제, 어떻게 문제가 발생했는지 파악하는 것입니다. 따라서 상당한 양의 개발 및 QA 노력이 줄어 듭니다. 시험 된 모든 것이 일반인이라면, 수업 내부에 정직한 적용 범위와 묘사가 없습니다.

이 작업을 수행하는 가장 좋은 방법 중 하나는 프로젝트에 테스트 참조를 추가하고 테스트를 개인 메서드와 병렬로 클래스에 배치하는 것입니다. 테스트가 최종 프로젝트에 빌드되지 않도록 적절한 빌드 로직을 넣으십시오.

그런 다음 이러한 방법을 테스트하면 얻을 수있는 모든 이점이 있으며 몇 초 또는 몇 시간 또는 몇 시간으로 문제를 찾을 수 있습니다.

요약하자면, 개인 메소드를 단위 테스트하십시오.


해서는 안됩니다 . 개인 메서드에 테스트가 필요한 복잡성이 충분한 경우 다른 클래스에 배치해야합니다. 높은 응집력을 유지하십시오 . 수업은 단 하나의 목적을 가져야합니다. 클래스 공용 인터페이스이면 충분합니다.


당신이 당신의 개인 분석법을 테스트하지 않는 경우, 그 방법이 깨지지 않는지 어떻게 알 수 있습니까?


언어에 따라 다릅니다. 과거에 C ++에서는 테스트 클래스를 친구 클래스로 선언했습니다. 불행히도, 테스트 클래스에 대해 알고 싶다면 프로덕션 코드가 필요합니다.


개인 메소드가 구현 세부 사항으로 간주되어 테스트 할 필요가 없다는 관점을 이해합니다. 그리고 우리가 물체 밖에서 만 개발해야한다면이 규칙을 고수 할 것입니다. 그러나 우리는 객체 외부에서만 개발하고 공개 메소드 만 호출하는 제한된 개발자입니까? 아니면 실제로 그 객체를 개발하고 있습니까? 우리는 외부 객체를 프로그램해야 할 의무가 없기 때문에 이러한 개인 메소드를 개발중인 새로운 공개 메소드로 호출해야 할 것입니다. 개인적인 방법이 모든 확률에 저항한다는 것을 아는 것이 좋지 않습니까?

일부 사람들이 우리가 그 대상에 다른 공개 방법을 개발하고 있다면 이것이 테스트되어야하고 그것이 바로 그것이라는 것입니다 (사적인 방법은 테스트없이 생활 할 수 있음). 그러나 이것은 객체의 모든 공개 메소드에도 적용됩니다. 웹 앱을 개발할 때 객체의 모든 공개 메소드가 컨트롤러 메소드에서 호출되므로 컨트롤러의 구현 세부 사항으로 간주 될 수 있습니다.

그렇다면 왜 단위 테스트 객체입니까? 실제로 어렵 기 때문에 기본 코드의 모든 분기를 트리거하는 적절한 입력을 사용하여 컨트롤러의 메소드를 테스트하고 있는지 확인하는 것은 불가능합니다. 다시 말해서, 스택에 높을수록 모든 동작을 테스트하기가 더 어려워집니다. 그리고 개인 메소드도 마찬가지입니다.

개인적 방법과 공공 방법의 경계는 시험에있어 심리적 기준입니다. 나에게 더 중요한 기준은 다음과 같습니다.

  • 다른 장소에서 메소드가 두 번 이상 호출됩니까?
  • 테스트가 필요할 정도로 정교합니까?

개인 메소드가 거대하거나 복잡하거나 자체 테스트가 필요할 정도로 중요하다는 것을 알게되면 다른 클래스에 넣고 공개합니다 (Method Object). 그런 다음 이전에는 개인적이지만 현재는 공개 된 방법을 쉽게 테스트 할 수 있습니다.


공개 대 개인은 API가 테스트에서 호출하는 것과 유용한 방법이 아니며 방법 대 클래스도 아닙니다. 대부분의 테스트 가능한 단위는 한 상황에서 볼 수 있지만 다른 상황에서는 숨겨져 있습니다.

중요한 것은 적용 범위와 비용입니다. 프로젝트의 적용 범위 목표 (라인, 분기, 경로, 블록, 방법, 클래스, 동등성 클래스, 유스 케이스 ... 팀이 결정한 모든 것)를 달성하면서 비용을 최소화해야합니다.

따라서 도구를 사용하여 적용 범위를 확보하고 테스트를 설계하여 비용을 최소화 할 수 있습니다 (단기 및 장기 ).

테스트를 필요 이상으로 비싸게 만들지 마십시오. 공개 진입 점 만 테스트하는 것이 가장 저렴한 경우에는 그렇게하십시오. 개인 메소드를 테스트하는 것이 가장 저렴한 경우 그렇게하십시오.

경험이 많을수록 장기 테스트 유지 관리 비용을 피하기 위해 리팩토링이 필요한 시점을 더 잘 예측할 수 있습니다.


이 방법이 충분히 / 복잡한 경우에는 보통 "보호"하고 테스트합니다. 일부 방법은 비공개로 유지되며 공개 / 보호 방법에 대한 단위 테스트의 일부로 암시 적으로 테스트됩니다.


나는 많은 사람들이 같은 생각을하고있는 것을 본다 : 공공 수준에서의 시험. 품질 관리팀이하는 일이 아닙니까? 입력 및 예상 출력을 테스트합니다. 개발자로서 공개 메소드 만 테스트하는 경우 QA의 업무를 다시 수행하고 "단위 테스트"를 통해 가치를 추가하지 않습니다.


"개인 메소드를 테스트해야합니까?" "때때로"입니다. 일반적으로 클래스의 인터페이스에 대해 테스트해야합니다.

  • 그 이유 중 하나는 기능에 대해 이중 적용 범위가 필요하지 않기 때문입니다.
  • 또 다른 이유는 개인 메소드를 변경하면 객체의 인터페이스가 전혀 변경되지 않은 경우에도 각 메소드를 업데이트해야하기 때문입니다.

예를 들면 다음과 같습니다.

class Thing
  def some_string
    one + two
  end

  private 

  def one
    'aaaa'
  end

  def two
    'bbbb'
  end

end


class RefactoredThing
def some_string
    one + one_a + two + two_b
  end

  private 

  def one
    'aa'
  end

  def one_a
    'aa'
  end

  def two
    'bb'
  end

  def two_b
    'bb'
  end
end

에서 RefactoredThing당신 이제 리팩토링에 대한 업데이트했다이있는 5 개 테스트를 가지고 있지만, 개체의 기능은 정말 변경되지 않았습니다. 따라서 사물이 그보다 복잡하고 다음과 같이 출력 순서를 정의하는 방법이 있다고 가정 해 봅시다.

def some_string_positioner
  if some case
  elsif other case
  elsif other case
  elsif other case
  else one more case
  end
end

이것은 외부 사용자가 실행해서는 안되지만 캡슐화 클래스는 많은 논리를 반복해서 실행하기 위해 무거울 수 있습니다. 이 경우에는 이것을 별도의 클래스로 추출하고 해당 클래스에 인터페이스를 제공하고 테스트하십시오.

마지막으로 주 객체가 매우 무겁고 방법이 매우 작으며 실제로 출력이 올바른지 확인해야한다고 가정 해 봅시다. "이 개인 방법을 테스트해야합니다!"라고 생각하고 있습니다. 무거운 작업을 초기화 매개 변수로 전달하여 객체를 더 가볍게 만들 수 있습니까? 그런 다음 더 가벼운 것을 전달하고 그것에 대해 테스트 할 수 있습니다.


아니요 프라이빗 메소드를 테스트하지 않아야 합니까? 또한 Mockito와 같은 인기있는 조롱 프레임 워크는 개인 메소드 테스트를 지원하지 않습니다.


하나의 요점은

논리의 정확성을 테스트하기 위해 테스트하고 개인 메소드가 논리를 전달하는 경우 테스트해야합니다. 그렇지 않습니까? 왜 우리는 그것을 건너 뛸까요?

방법의 가시성을 기반으로 테스트를 작성하는 것은 전혀 관련이 없습니다.

거꾸로

반면, 원래 클래스 외부에서 전용 메소드를 호출하는 것이 주요 문제입니다. 또한 일부 조롱 도구에서 개인 메서드를 조롱하는 데 제한이 있습니다. (예 : Mockito )

이를 지원하는 Power Mock 과 같은 도구 가 있지만 위험한 작업입니다. 그 이유는이를 달성하기 위해 JVM을 해킹해야하기 때문입니다.

해결할 수있는 한 가지 해결 방법은 (개인 메서드에 대한 테스트 사례를 작성하려는 경우)

보호 된 개인 메소드를 선언하십시오 . 그러나 여러 상황에서는 편리하지 않을 수 있습니다.


단위 테스트의 개념을 이해하지 못했지만 이제 목표가 무엇인지 알고 있습니다.

단위 테스트는 완전한 테스트가 아닙니다 . 따라서 품질 보증 및 수동 테스트를 대체하지 않습니다. 이 측면에서 TDD의 개념은 개인 메서드뿐만 아니라 리소스를 사용하는 메서드 (특히 제어 할 수없는 리소스)를 포함한 모든 것을 테스트 할 수 없기 때문에 잘못되었습니다. TDD는 모든 품질을 달성 할 수 없었습니다.

단위 테스트는 피벗 테스트입니다. 임의의 피벗을 표시 하면 피벗 결과가 동일하게 유지됩니다.


퍼블릭 또는 프라이빗 메소드 또는 함수에 관한 것이 아니라 구현 세부 사항에 관한 것입니다. 개인 함수는 구현 세부 사항의 한 측면 일뿐입니다.

결국 단위 테스트는 화이트 박스 테스트 방식입니다. 예를 들어, 지금까지 테스트에서 무시 된 코드의 일부를 식별하기 위해 적용 범위 분석을 사용하는 사람은 구현 세부 정보로 이동합니다.

A) 예, 구현 세부 정보를 테스트해야합니다.

성능상의 이유로 최대 10 개의 요소가있는 경우 개인용 BubbleSort 구현을 사용하고 10 개 이상의 요소가있는 경우 다른 정렬 방식 (예 : 힙 정렬)의 개인 구현을 사용하는 정렬 함수를 생각해보십시오. 공개 API는 정렬 함수의 API입니다. 그러나 테스트 스위트는 실제로 두 가지 정렬 알고리즘이 사용된다는 지식을 더 잘 활용합니다.

이 예제에서는 반드시 공개 API에서 테스트를 수행 할 수 있습니다. 그러나 힙 정렬 알고리즘이 충분히 테스트되도록 10 개 이상의 요소로 정렬 함수를 실행하는 여러 테스트 사례가 필요합니다. 이러한 테스트 사례 만 존재한다는 것은 테스트 스위트가 기능의 구현 세부 사항에 연결되어 있음을 나타냅니다.

정렬 함수의 구현 세부 사항이 변경되면 두 정렬 알고리즘 사이의 한계가 이동되거나 힙 정렬이 mergesort 등으로 대체되는 방식으로 인해 기존 테스트가 계속 작동합니다. 그럼에도 불구하고 그들의 가치는 의문의 여지가 있으며 변경된 정렬 기능을 더 잘 테스트하기 위해 재 작업해야 할 것입니다. 다시 말해, 테스트가 공개 API에서 수행되었다는 사실에도 불구하고 유지 관리 노력이있을 것입니다.

B) 구현 세부 사항을 테스트하는 방법

많은 사람들이 개인 기능이나 구현 세부 사항을 테스트해서는 안된다고 주장하는 한 가지 이유는 구현 세부 사항이 변경 될 가능성이 높기 때문입니다. 이러한 변경 가능성은 적어도 인터페이스 뒤에 구현 세부 사항을 숨기는 이유 중 하나입니다.

이제 인터페이스 뒤의 구현에는 내부 인터페이스의 개별 테스트가 옵션 일 수있는 더 큰 개인 부품이 포함되어 있다고 가정하십시오. 어떤 사람들은이 부분들이 비공개 일 때 테스트해서는 안되며, 공개 된 것으로 바꿔야한다고 주장합니다. 일단 공개되면 해당 코드를 단위 테스트해도 괜찮습니다.

이것은 흥미 롭습니다. 인터페이스는 내부에 있지만 구현 세부 사항이므로 변경 될 수 있습니다. 동일한 인터페이스를 사용하여 공개하면 약간의 마술 변환, 즉 변경 가능성이 적은 인터페이스로 전환됩니다. 분명히이 주장에는 약간의 결함이 있습니다.

그럼에도 불구하고, 이것 뒤에 배후의 진실이있다 : 구현 세부 사항을 테스트 할 때, 특히 내부 인터페이스를 사용하여, 안정적으로 유지 될 수있는 인터페이스를 사용하도록 노력해야한다. 그러나 일부 인터페이스가 안정적인지 여부는 공용인지 개인인지에 따라 결정될 수 없습니다. 전 세계에서 일한 프로젝트에서 공용 인터페이스도 종종 충분히 변경되었으며 많은 개인용 인터페이스는 오랫동안 변하지 않았습니다.

여전히 "정문"을 사용하는 것이 좋습니다 ( http://xunitpatterns.com/Principles%20of%20Test%20Automation.html 참조 ). 그러나 "정문 전용"이 아니라 "정문 우선"이라고합니다.

C) 요약

구현 세부 사항도 테스트하십시오. 안정적인 인터페이스 (공용 또는 개인)에 대한 테스트를 선호합니다. 구현 세부 사항이 변경되면 공개 API에 대한 테스트도 수정해야합니다. 사적인 것을 공개적으로 바꾸는 것이 마술의 안정성을 바꾸지는 않습니다.


그렇습니다. 가능하면 개인 메소드를 테스트해야합니다. 왜? 테스트 케이스 의 불필요한 상태 공간 폭발 을 피하기 위해 궁극적으로 동일한 입력에서 동일한 개인 기능을 반복적으로 암시 적으로 테스트합니다. 예를 들어 왜 그런지 설명해 봅시다.

다음과 같이 약간 고안된 예를 고려하십시오. 3 개의 정수를 취하고 3 개의 정수가 모두 소수 인 경우에만 true를 리턴하는 함수를 공개적으로 노출한다고 가정하십시오. 다음과 같이 구현할 수 있습니다.

public bool allPrime(int a, int b, int c)
{
  return andAll(isPrime(a), isPrime(b), isPrime(c))
}

private bool andAll(bool... boolArray)
{
  foreach (bool b in boolArray)
  {
    if(b == false) return false;
  }
  return true;
}

private bool isPrime(int x){
  //Implementation to go here. Sorry if you were expecting a prime sieve.
}

이제 공개 기능 만 테스트해야하는 엄격한 접근 방식을 취해야한다면 테스트 만 할 수 allPrime있고 그렇지 않을 isPrime수도 andAll있습니다.

테스터, 우리는 각 인수에 다섯 가능성에 관심이있을 수 : < 0, = 0, = 1, prime > 1, not prime > 1. 그러나 철저하게, 우리는 또한 모든 논증의 조합이 어떻게 함께 작용 하는지를보아야합니다. 따라서 5*5*5직감에 따라 = 125 테스트 사례입니다.이 기능을 철저히 테스트해야합니다.

반면에 개인 기능을 테스트 할 수 있다면 더 적은 테스트 사례로 많은 근거를 다룰 수 있습니다. isPrime이전 직관과 동일한 수준으로 테스트 하려면 5 개의 테스트 사례 만 있으면됩니다 . 그리고 Daniel Jackson이 제안한 작은 범위 가설 에 따르면 , 우리는 andAll함수를 3 또는 4와 같은 작은 길이까지만 테스트하면 됩니다. 최대 16 개의 테스트가 더 있습니다. 총 21 개의 테스트가 있습니다. 물론, 우리는 아마도에 대해 몇 가지 테스트 를 수행하고 싶을 수도 allPrime있지만, 우리가 염려했던 입력 시나리오의 125 가지 조합을 모두 철저히 다루어야 할 의무는 없습니다. 몇 가지 행복한 길.

확실히 좋은 예이지만 분명한 시연이 필요했습니다. 그리고 패턴은 실제 소프트웨어로 확장됩니다. 개인 기능은 일반적으로 가장 낮은 수준의 빌딩 블록이므로 더 높은 수준의 논리를 생성하기 위해 종종 결합됩니다. 더 높은 수준에서, 우리는 다양한 조합으로 인해 더 낮은 수준의 물건을 더 많이 반복합니다.


또한 메소드를 패키지 전용 (기본값)으로 설정할 수 있으며 개인용이 아닌 한 단위 테스트를 수행 할 수 있어야합니다.

참고 URL : https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones


반응형