개인 메소드를 테스트하는 방법에 대한 이 게시물 을 읽었습니다 . 나는 항상 객체 외부에서 호출되는 공용 메소드 만 테스트하는 것이 더 빠르다고 생각하기 때문에 테스트하지 않습니다. 개인 메소드를 테스트합니까? 항상 테스트해야합니까?
개인 메소드를 테스트하는 방법에 대한 이 게시물 을 읽었습니다 . 나는 항상 객체 외부에서 호출되는 공용 메소드 만 테스트하는 것이 더 빠르다고 생각하기 때문에 테스트하지 않습니다. 개인 메소드를 테스트합니까? 항상 테스트해야합니까?
답변:
개인 메소드를 단위 테스트하지 않습니다. 개인용 메소드는 클래스 사용자에게 숨겨져 야하는 구현 세부 사항입니다. 전용 메서드를 테스트하면 캡슐화가 중단됩니다.
개인 메소드가 거대하거나 복잡하거나 자체 테스트가 필요할 정도로 중요하다는 것을 알게되면 다른 클래스에 넣고 공개합니다 ( Method Object ). 그런 다음 이제는 자체 클래스에 존재하는 이전의 개인용이지만 현재 공용 인 방법을 쉽게 테스트 할 수 있습니다.
테스트의 목적은 무엇입니까?
지금까지 답변의 대부분은 개인 메소드가 퍼블릭 인터페이스가 잘 테스트되고 작동하는 한 중요하지 않은 구현 세부 사항이라고 말합니다. 테스트의 유일한 목적이 공용 인터페이스가 작동하도록 보장하는 것이라면 이것이 맞습니다 .
개인적으로, 코드 테스트의 주된 용도는 향후 코드 변경으로 인해 문제가 발생하지 않도록하고 문제가있는 경우 디버깅 작업을 지원하는 것입니다. 공개 인터페이스 (더 이상 그렇지 않다면!)가 철저히 목적을 달성하는 것처럼 개인 메소드를 테스트하는 것이 그 목적을 달성한다는 것을 알았습니다.
다음을 고려하십시오. 개인 메소드 B를 호출하는 공용 메소드 A가 있습니다. A와 B는 모두 메소드 C를 사용합니다. C는 변경 될 수 있으며 (아마도 공급 업체에 의해) 벤더가 테스트 실패를 시작합니다. 문제가 A의 C 사용, B의 C 사용 또는 두 가지 모두에 있는지 알 수 있도록 비공개 테스트를 수행해도 B에 대한 테스트를하는 것이 유용하지 않습니까?
공용 메서드의 테스트 범위가 불완전한 경우 개인 메서드를 테스트해도 가치가 높아집니다. 이것이 일반적으로 피하고자하는 상황이지만 효율성 단위 테스트는 버그를 찾는 테스트와 해당 테스트의 개발 및 유지 관리 비용에 따라 달라집니다. 경우에 따라 100 % 테스트 범위의 이점이 해당 테스트 비용을 보증하기에 충분하지 않은 것으로 판단되어 공용 인터페이스의 테스트 범위에 차이가 생길 수 있습니다. 그러한 경우, 개인 메소드에 대한 목표가 명확한 테스트는 코드 기반에 매우 효과적 일 수 있습니다.
testDoSomething()
또는에 있을 수 있습니다 testDoSomethingPrivate()
. 따라서 테스트 가치가 떨어집니다. . private stackoverflow.com/questions/34571/… 를 테스트해야하는 더 많은 이유가 있습니다 .
나는 그들의 책 Pragmatic Unit Testing 에서 Dave Thomas와 Andy Hunt의 조언을 따르는 경향이 있습니다 .
일반적으로 테스트 목적으로 캡슐화를 중단하고 싶지 않습니다 (또는 엄마가 "개인을 노출시키지 마십시오!"라고 말한 것처럼). 대부분의 경우 공용 메소드를 사용하여 클래스를 테스트 할 수 있어야합니다. 개인 또는 보호 된 액세스 뒤에 숨겨져있는 중요한 기능이있는 경우, 다른 클래스가 나 가려고 애 쓰고 있다는 경고 신호일 수 있습니다.
그러나 때로는 개인적인 방법을 테스트하는 것을 막을 수 없습니다 . 완전히 강력한 프로그램을 작성 하고 있다는 확신을 줄 수 있기 때문 입니다.
프로젝트에서 최신 QA 권장 사항을 점점 더 많이 따르면서 개인 기능을 테스트해야한다고 생각합니다.
함수 당 순환 복잡성 이 10 개 이하 입니다.
이제이 정책을 시행 할 때의 부작용은 매우 큰 공공 기능 중 다수가 더 집중되고 이름이 잘 지정된 개인 기능 으로 나뉘어져 있다는 것입니다 .
공공 기능은 여전히 (물론) 있지만 본질적으로 모든 개인 '하위 기능'을 호출하도록 축소되었습니다.
콜 스택을 읽기가 훨씬 쉽기 때문에 실제로 멋지다. '어떻게 도착했는지')
그러나 이제는 이러한 개인 기능을 직접 단위 테스트하는 것이 더 쉬워 보이고 대규모 공용 기능의 테스트는 시나리오를 해결해야하는 일종의 '통합'테스트로 남겨 둡니다.
그냥 내 2 센트.
예, 개인 함수는 공개 메소드로 테스트되었지만 TDD (Test Driven Design)에서는 응용 프로그램의 가장 작은 부분을 테스트하는 것이 좋기 때문에 개인 함수를 테스트합니다. 그러나 테스트 단위 클래스에있을 때는 개인 기능에 액세스 할 수 없습니다. 다음은 개인 메소드를 테스트하기 위해 수행하는 작업입니다.
왜 우리는 개인적인 방법을 가지고 있습니까?
개인 함수는 주로 공개 메소드에서 읽을 수있는 코드를 작성하려고하기 때문에 클래스에 존재합니다. 우리는이 클래스의 사용자가 이러한 메소드를 직접 호출하는 것이 아니라 공용 메소드를 통해 호출하기를 원하지 않습니다. 또한 클래스를 확장 할 때 (보호 된 경우) 행동을 변경하지 않기를 원하므로 비공개입니다.
코딩 할 때는 TDD (Test-driven-design)를 사용합니다. 이것은 때때로 우리가 개인적인 기능을 테스트하고 싶어한다는 것을 의미합니다. 비공개 함수는 phpUnit에서 테스트 할 수 없습니다. Test 클래스에서는 비공개 함수이기 때문에 액세스 할 수 없기 때문입니다.
우리는 여기에 3 가지 해결책이 있다고 생각합니다.
1. 공개 방법을 통해 개인을 테스트 할 수 있습니다
장점
단점
2. 개인이 매우 중요한 경우, 별도의 새 클래스를 만드는 것이 코드 스멜 일 수 있습니다.
장점
단점
3. 액세스 수정자를 (최종) 보호로 변경하십시오.
장점
단점
예
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 사람들의 주요 요점).
구체적인 예를 통해 이들 각각을 설명하겠습니다. 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_TEST
googletest 또는 언어가 지원하는 경우 반영입니다.
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
하고 작업을 처리 하는 클래스가 필요합니다 . 당연히 하나의 테스트가 중단 될 것으로 예상하지만 개인 기능을 테스트하면 통증이 커집니다.
소프트웨어에는 "하나의 크기가 모두 맞는"것은 없습니다. 때때로 "규칙을 어기는 것"(괜찮은)이 좋습니다. 가능한 경우 개인 기능을 테스트하지 않는 것이 좋습니다. 괜찮다고 생각되는 두 가지 주요 상황이 있습니다.
나는 레거시 시스템 (광활한 Michael Feathers 팬이기 때문에)과 광범위하게 일해 왔으며 때로는 개인 기능을 테스트하는 것이 가장 안전하다고 말할 수 있습니다. "특성 테스트"를 기준으로 가져 오는 데 특히 도움이 될 수 있습니다.
당신은 서두르고 있으며 지금 여기에서 가능한 가장 빠른 일을해야합니다. 장기적으로는 개인 메소드를 테스트하고 싶지 않습니다. 그러나 설계 문제를 해결하기 위해 리팩토링하는 데 보통 시간이 걸린다고 말하겠습니다. 때로는 일주일 안에 배송해야합니다. 괜찮습니다. 작업을 완료하는 가장 빠르고 안정적인 방법이라고 생각하는 경우 모색 도구를 사용하여 빠르고 더러운 작업을 수행하고 개인 방법을 테스트하십시오. 그러나 당신이 한 일이 장기적으로 차선책이라는 것을 이해하고 다시 돌아 오는 것을 고려하십시오 (또는 잊어 버렸지 만 나중에 볼 경우 수정하십시오).
다른 상황에서는 괜찮을 것입니다. 괜찮다고 생각하고 정당성이 충분하다면 그렇게하십시오. 아무도 당신을 막고 있지 않습니다. 잠재적 인 비용에주의하십시오.
제쳐두고, 나는 개인 메소드 테스트에 대한 변명으로 TDD를 사용하는 사람들을 정말로 좋아하지 않습니다. 나는 TDD를 연습하지만 TDD가 당신에게 이것을 강요한다고 생각하지 않습니다. 먼저 공용 인터페이스 용 테스트를 작성한 다음 해당 인터페이스를 만족시키는 코드를 작성할 수 있습니다. 때로는 공용 인터페이스에 대한 테스트를 작성하고 하나 또는 두 개의 더 작은 개인용 메소드를 작성하여이를 만족시킬 것입니다 (그러나 개인용 메소드를 직접 테스트하지는 않지만 작동하거나 공개 테스트가 실패한다는 것을 알고 있습니다 ). 해당 개인 메소드의 엣지 케이스를 테스트 해야하는 경우 공용 인터페이스를 통해 테스트를 수행하는 전체 테스트를 작성합니다.엣지 케이스를 치는 방법을 알 수 없다면 이는 각각 고유 한 공개 방법으로 작은 구성 요소를 리팩토링해야한다는 강력한 신호입니다. 개인 함수가 너무 많은 일을하고 클래스의 범위를 벗어났다는 신호 입니다.
또한 때때로 나는 씹기에는 너무 물린 테스트를 작성한다는 것을 알게 되었기 때문에 "나는 API를 더 사용할 때 나중에 다시 테스트로 돌아갈 것"이라고 생각합니다. 의견을 말하고 내 마음 뒤에 보관하십시오.) 이것은 내가 만난 많은 개발자들이 TDD를 희생양으로 사용하여 개인 기능에 대한 테스트를 작성하는 곳입니다. "아, 다른 테스트가 필요하지만이 테스트를 작성하려면 이러한 개인 메소드가 필요합니다. 따라서 테스트를 작성하지 않고는 프로덕션 코드를 작성할 수 없으므로 테스트를 작성해야합니다. 개인적인 방법으로 그러나 그들이 실제로해야 할 일은 현재 클래스에 많은 개인 메소드를 추가 / 테스트하는 대신 더 작고 재사용 가능한 구성 요소로 리팩토링하는 것입니다.
노트 :
얼마 전 GoogleTest를 사용하여 개인 메소드를 테스트 하는 것과 비슷한 질문에 대답했습니다 . 나는 대부분 더 많은 언어에 대해 그 대답을 수정했습니다.
추신 : Michael Feathers의 빙산 강의 및 모색 도구에 대한 관련 강의는 다음과 같습니다. https://www.youtube.com/watch?v=4cVZvoFGJTU
_
으로 지정하면 "이봐, 이건 'private"입니다. 사용할 수는 있지만 완전 공개는 재사용 할 수 있도록 설계되지 않았으므로 실제로 사용하는 경우에만 사용해야합니다. 뭘하는지 알아 " 어떤 언어로든 동일한 접근 방식을 사용할 수 있습니다. 해당 구성원을 공개로 설정하지만 주요 내용으로 표시하십시오 _
. 또는 이러한 기능은 실제로 개인용이어야하며 공용 인터페이스를 통해 테스트해야합니다 (자세한 내용은 답변 참조). 경우에 따라서는 일반적인 규칙이 없습니다
공개 메소드를 호출하여 개인 메소드를 테스트하지 않으면 어떻게됩니까? 보호되지 않거나 친구가 아닌 비공개로 이야기하고 있습니다.
개인 메소드가 잘 정의 된 경우 (즉, 테스트 가능하고 시간이 지남에 따라 변경되지 않는 기능이있는 경우) yes입니다. 나는 의미가있는 곳에서 테스트 할 수있는 모든 것을 테스트합니다.
예를 들어, 암호화 라이브러리는 한 번에 8 바이트 만 암호화하는 개인 방법으로 블록 암호화를 수행한다는 사실을 숨길 수 있습니다. 단위 테스트를 작성하려고합니다. 숨겨져 있어도 변경하려는 것이 아니며 향후 성능 향상으로 인해 중단되는 경우 그것이 개인 기능이 아니라 개인 기능이라는 것을 알고 싶습니다. 공공 기능 중 하나가 고장났습니다.
나중에 디버깅 속도가 빨라집니다.
-아담
테스트 기반 (TDD)을 개발하는 경우 개인 방법을 테스트합니다.
저는이 분야의 전문가는 아니지만 단위 테스트는 구현이 아닌 동작을 테스트해야합니다. 전용 메소드는 구현의 일부이므로 IMHO를 테스트하지 않아야합니다.
나는 특히 TDD에 손을 대면서이 문제를 잠시 동안 조롱했습니다.
TDD의 경우이 문제를 철저히 해결한다고 생각되는 두 개의 게시물을 보았습니다.
요약해서 말하자면:
테스트 주도 개발 (디자인) 기술을 사용할 때 전용 메소드는 이미 작동하고 테스트 된 코드의 리팩토링 프로세스 중에 만 발생해야합니다.
프로세스의 특성상 철저하게 테스트 된 기능에서 추출 된 간단한 구현 기능은 자체 테스트됩니다 (예 : 간접 테스트 범위).
나에게 코딩의 시작 부분에서 대부분의 메소드는 디자인을 캡슐화 / 설명하기 때문에 더 높은 수준의 함수가 될 것입니다.
따라서 이러한 방법은 공개적이며 테스트하기에 충분할 것입니다.
모든 것이 잘 작동하고 나면 가독성 과 청결을 위해 리팩토링을 고려 하고 있습니다.
위에서 인용 한 것처럼 "개인 메소드를 테스트하지 않으면 메소드가 중단되지 않는지 어떻게 알 수 있습니까?"
이것은 중요한 문제입니다. 단위 테스트의 가장 큰 요점 중 하나는 언제, 어떻게, 어떻게 문제가 발생했는지 아는 것입니다. 따라서 상당한 양의 개발 및 QA 노력이 줄어 듭니다. 시험 된 모든 것이 일반인이라면, 수업 내부에 정직한 적용 범위와 묘사가 없습니다.
이 작업을 수행하는 가장 좋은 방법 중 하나는 프로젝트에 테스트 참조를 추가하고 테스트를 개인 메소드와 병렬로 클래스에 배치하는 것입니다. 테스트가 최종 프로젝트에 빌드되지 않도록 적절한 빌드 로직을 넣으십시오.
그런 다음 이러한 방법을 테스트하면 얻을 수있는 모든 이점이 있으며 몇 초 또는 몇 시간 또는 몇 시간으로 문제를 찾을 수 있습니다.
요약하자면, 개인 메소드를 단위 테스트하십시오.
해서는 안됩니다 . 개인 메서드에 테스트가 필요한 복잡성이 충분한 경우 다른 클래스에 배치해야합니다. 높은 응집력을 유지 , 클래스는 하나의 목적을 가져야합니다. 클래스 공용 인터페이스이면 충분합니다.
당신이 당신의 개인 방법을 테스트하지 않으면 어떻게 깨지지 않는지 어떻게 알 수 있습니까?
개인 메소드가 구현 세부 사항으로 간주되어 테스트 할 필요가 없다는 관점을 이해합니다. 그리고 우리가 물체 밖에서 만 개발해야한다면이 규칙을 고수 할 것입니다. 그러나 우리는 객체 외부에서만 개발하고 공개 메소드 만 호출하는 제한된 개발자입니까? 아니면 실제로 그 객체를 개발하고 있습니까? 우리는 외부 객체를 프로그래밍 할 의무가 없기 때문에 이러한 개인 메소드를 개발중인 새로운 공개 메소드로 호출해야 할 것입니다. 개인적인 방법이 모든 확률에 저항한다는 것을 아는 것이 좋지 않습니까?
일부 사람들이 우리가 그 대상에 다른 공개 방법을 개발하고 있다면 이것이 테스트되어야하고 그것이 전부라고 대답 할 수 있다는 것을 알고 있습니다. 그러나 이것은 객체의 모든 공개 메소드에도 적용됩니다. 웹 앱을 개발할 때 객체의 모든 공개 메소드가 컨트롤러 메소드에서 호출되므로 컨트롤러의 구현 세부 사항으로 간주 될 수 있습니다.
그렇다면 왜 단위 테스트 객체입니까? 실제로는 어렵 기 때문에 기본 코드의 모든 분기를 트리거하는 적절한 입력으로 컨트롤러의 메소드를 테스트하고 있다는 것을 확신하는 것은 불가능하지 않습니다. 다시 말해, 스택 상태가 높을수록 모든 동작을 테스트하기가 더 어려워집니다. 그리고 개인 메소드도 마찬가지입니다.
개인적 방법과 공공 방법의 경계는 시험에있어 심리적 기준입니다. 나에게 더 중요한 기준은 다음과 같습니다.
공개 대 개인은 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 )
Power Mock 과 같은 도구가 있지만이를 지원하는 가 있지만 위험한 작업입니다. 그 이유는이를 달성하기 위해 JVM을 해킹해야하기 때문입니다.
할 수있는 한 가지 해결 방법은 (개인 메서드에 대한 테스트 사례를 작성하려는 경우)
보호 된 개인 메소드를 선언하십시오 . 그러나 여러 상황에서는 편리하지 않을 수 있습니다.
퍼블릭 또는 프라이빗 메소드 또는 함수뿐만 아니라 구현 세부 사항에 관한 것입니다. 개인 함수는 구현 세부 사항의 한 측면 일뿐입니다.
결국 단위 테스트는 화이트 박스 테스트 방식입니다. 예를 들어, 지금까지 테스트에서 무시 된 코드의 일부를 식별하기 위해 적용 범위 분석을 사용하는 사람은 구현 세부 정보로 이동합니다.
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 개의 테스트가 수행됩니다. 물론 125 대신에 몇 가지 테스트 를 수행하고 싶을 것입니다 .allPrime
있지만, 우리가 염려했던 입력 시나리오의 125 가지 조합을 모두 철저히 다루어야 할 의무는 없습니다. 몇 가지 행복한 길.
확실히 좋은 예이지만 분명한 시연이 필요했습니다. 그리고 패턴은 실제 소프트웨어로 확장됩니다. 개인 기능은 일반적으로 가장 낮은 수준의 빌딩 블록이므로 더 높은 수준의 논리를 생성하기 위해 종종 결합됩니다. 더 높은 수준에서, 우리는 다양한 조합으로 인해 더 낮은 수준의 물건을 더 많이 반복합니다.
isPrime
은 완전히 독립적이므로 모든 조합을 맹목적으로 테스트하는 것은 목적이 없습니다. 둘째, isPrime
private 이라는 순수한 기능을 표시하면 많은 디자인 규칙을 위반하여 어디서부터 시작 해야할지 모릅니다. isPrime
공개 기능이어야합니다. 즉,이 극도로 나쁜 예에 관계없이 당신이 말하는 것을 얻습니다. 그러나 실제 소프트웨어 시스템에서는 이것이 거의 좋은 아이디어가 아닌 경우 조합 테스트를 수행 하려는 전제를 기반으로합니다 .