IT

단위 테스트를 위해 비공개 메서드를 공개하는 중입니다. 좋은 생각인가요?

lottoking 2020. 8. 18. 07:55
반응형

단위 테스트를 위해 비공개 메서드를 공개하는 중입니다. 좋은 생각인가요?


중재자 참고 : 여기에 이미 39 개의 답변이 게시되었습니다. 답변 을 게시하기 전에 토론에 의미있는 내용을 추가 할 수 있는지 여부를 고려 하십시오 . 다른 사람이 이미 말한 것을 반복하는 것 이상입니다.


나는 가끔 단위 테스트를 작성하기 위해 클래스 public에서 private 메소드를 위해 등급을 지정하는 경우가 있습니다.

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

내가별로 좋아하지 않기 때문에 다른 사람들이 일을하는 것을 발견하고 ?? 나는 개인적으로 개체가 클래스 외부에서 어떤 서비스도 제공하지 않는 메소드를 공개하는 문제보다 중요하다고 생각합니다.

최신 정보

여러분의 답변에 감사 드리며, 사람들의 관심을 끌 것입니다. 일반적으로 공개 된 공개 API를 통해 테스트가 제공됩니다. 이것이 사용되는 유일한 방법이기 때문에 이에 동의합니다. 많은 언급 된 한 몇 가지 경우는 흔하지 않은 경우였으며, 수행 할 때의 이점이 그만한 가치가 있다는 생각이있었습니다.

그러나 나는 모든 사람들의 지적을 볼 수 있습니다. 그리고 제안에 대해 조금 더 생각할 때 테스트를 수용하기 위해 코드를 변경하는 것은 나쁜 생각이라고 생각합니다. 결국 테스트는 어떤 방식 으로든 지원 도구이고 원할 경우 시스템을 '지원 도구 지원'으로 변경하는 것은 노골적이라고 생각합니다. 나쁜 습관.


참고 :
이 답변은 원래 단위 테스트만으로 게터를 통해 개인 인스턴스 변수를 노출하는 좋은 이유입니까? 라는 질문에 게시되었습니다 . 여기에 존재했을 경우에 거기에 제시된 사용 사례에 따라 다를 수 있습니다.

일반적으로 필자는 테스트를 더 쉽게 만들기 위해 "프로덕션"코드를 리팩토링하는 일을 주로합니다. 그러나 나는 좋은 전화라고 생각하지 않습니다. 좋은 단위 테스트 (보통)는 클래스의 구현 세부 사항에 신경을 쓰지 않고 가시적 인 동작에만 신경을 써야합니다. 내부 스택을 테스트에 노출하는 대신 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)을 사용하면 리팩토링 단계 ( 리팩토링 패턴의 예는 Extract Method 참조) 중에 비공개 메서드가 생성 되므로 필요한 테스트 범위가 이미 있어야한다는 것입니다. . 올바로 수행되면 (물론 정확성과 관련하여 여러 의견이 섞여있을 것입니다), 테스트 할 수 있도록 비공개 메서드를 공개해야하는 것에 대해 걱정할 필요가 없습니다.


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

그러면 현재 클래스가 구현 세부 정보를 노출하지 않고 유틸리티 클래스에 명확하게 위임 할 수 있습니다. 테스트는 다른 사람들이 권장하는 페이지 매김 요구 사항과 관련이 있습니다.


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;
   } 

"추가"를 테스트 할 이유가 없습니다. 대신 "책"을 테스트 할 수 있습니다.

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


나는 유닛 테스트를받는 보너스가 일부 멤버의 가시성을 높이는 문제보다 더 크다는 데 동의하는 경향이 있습니다. 약간의 개선은 보호되고 가상으로 만든 다음 테스트 클래스에서 재정 의하여 노출하는 것입니다.

또는 별도로 테스트하려는 기능이 디자인에서 누락 된 개체를 제안하지 않습니까? 아마도 별도의 테스트 가능한 클래스에 넣을 수 있습니다. 그런 다음 기존 클래스가이 새 클래스의 인스턴스에 위임합니다.


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

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

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


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

때때로이 메서드는 ifdef 블록 (저는 주로 C ++로 작성)에 래핑되어 릴리스 용으로 컴파일되지 않습니다. 그러나 릴리스에서 프로그램의 개체 트리를 확인하는 유효성 검사 방법을 제공하는 것은 종종 유용합니다.


Guava에는 확장 된 범위 (패키지 또는 공용)가있는 메서드를 표시하기위한 @VisibleForTesting 주석이 있습니다. 같은 일에 @Private 주석을 사용합니다.

공개 API를 테스트해야하지만 때로는 일반적으로 공개되지 않는 항목을 얻는 것이 편리하고 합리적입니다.

언제:

  • 클래스를 여러 클래스로 나눔으로써 toto에서 클래스의 가독성이 현저히 떨어집니다.
  • 더 테스트 가능하게 만들기 위해
  • 내부에 대한 테스트 액세스를 제공하면 트릭을 수행 할 수 있습니다.

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


일반적으로 이러한 메서드를 그대로두고 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개인 메서드에 액세스 할 수 있도록 특별히 deigned 라는 특수 클래스 가 있습니다.

MSDN 또는 Stack Overflow 에서 자세한 내용을 확인 하십시오.

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

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

그래도 개인 메서드를 테스트하지 말라는 일반적인 권장 사항을 고수하지만 평소와 같이 항상 예외가 있습니다.


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

참고 URL : https://stackoverflow.com/questions/31646092/is-unit-testing-alone-ever-a-good-reason-to-expose-private-instance-variables-vi

반응형