Programing

단위 테스트를 위해 개인용 메소드를 공개하는 것은 좋은 생각입니까?

lottogame 2020. 3. 20. 08:13
반응형

단위 테스트를 위해 개인용 메소드를 공개하는 것은 좋은 생각입니까?


중재자 참고 : 여기에 이미 39 개의 답변이 게시되어 있습니다 (일부는 삭제되었습니다). 답변 을 게시하기 전에 토론에 의미있는 것을 추가 할 수 있는지 여부를 고려 하십시오 . 다른 사람이 이미 말한 것을 반복했을 가능성이 높습니다.


가끔 단위 테스트를 작성하기 위해 클래스 공용에서 개인용 메소드를 작성해야하는 경우가 있습니다.

일반적으로 메소드에는 클래스의 다른 메소드간에 공유되는 논리가 포함되어 있고 자체적으로 논리를 테스트하는 것이 더 어렵거나 스레딩 문제에 대해 걱정할 필요없이 동기식 스레드에서 사용되는 논리를 테스트하고 싶을 수도 있습니다. .

다른 사람들이 스스로하는 일을하는 것을 좋아합니까? 왜냐하면 저는 그 일을 정말로 좋아하지 않기 때문입니다 ?? 개인적으로 보너스는 클래스 외부에서 서비스를 제공하지 않는 메소드를 공개하는 문제보다 중요하다고 생각합니다 ...

최신 정보

답변 해 주셔서 감사합니다. 사람들의 관심을 끌었던 것 같습니다. 클래스가 사용될 유일한 방법이기 때문에 공개 API를 통해 테스트를 수행해야한다는 일반적인 합의가 있다고 생각합니다. 위에서 언급 한 위에서 언급 한 몇 가지 사례는 드문 경우이며이를 수행 할 때의 이점이 가치 있다고 생각했습니다.

그러나 모든 사람들이 결코 그런 일이 일어나지 않아야한다는 점을 알 수 있습니다. 그리고 그것에 대해 조금 더 생각할 때 테스트를 수용하기 위해 코드를 변경하는 것은 나쁜 생각이라고 생각합니다. 결국 테스트가 일종의 지원 도구라고 생각하고 시스템을 '지원 도구를 지원하도록'변경하는 것이 뻔하다 고 생각합니다. 나쁜 연습.


참고 :
이 답변은 원래 질문에 대해 게시되었습니다. 단위 테스트만으로도 getter를 통해 프라이빗 인스턴스 변수를 노출해야하는 좋은 이유가 있습니까? 이것은 이것에 합병되었으므로, 여기에 제시된 유스 케이스에 특유한 것이 될 수 있습니다.

일반적인 설명으로, 나는 보통 테스트하기 쉽도록 "프로덕션"코드를 리팩토링하는 일을합니다. 그러나 나는 이것이 좋은 전화라고 생각하지 않습니다. 좋은 단위 테스트는 (보통) 클래스의 구현 세부 사항, 가시적 인 동작에만 신경 쓰지 않아야합니다. 대신 시험에 내부 스택을 노출, 당신은 클래스를 사용하면 호출 한 후 그것을 기대하는 순서로 페이지를 반환하는지 테스트 할 수 first()또는 last().

예를 들어, 다음 의사 코드를 고려하십시오.

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

개인적으로, 나는 대중 API를 사용하지 않고 단위 테스트를 거라고 나는 확실히 개인 방법 공개는하지 않습니다 않겠다고 단지 시험에 쉽게 그것을 만들 수 있습니다.

개인 메소드를 개별적 으로 테스트하려는 경우 Java에서 Easymock / Powermock 을 사용하여이를 수행 할 수 있습니다.

당신은 그것에 대해 실용적이어야하고 또한 사물을 테스트하기 어려운 이유를 알고 있어야합니다.

' 테스트 듣기 '-테스트하기 어려운 경우 디자인에 대해 이야기하고 있습니까? 이 방법에 대한 테스트가 사소하고 공개 API를 통한 테스트로 쉽게 다루어지는 위치를 리팩터링 할 수 있습니까?

여기에 무엇 마이클 깃털 것은 '에 말을 가지고있어 효과적으로 레거시 코드로 작업 "

"많은 사람들 이이 문제를 해결하는 방법을 알아 내려고 많은 시간을 소비합니다 ... 실제 답변은 개인 메소드를 테스트 해야하는 충동이 있다면 메소드가 비공개가되어서는 안된다는 것입니다. "당신을 귀찮게 할 것입니다. 그것은 별도의 책임의 일부이기 때문입니다. 다른 반에 있어야합니다." [ M. Feathers의 레거시 코드 (2005)로 효과적으로 작업 ]


다른 사람들이 말했듯이, 개인 메소드를 단위 테스트하는 것이 다소 의심됩니다. 개인 구현 세부 정보가 아닌 공용 인터페이스를 단위 테스트합니다.

즉, C #에서 개인 단위로 테스트 할 때 사용하는 기술은 액세스 가능성 보호를 개인에서 내부로 다운 그레이드 한 다음 InternalsVisibleTo 사용하여 단위 테스트 어셈블리를 친구 어셈블리로 표시하는 것 입니다. 그러면 단위 테스트 어셈블리가 내부를 공개로 취급 할 수 있지만 실수로 공개 표면 영역에 추가하는 것에 대해 걱정할 필요는 없습니다.


많은 답변은 공용 인터페이스 테스트 만 제안하지만 IMHO 이것은 비현실적입니다. 메서드가 5 단계를 수행하는 작업을 수행하는 경우 5 단계를 모두 테스트하지 않고 별도로 테스트해야합니다. 이 모든 다섯 가지 방법, 시험이 필요합니다 (테스트 이외의) 다른 수 있습니다을 private.

"비공개"메소드를 테스트하는 일반적인 방법은 모든 클래스에 고유 한 인터페이스를 제공하고 "비공개"메소드 public를 작성하지만 인터페이스에는 포함시키지 않는 것입니다. 이런 식으로, 그들은 여전히 ​​테스트 할 수 있지만 인터페이스를 팽창시키지 않습니다.

예, 이렇게하면 파일 및 클래스가 팽창합니다.

예, 이렇게하면 publicprivate지정자가 중복됩니다.

예, 이것은 엉덩이에 통증이 있습니다.

불행히도 이것은 코드를 테스트 할 수 있도록 만드는 많은 희생 중 하나입니다 . 아마도 미래 언어 (또는 미래 버전의 C # / Java)에는 클래스 및 모듈 테스트 가능성을보다 편리하게 만드는 기능이있을 것입니다. 그러나 그 동안 우리는이 후프를 뛰어 넘어야합니다.


각 단계가 자신의 클래스 여야한다고 주장하는 사람들도 있지만 동의하지 않습니다. 모두 공유 상태라면 5 가지 방법으로 5 개의 클래스를 만들 이유가 없습니다. 더 나쁜 것은 이로 인해 파일 및 클래스 팽창이 발생한다는 것입니다. 또한 모듈의 공개 API를 감염시킵니다. 다른 모든 클래스 public에서 테스트하려는 경우 해당 클래스가 모두 있어야합니다 (또는 동일한 모듈에 테스트 코드를 포함하여 테스트 코드를 제품과 함께 제공함). .


단위 테스트는 클래스의 다른 부분에서 클래스를 사용하는 유일한 방법 인 공개 계약을 테스트해야합니다. 비공개 메소드는 구현 세부 사항이므로 공개 API가 올바르게 작동하는 한 테스트 사례를 변경하지 않고 구현이 중요하지 않고 변경 될 수있는 경우 테스트하지 않아야합니다.


IMO, 당신은 당신의 클래스가 어떻게 구현되었는지에 대해 깊이 가정하지 않는 테스트를 작성해야합니다. 나중에 다른 내부 모델을 사용하여 리팩토링하고 싶을 수도 있지만 이전 구현에서 제공하는 것과 동일한 보장을 계속합니다.

이를 염두에 두면서 현재 클래스의 내부 구현에 관계없이 계약이 여전히 유지되고 있는지 테스트하는 데 집중하는 것이 좋습니다. 퍼블릭 API의 속성 기반 테스트.


비공개 패키지로 만드는 것은 어떻습니까? 그런 다음 테스트 코드는 코드와 패키지의 다른 클래스도 볼 수 있지만 여전히 사용자에게 숨겨져 있습니다.

그러나 실제로는 개인 메소드를 테스트해서는 안됩니다. 이는 구현 세부 사항이며 계약의 일부가 아닙니다. 그들이하는 모든 것은 공개 메소드를 호출하여 다루어야합니다 (공개 메소드로 실행되지 않는 코드가있는 경우 계속 진행해야 함). 개인 코드가 너무 복잡하면 클래스가 너무 많은 일을하고 리팩토링을 원할 것입니다.

메소드를 공개하는 것은 큰 노력입니다. 일단 그렇게하면 사람들은 그것을 사용할 수 있으며 더 이상 변경할 수 없습니다.


업데이트 : 나는 다른 많은 장소 에서이 질문에 대한보다 광범위하고 완전한 답변을 추가했습니다. 이것은 내 블로그에서 찾을 수 있습니다 .

테스트하기 위해 공개해야 할 것이 있다면, 이는 일반적으로 테스트중인 시스템이 단일 책임 원칙을 따르지 않는다는 것을 암시합니다 . 따라서 소개해야 할 누락 된 클래스가 있습니다. 코드를 새 클래스로 추출한 후 공개하십시오. 이제 쉽게 테스트 할 수 있으며 SRP를 따르고 있습니다. 다른 클래스는 단순히 컴포지션을 통해이 새 클래스를 호출해야합니다.

어셈블러를 테스트하기 위해 코드를 보이는 것으로 표시하는 등의 방법을 공개하거나 사용하는 방법은 항상 최후의 수단이어야합니다.

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

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

유효성 검증기 오브젝트를 도입하여이를 리 팩터하십시오.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

이제 유효성 검사기가 올바르게 호출되었는지 테스트하기 만하면됩니다. 실제 유효성 검사 프로세스 (이전의 개인 논리)는 순수 격리로 테스트 할 수 있습니다. 이 유효성 검사를 통과하기 위해 복잡한 테스트 설정이 필요하지 않습니다.


좋은 답변입니다. 내가 언급하지 않은 것 중 하나는 테스트 중심 개발 (TDD)을 사용하여 리팩토링 단계에서 개인 메소드가 작성되므로 ( 리팩토링 패턴의 예는 추출 메소드 참조 ) 이미 필요한 테스트 범위를 가지고 있어야한다는 것입니다 . 올바르게 수행되면 (물론 정확성에 관해서는 다양한 의견이 나올 것입니다), 테스트 할 수 있도록 개인용 메소드를 공개해야하는 것에 대해 걱정할 필요가 없습니다.


스택 관리 알고리즘을 유틸리티 클래스로 분리하지 않는 이유는 무엇입니까? 유틸리티 클래스는 스택을 관리하고 공개 접근자를 제공 할 수 있습니다. 단위 테스트는 구현 세부 사항에 중점을 둘 수 있습니다. 알고리즘 적으로 까다로운 클래스에 대한 심층 테스트는 엣지 케이스를 축소하고 적용 범위를 보장하는 데 매우 유용합니다.

그런 다음 현재 클래스는 구현 세부 정보를 노출시키지 않고 유틸리티 클래스에 깔끔하게 위임 할 수 있습니다. 테스트는 다른 사람들이 권장 한대로 페이지 매김 요구 사항과 관련이 있습니다.


Java에는 패키지를 비공개 로 만드는 옵션도 있습니다 (예 : 가시성 수정 자 제외). 단위 테스트가 테스트중인 클래스와 동일한 패키지에있는 경우 이러한 메소드를 볼 수 있어야하며 메소드를 완전히 공개하는 것보다 조금 더 안전합니다.


전용 메소드는 일반적으로 "도우미"메소드로 사용됩니다. 따라서 기본 값만 반환하고 특정 개체 인스턴스에서는 작동하지 않습니다.

테스트하려는 경우 몇 가지 옵션이 있습니다.

  • 반사 사용
  • 메소드에 패키지 액세스 권한 부여

또는 새 클래스에 적합한 후보 인 경우 도우미 메소드를 공용 메소드로 사용하여 새 클래스를 작성할 수 있습니다.

여기아주 좋은 기사가 있습니다 .


C #을 사용하는 경우 내부 메소드를 만들 수 있습니다. 그렇게하면 공용 API를 오염시키지 않습니다.

그런 다음 dll에 속성을 추가하십시오.

[조립품 : InternalsVisibleTo ( "MyTestAssembly")]

이제 모든 메소드가 MyTestAssembly 프로젝트에 표시됩니다. 아마도 완벽하지는 않지만 테스트하기 위해 개인 메소드를 공개하는 것이 좋습니다.


필요한 경우 리플렉션을 사용하여 개인 변수에 액세스하십시오.

그러나 실제로 클래스의 내부 상태에 신경 쓰지 않고 공개 메소드가 예상 할 수있는 상황에서 예상 한 것을 반환하는지 테스트하고 싶습니다.


단위 테스트 측면에서 더 많은 메소드를 추가해서는 안됩니다. 나는 당신이 first()메소드 에 대해 테스트 케이스를 만드는 것이 더 좋을 것이라고 믿는다 . 각 테스트 전에 호출 될 것이다. 당신은 여러 번 호출 할 수 있습니다 - next(), previous()그리고 last()성과가 당신의 기대를 일치하는지 확인합니다. 테스트 목적으로 만 클래스에 더 많은 메소드를 추가하지 않으면 테스트의 "블랙 박스"원칙을 고수 할 것입니다.


나는 그것이 당신이 이익을 얻었는지 잠재적으로 문제가 있는지 확실하지 않은 것이 나쁜 생각이라고 말하고 싶습니다. 개인 계약을 테스트하기 위해 통화 계약을 변경하는 경우 클래스 사용법을 테스트하는 것이 아니라 의도하지 않은 인공 시나리오를 만드는 것입니다.

또한, 방법을 공개로 선언함으로써 6 개월 동안 (메서드를 공개 한 유일한 이유는 테스트 용이라는 사실을 잊어 버린 후) 누군가 (또는 프로젝트를 넘긴 경우) 누군가 완전히 다른 원을 얻는다는 것 이를 사용하지 않으면 의도하지 않은 결과 및 / 또는 유지 보수 악몽이 발생할 수 있습니다.


먼저 메소드가 다른 클래스로 추출되어 공개되어야하는지 확인하십시오. 그렇지 않은 경우 패키지로 보호하고 Java에서 @VisibleForTesting으로 주석을 달도록하십시오 .


업데이트에서 공개 API를 사용하여 테스트하는 것이 좋다고 말합니다. 실제로 두 개의 학교가 있습니다.

  1. 블랙 박스 테스트

    블랙 박스 스쿨은 그 클래스가 아무도 그 구현을 볼 수없는 블랙 박스로 간주되어야한다고 말합니다. 이것을 테스트하는 유일한 방법은 공용 API를 사용하는 것입니다. 클래스의 사용자가 사용하는 것처럼.

  2. 화이트 박스 테스트.

    화이트 박스 스쿨은 자연스럽게 클래스 구현에 대한 지식을 사용하고 클래스가 제대로 작동하는지 테스트합니다.

나는 토론에 참여할 수 없다. 클래스 (또는 라이브러리 또는 기타)를 테스트하는 두 가지 뚜렷한 방법이 있다는 것이 흥미로울 것이라고 생각했습니다.


실제로이 작업을 수행해야하는 상황이 있습니다 (예 : 복잡한 알고리즘을 구현할 때). 그냥 패키지 전용으로하면 충분합니다. 그러나 대부분의 경우 논리가 다른 클래스로 분해되어야하는 너무 복잡한 클래스가있을 수 있습니다.


독립적으로 테스트하려는 개인 메소드는 클래스에 다른 "개념"이 묻혀 있음을 나타냅니다. 해당 "개념"을 자체 클래스로 추출하여 별도의 "단위"로 테스트하십시오.

이 비디오보고 주제에 대해 정말 흥미로운 내용을 살펴보십시오 .


테스트에서 코드를 지시하지 마십시오. 나는 TDD 또는 다른 DD에 대해 말하고 있지 않습니다. 정확하게 당신의 요구입니다. 앱에 공개 된 방법이 필요합니까? 그렇다면 테스트하십시오. 그렇지 않은 경우 테스트를 위해 공개하지 마십시오. 변수 및 기타와 동일합니다. 응용 프로그램의 요구에 따라 코드가 지시되고 테스트에서 요구가 충족되는지 테스트하십시오. (다시 테스트를 의미하는 것은 아니며 테스트 목표를 달성하기 위해 클래스 구조를 변경하는 것을 의미하지는 않습니다).

대신 "높은 테스트"를해야합니다. private 메소드를 호출하는 메소드를 테스트하십시오. 그러나 테스트는 "구현 결정"이 아니라 응용 프로그램 요구 사항을 테스트해야합니다.

예를 들어 (여기의 의사 코드);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

"add"를 테스트 할 이유가 없습니다. 대신 "books"를 테스트 할 수 있습니다.

테스트가 코드 디자인 결정을 내릴 수 있도록하지 마십시오. 결과를 얻는 방법이 아니라 예상 결과를 얻는 지 테스트하십시오.


나는 단위 테스트의 보너스가 일부 회원의 가시성을 높이는 문제보다 중요하다는 데 동의하는 경향이 있습니다. 약간의 개선은 그것을 보호하고 가상으로 만든 다음 테스트 클래스에서 재정 의하여 공개하는 것입니다.

또는 기능을 별도로 테스트하려는 경우 디자인에서 누락 된 객체를 제안하지 않습니까? 어쩌면 별도의 테스트 가능한 클래스에 넣을 수 있습니다 ... 기존 클래스는이 새로운 클래스의 인스턴스에 위임합니다.


나는 일반적으로 테스트 클래스를 테스트중인 클래스와 동일한 프로젝트 / 어셈블리에 유지합니다.
이렇게 internal하면 함수 / 클래스를 테스트 할 수 있도록 가시성이 필요 합니다.

이것은 테스트 과정을 걸러 내야하는 건물 프로세스를 다소 복잡하게 만듭니다. 모든 테스트 클래스의 이름을 지정 TestedClassTest하고 정규식을 사용하여 해당 클래스를 필터링하여 이를 달성합니다 .

이것은 물론 질문의 C # / .NET 부분에만 적용됩니다.


나는 종종 같은 방법이라는 것을 추가 할 것이다 validate, verify, check이 객체의 내부 상태를 테스트하기 위해 호출 할 수 있도록 클래스, 등.

때때로이 메소드는 ifdef 블록 (주로 C ++로 작성)으로 래핑되어 릴리스 용으로 컴파일되지 않습니다. 그러나 릴리스시 프로그램의 오브젝트 트리를 검사하는 검증 방법을 제공하는 것이 종종 유용합니다.


구아바에는 스코프 (패키지 또는 퍼블릭)가 확대 된 메소드를 표시하기위한 @VisibleForTesting 주석이 있습니다. 나는 같은 것에 @Private 주석을 사용한다.

공개 API를 테스트해야하지만, 일반적으로 공개되지 않은 것들을 얻는 것이 편리하고 합리적인 경우가 있습니다.

언제:

  • 클래스를 여러 클래스로 나눠서 읽기가 쉽지 않습니다.
  • 더 테스트 가능하게하기 위해
  • 내부에 테스트 액세스를 제공하면 트릭을 수행 할 수 있습니다.

종교가 공학을 능가하는 것 같습니다.


나는 보통 이러한 메소드를 그대로두고 protected유닛 테스트를 동일한 패키지 (그러나 다른 프로젝트 또는 소스 폴더에)에 배치합니다. 클래스 로더는 동일한 네임 스페이스 내에 배치하기 때문에 모든 보호 된 메소드에 액세스 할 수 있습니다.


아니, 그 고양이를 스키닝하는 더 좋은 방법이 있기 때문입니다.

일부 단위 테스트 하네스는 클래스 정의에서 매크로를 사용하여 테스트 모드에서 빌드 될 때 자동으로 확장되어 후크를 만듭니다. 매우 C 스타일이지만 작동합니다.

보다 쉬운 OO 관용구는 "비공개"가 아닌 "보호 된"테스트하려는 항목을 만드는 것입니다. 테스트 하네스는 테스트중인 클래스에서 상속 된 다음 모든 보호 된 멤버에 액세스 할 수 있습니다.

또는 "친구"옵션을 선택하십시오. 개인적으로 이것은 캡슐화 규칙을 위반하기 때문에 가장 좋아하는 C ++의 기능이지만 C ++이 일부 기능을 구현하는 방법에 필요합니다.

어쨌든 단위 테스트를 수행하는 경우 해당 멤버에 값을 주입해야 할 가능성이 높습니다. 화이트 박스 문자 메시지는 완벽하게 유효합니다. 그리고 정말 것이다 당신의 캡슐화를 휴식.


다른 사람들의 의견에서 알 수 있듯이 단위 테스트는 공개 API에 중점을 두어야합니다. 그러나 장단점과 정당성을 제외하고 리플렉션을 사용하여 단위 테스트에서 개인 메서드를 호출 할 수 있습니다. 물론 JRE 보안이 허용하는지 확인해야합니다. private 메소드 호출은 Spring Framework가 ReflectionUtils 와 함께 사용하는 것입니다 ( makeAccessible(Method)메소드 참조 ).

다음은 전용 인스턴스 메소드가있는 작은 예제 클래스입니다.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

그리고 프라이빗 인스턴스 메소드를 실행하는 예제 클래스.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

B를 실행하면 인쇄됩니다. Doing something private.실제로 필요할 경우 리플렉션을 사용하여 프라이빗 인스턴스 메서드에 액세스 할 수 있습니다.


.Net에는 클래스의 PrivateObject전용 메소드에 액세스 할 수 있도록 특별히 고안된 특수 클래스 가 있습니다.

MSDN 또는 여기에서 스택 오버플로대해 자세히 알아보십시오.

(지금까지 아무도 언급하지 않았는지 궁금합니다.)

이것이 충분하지 않은 상황이 있지만,이 경우에는 반사를 사용해야합니다.

그래도 개인 방법을 테스트하지 않는 일반적인 권장 사항을 준수하지만 평소와 같이 항상 예외가 있습니다.


개인적으로 개인 메서드를 테스트 할 때도 동일한 문제가 있으며 일부 테스트 도구가 제한되어 있기 때문입니다. 제한된 도구로 인해 디자인이 아닌 도구를 변경해야하는 경우 디자인이 제한적이지 않습니다. C #을 요구하면 좋은 테스트 도구를 제안 할 수 없지만 Java의 경우 TestNG 및 PowerMock의 두 가지 강력한 도구가 있으며 .NET 플랫폼에 해당하는 테스트 도구를 찾을 수 있습니다

참고 URL : https://stackoverflow.com/questions/7075938/making-a-private-method-public-to-unit-test-it-good-idea

반응형