동반자 클래스를 사용하는 C ++의 단위 테스트 전용 메소드


15

나는 이것이 논쟁의 여지가 있다는 것을 알고 있지만 이것이 최선의 선택이라고 가정 해 봅시다. 이 작업을 수행하는 실제 기술이 무엇인지 궁금합니다. 내가 보는 접근 방식은 다음과 같습니다.

1) 내가 시험하고 싶은 수업의 친구 수업을하십시오.

2) 친구 클래스에서 테스트 된 클래스의 개인 메소드를 호출하는 공개 메소드를 작성하십시오.

3) 친구 수업의 공개 방법을 테스트하십시오.

위의 단계를 설명하는 간단한 예는 다음과 같습니다.

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

편집하다:

토론에서 사람들이 내 코드베이스에 대해 궁금해하는 답변 중 하나를 따르는 것을 볼 수 있습니다.

내 클래스에는 다른 메소드에 의해 호출되는 메소드가 있습니다. 이러한 메소드 중 어느 것도 클래스 외부에서 호출해서는 안되므로 개인용이어야합니다. 물론 그것들은 하나의 방법으로 들어갈 수 있지만 논리적으로는 훨씬 더 분리되어 있습니다. 이러한 방법은 단위 테스트를 보장하기에 충분히 복잡하며 성능 문제로 인해 이러한 방법을 리팩토링해야 할 가능성이 높으므로 리팩토링이 아무 것도 깨지지 않도록 테스트하는 것이 좋습니다. 테스트를 포함하여이 프로젝트를 수행하는 유일한 사람은 아니지만 팀에서 일하는 유일한 사람은 아닙니다.

위에서 말한 것처럼, 내 질문은 개인 메소드에 대한 단위 테스트를 작성하는 것이 좋은 습관인지 여부에 관한 것이 아니라 피드백을 주셔서 감사합니다.


5
의심스러운 질문에 대한 의심스러운 제안이 있습니다. 릴리스 된 코드가 테스트에 대해 알아야하기 때문에 친구의 결합을 좋아하지 않습니다. 아래의 Nir의 대답은 그것을 완화하는 한 가지 방법이지만 여전히 테스트에 맞게 클래스를 변경하는 것을 좋아하지 않습니다. 상속을 자주 사용하지 않기 때문에 때로는 개인 메서드를 보호하고 테스트 클래스가 필요에 따라 상속 및 노출하도록합니다. 나는이 의견에 대해 적어도 세 가지 "부스 (boo hisses)"를 기대하지만, 실제로는 공개 API와 테스트 API가 다르고 여전히 개인 API와는 다를 수 있습니다. Meh.
J Trana

4
@JTrana : 적절한 답변으로 작성하지 않겠습니까?
Bart van Ingen Schenau

확인. 이것은 당신이 자랑스러워하는 사람들 중 하나가 아니지만 희망적으로 도움이 될 것입니다.
J Trana

답변:


23

내가 자주 사용하는 친구 (어떤 의미에서)에 대한 대안은 내가 access_by로 알게 된 패턴이다. 꽤 간단합니다.

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

이제 클래스 B가 A 테스트에 관여한다고 가정합니다. 다음과 같이 작성할 수 있습니다.

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

그런 다음이 access_by 전문화를 사용하여 A의 개인 메소드를 호출 할 수 있습니다. 기본적으로 이것이하는 일은 A의 개인 메소드를 호출하려는 클래스의 헤더 파일에 우정을 선언하는 것입니다. 또한 A의 소스를 변경하지 않고도 A에 친구를 추가 할 수 있습니다. 관용적으로, 그것은 A의 소스를 읽는 사람에게 A가 B의 인터페이스를 확장한다는 의미에서 B를 진정한 친구로 나타내지 않는다는 것을 나타냅니다. 오히려 A의 인터페이스는 주어진대로 완성되고 B는 A에 대한 특별한 액세스가 필요합니다 (테스트의 좋은 예입니다. 부스트 파이썬 바인딩을 구현할 때이 패턴을 사용했습니다. 때로는 C ++에서 비공개 해야하는 함수가 편리합니다) 구현을 위해 파이썬 레이어에 노출하십시오).


를 만드는 유효한 유스 케이스에 대해 궁금한 friend access_by것은 친구가 아닌 첫 번째 충분하지 않습니다. 중첩 된 구조체이기 때문에 A 내의 모든 것에 액세스 할 수 있습니까? 예. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
악몽

10

테스트하기 어려운 경우 잘못 작성되었습니다.

자체 테스트를 보증 할 정도로 복잡한 개인용 메소드가있는 클래스가 있으면 클래스가 너무 많이 수행됩니다. 안에는 다른 클래스가 있습니다.

테스트하려는 개인 메소드를 새 클래스로 추출하여 공개하십시오. 새 클래스를 테스트하십시오.

이 리팩토링을 통해 코드를 쉽게 테스트 할 수있을뿐만 아니라 코드를보다 쉽게 ​​이해하고 유지 관리 할 수 ​​있습니다.


1
나는이 답변에 전적으로 동의합니다. 공용 메소드를 테스트하여 개인 메소드를 완전히 테스트 할 수 없다면 뭔가 옳지 않으며 개인 메소드를 자체 클래스로 리팩토링하는 것이 좋은 해결책이 될 것입니다.
David Perfors

4
내 코드베이스에서 클래스에는 계산 그래프를 초기화하는 매우 복잡한 메소드가 있습니다. 여러 하위 기능을 순차적으로 호출하여 다양한 측면을 달성합니다. 각 하위 기능은 매우 복잡하며 코드의 총계는 매우 복잡합니다. 그러나이 클래스와 올바른 순서로 호출되지 않으면 하위 기능은 의미가 없습니다. 모든 사용자가 신경 쓰는 것은 계산 그래프가 완전히 초기화되는 것입니다. 중간체는 사용자에게 무가치합니다. 이걸 리팩토링하는 방법과 왜 프라이빗 메소드를 테스트하는 것보다 더 합리적인지 듣고 싶습니다.
Nir Friedman

1
@Nir : 사소한 일을하십시오. 모든 메소드가 공개 된 클래스를 추출하고 기존 클래스를 새 클래스 주위의 외관으로 만듭니다.
케빈 클라인

이 답변은 정확하지만 실제로는 작업중 인 데이터에 따라 다릅니다. 내 경우에는 실제 테스트 데이터가 제공되지 않으므로 실시간 데이터를 관찰 한 다음 응용 프로그램에 "주입"하여 일부를 작성해야합니다. 단일 데이터는 처리하기에 너무 복잡하므로 실시간 데이터를 실제로 재생하는 것보다 각각을 대상으로하는 실시간 데이터의 하위 집합 인 부분 테스트 데이터를 인위적으로 생성하는 것이 훨씬 쉽습니다. 개인 기능은 복잡하지 않습니다 (각각의 기능을 가진) 몇몇 다른 작은 클래스의 구현을 요구하기에 충분
rbaleksandar

4

비공개 메소드를 테스트해서는 안됩니다. 기간. 클래스를 사용하는 클래스는 해당 클래스에서 사용하는 메소드가 아니라 제공하는 메소드에만 관심이 있습니다.

코드 적용 범위가 걱정된다면 공용 메서드 호출 중 하나에서 해당 프라이빗 메서드를 테스트 할 수있는 구성을 찾아야합니다. 그렇게 할 수 없다면 처음에 방법을 사용하는 것이 무엇입니까? 단순히 도달 할 수없는 코드입니다.


6
사적인 방법의 요점은 개발을 용이하게하는 것 (문제의 분리 또는 DRY 또는 그 밖의 많은 것들을 유지함)이지만 영구적이지 않습니다. 그들은 그런 이유로 비공개입니다. 그것들은 하나의 구현에서 다음 구현으로 급격하게 나타나거나 사라지거나 기능이 변경 될 수 있으므로 단위 테스트에 연결하는 것이 항상 실용적이거나 유용한 것은 아닙니다.
Ampt

8
항상 실용적이거나 유용하지는 않지만 테스트해서는 안된다는 말과는 거리가 멀다. 당신은 다른 사람들의 개인적인 방법 인 것처럼 개인적인 방법에 대해 이야기하고 있습니다. "그들은 나타날 수있다 사라진다 ...". 아니요, 그들은 할 수 없었습니다. 직접 단위 테스트를 수행하는 경우 직접 관리해야하기 때문입니다. 구현을 변경하면 테스트가 변경됩니다. 요컨대, 당신의 담요 진술은 정당하지 않습니다. OP에 대해 경고하는 것이 좋지만 그의 질문은 여전히 ​​정당화되며 귀하의 답변은 실제로 대답하지 않습니다.
Nir Friedman

2
또한 OP는 OP가 그것이 논쟁의 여지가 있다는 것을 알고 있다고 말했다. 그가 어쨌든 그것을 원한다면, 그는 정말로 그럴만한 이유가 있습니까? 우리 중 어느 누구도 그의 코드베이스의 세부 사항을 알지 못합니다. 내가 사용하는 코드에는 경험이 풍부하고 전문적인 프로그래머가 있으며, 경우에 따라 개인 메서드를 단위 테스트하는 것이 유용하다고 생각했습니다.
Nir Friedman

2
-1, "개인 메소드를 테스트해서는 안됩니다."와 같은 답변 IMHO는 도움이되지 않습니다. 이 사이트에서는 개인 테스트 방법과 테스트 방법을 테스트하지 않을시기에 대해 충분히 논의했습니다. OP는이 논의에 대해 알고 있음을 보여 주었으며, 분명히 자신의 경우에 개인 방법을 테스트하는 것이 길이라는 가정하에 솔루션을 찾고 있습니다.
Doc Brown

3
여기서 문제는 이것이 매우 고전적인 XY 문제 일 수 있다는 것 입니다. OP는 자신의 개인 방법을 여러 가지 이유로 단위 테스트해야한다고 생각합니다. 실제로 그는 더 실용적인 관점에서 테스트에 접근하여 개인을 볼 수 있습니다 클래스의 최종 사용자와의 계약 인 공용 메소드에 대한 단순한 도우미 기능으로서의 메소드.
Ampt

3

이를 수행하는 몇 가지 옵션이 있지만 본질적으로 모듈의 공개 인터페이스를 수정하여 내부 구현 세부 사항에 액세스 할 수 있도록합니다 (단위 테스트를 밀접하게 결합 된 클라이언트 종속성으로 효과적으로 변환해야 함). 전혀 의존 하지 않습니다 ).

  • 테스트 된 클래스에 친구 (클래스 또는 함수) 선언을 추가 할 수 있습니다.

  • 테스트 된 코드를 작성 #define private public하기 전에 테스트 파일의 시작 부분에 추가 할 수 있습니다 #include. 테스트 된 코드가 이미 컴파일 된 라이브러리 인 경우 헤더가 더 이상 이미 컴파일 된 이진 코드와 일치하지 않을 수 있습니다 (UB 발생).

  • 테스트 된 클래스에 매크로를 삽입하고 나중에 해당 매크로의 의미 (테스트 코드에 대한 다른 정의)를 결정할 수 있습니다. 이렇게하면 내부를 테스트 할 수 있지만 타사 클라이언트 코드가 클래스에 해킹 될 수 있습니다 (추가 한 선언에서 자체 정의를 작성하여).


2

의심스러운 질문에 대한 의심스러운 제안이 있습니다. 릴리스 된 코드가 테스트에 대해 알아야하기 때문에 친구의 결합을 좋아하지 않습니다. Nir의 대답은 그것을 완화하는 한 가지 방법이지만 테스트에 맞게 클래스를 변경하는 것을 좋아하지는 않습니다.

상속을 자주 사용하지 않기 때문에 때로는 개인 메서드를 보호하고 테스트 클래스가 필요에 따라 상속 및 노출하도록합니다. 실제로 공개 API와 테스트 API는 개인 API와 다르고 여전히 다르기 때문에 일종의 바인드가 가능합니다.

여기이 트릭에 의지 한 실용적인 예가 있습니다. 임베디드 코드를 작성하고 상태 머신에 상당히 의존합니다. 외부 API는 내부 상태 머신 상태를 반드시 알아야 할 필요는 없지만 테스트는 디자인 문서의 상태 머신 다이어그램에 대한 적합성을 테스트해야합니다. "현재 상태"게터를 보호 된 것으로 노출 한 다음 테스트 액세스 권한을 부여하여 상태 머신을보다 완벽하게 테스트 할 수 있습니다. 나는 종종 이런 종류의 수업을 블랙 박스로 시험하기가 어렵다는 것을 알게된다.


Java 접근 방식이지만 개인 패키지를 기본 레벨로 설정하는 대신 동일한 패키지 (테스트 클래스)의 다른 클래스가이를 볼 수 있도록하는 것이 표준입니다.

0

친구를 사용하지 않으려면 많은 해결 방법으로 코드를 작성할 수 있습니다.

클래스를 작성할 수 있으며 개인 메소드를 전혀 가질 수 없습니다. 컴파일 유닛 내에서 구현 함수를 만들고 클래스가이를 호출하고 액세스해야하는 데이터 멤버를 전달하게하면됩니다.

그리고 예. 앞으로 헤더를 변경하지 않고도 서명을 변경하거나 새로운 "구현"방법을 추가 할 수 있습니다.

그만한 가치가 있든 없든 무게를 달아야합니다. 그리고 많은 사람들이 누가 당신의 헤더를 보게 될지에 달려 있습니다.

타사 라이브러리를 사용하는 경우 단위 테스터에게 친구 선언이 표시되지 않습니다. 라이브러리를 구축하고 싶지 않을 때 테스트를 실행하고 싶지 않습니다. 불행히도 너무 많은 타사 오픈 소스 라이브러리가 구축했습니다.

테스트는 라이브러리 작성자가 아닌 사용자의 작업입니다.

그러나 모든 클래스가 라이브러리 사용자에게 표시되는 것은 아닙니다. 많은 클래스는 "구현"이며 제대로 작동하도록하는 가장 좋은 방법을 구현합니다. 이 경우에도 개인 메소드와 멤버가있을 수 있지만 단위 테스터가 테스트 할 수 있습니다. 강력한 코드를 더 빨리 만들 수 있다면 그렇게해야합니다. 그렇게해야하는 사람들이 쉽게 유지할 수 있습니다.

수업의 사용자가 모두 자신의 회사 또는 팀 내에있는 경우 회사의 코딩 표준에서 허용한다고 가정하면 해당 전략에 대해 좀 더 긴장을 풀 수 있습니다.

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